├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── issue-template.md ├── PULL_REQUEST_TEMPLATE.md ├── auto_assign.yml └── workflows │ └── builder.yml ├── .gitignore ├── GetARock ├── .swiftlint.yml ├── GetARock.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── GetARock │ ├── Global │ │ ├── Base │ │ │ └── BaseViewController.swift │ │ ├── Extension │ │ │ ├── CollectionReference+Extension.swift │ │ │ ├── Encodable+Extension.swift │ │ │ ├── NSObject+Extension.swift │ │ │ ├── UIColor+Extension.swift │ │ │ └── UIView+Extension.swift │ │ ├── Literals │ │ │ ├── ImageLiteral.swift │ │ │ └── StringLiteral.swift │ │ ├── Resource │ │ │ ├── Assets.xcassets │ │ │ │ ├── AccentColor.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ ├── BandLocation.imageset │ │ │ │ │ ├── BandLocation.svg │ │ │ │ │ └── Contents.json │ │ │ │ ├── Bass.imageset │ │ │ │ │ ├── Bass.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ ├── CreateEvent.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── makeEvent.svg │ │ │ │ ├── CurrentLocation.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── CurrentLocation.svg │ │ │ │ ├── Drum.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Drum.png │ │ │ │ ├── Etc.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Etc.png │ │ │ │ ├── GatheringLocation.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── GatheringLocation.svg │ │ │ │ ├── GatheringLocatonIcon.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Group 284.png │ │ │ │ ├── Guitar.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Guitar.png │ │ │ │ ├── Keyboard.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Keyboard.png │ │ │ │ ├── MyEvent.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── MyEvent.svg │ │ │ │ ├── MyPage.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── MyPage.svg │ │ │ │ ├── Setting.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Setting.svg │ │ │ │ ├── TimelineDot.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── TimelineDot.svg │ │ │ │ └── Vocal.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Vocal.png │ │ │ ├── Storyboards │ │ │ │ ├── AddGathering.storyboard │ │ │ │ ├── BandInfo.storyboard │ │ │ │ ├── BandTimeline.storyboard │ │ │ │ ├── Base.lproj │ │ │ │ │ ├── Landing.storyboard │ │ │ │ │ └── LaunchScreen.storyboard │ │ │ │ ├── GatheringInfoPage.storyboard │ │ │ │ ├── GatheringList.storyboard │ │ │ │ ├── MainMap.storyboard │ │ │ │ ├── ReportReasonList.storyboard │ │ │ │ └── ReportReasonListCell.xib │ │ │ └── Xibs │ │ │ │ ├── BandTimelineCell.xib │ │ │ │ ├── GatheringListCell.xib │ │ │ │ ├── LocationCandidateCell.xib │ │ │ │ ├── RepertoireTableViewCell.xib │ │ │ │ ├── ReportReasonListCell.xib │ │ │ │ ├── ReportReasonListHeader.xib │ │ │ │ └── SampleView.xib │ │ ├── Supports │ │ │ ├── AppDelegate.swift │ │ │ ├── Info.plist │ │ │ └── SceneDelegate.swift │ │ ├── UIComponent │ │ │ ├── ActionSheet.swift │ │ │ ├── Cell │ │ │ │ └── CommentTableViewCell.swift │ │ │ ├── CommentCreateButton.swift │ │ │ ├── CommentListView.swift │ │ │ ├── CommentWritingPopupViewController.swift │ │ │ ├── EmptyListView.swift │ │ │ ├── SampleView.swift │ │ │ └── SwitchingViewSegmentedControl.swift │ │ └── Utils │ │ │ ├── Date+String.swift │ │ │ └── UIView+Gradation.swift │ ├── GoogleService-Info.plist │ ├── Network │ │ ├── API │ │ │ ├── AuthAPI.swift │ │ │ ├── BandAPI.swift │ │ │ └── GatheringAPI.swift │ │ ├── Mock │ │ │ └── Data │ │ │ │ └── MockBandData.swift │ │ ├── Model │ │ │ ├── AgeGroup.swift │ │ │ ├── Band.swift │ │ │ ├── Coordinate.swift │ │ │ ├── DTO │ │ │ │ ├── GatheringCommentDTO.swift │ │ │ │ ├── GatheringDTO.swift │ │ │ │ └── VisitorCommentDTO.swift │ │ │ ├── Gathering.swift │ │ │ ├── GatheringComment.swift │ │ │ ├── Location.swift │ │ │ ├── PlayPosition.swift │ │ │ └── VisitorComment.swift │ │ └── Storage │ │ │ ├── UserDefaultHandler.swift │ │ │ └── UserDefaultStorage.swift │ └── Screen │ │ ├── AddGathering │ │ ├── Cell │ │ │ └── LocationCandidateCell.swift │ │ └── VC │ │ │ ├── AddGatheringViewController.swift │ │ │ ├── Extension │ │ │ ├── AddGatheringViewController+ScrollAdaptToKeyboard.swift │ │ │ └── LocationSearchResultViewController+ScrollAdaptToKeyboard.swift │ │ │ ├── LocationSearchResultViewController.swift │ │ │ └── LocationSearchViewController.swift │ │ ├── BandInfo │ │ ├── Cell │ │ │ ├── BandMemberCollectionViewCell.swift │ │ │ ├── BandTimelineCell.swift │ │ │ └── RepertoireTableViewCell.swift │ │ ├── TopViewOfInfoView.swift │ │ └── VC │ │ │ ├── BandInfoViewController.swift │ │ │ ├── BandPageViewController.swift │ │ │ ├── BandTimelineViewController.swift │ │ │ └── VisitorCommentViewController.swift │ │ ├── Gathering │ │ ├── GatheringListCell.swift │ │ ├── GatheringListTapViewController.swift │ │ └── GatheringListViewController.swift │ │ ├── GatheringInfo │ │ ├── GatheringInfoViewController.swift │ │ ├── ReportReasonListCell.swift │ │ └── ReportReasonListController.swift │ │ ├── Landing │ │ └── LandingViewController.swift │ │ ├── MainMap │ │ ├── Component │ │ │ ├── BandAnnotationView.swift │ │ │ └── GatheringAnnotationView.swift │ │ └── MainMapViewController.swift │ │ └── MyPage │ │ └── MyPageViewController.swift ├── GetARockTests │ └── GetARockTests.swift └── GetARockUITests │ ├── GetARockUITests.swift │ └── GetARockUITestsLaunchTests.swift └── README.md /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 개발 중 생긴 버그를 작성합니다. 4 | title: "[Bug] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # 🐞 버그 설명 11 | | 문제 발생 일시 | 발생 위치 | 발생 조건 | 관련 이슈 | 12 | |:---:|:---:|:---:|:---:| 13 | | | | | | 14 | 15 | ## 증상 16 | 17 | 18 | ## 스크린샷 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue template 3 | about: 기본 이슈 템플릿 4 | title: "[Feat] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 💡 Issue 11 | 12 | 13 | ## 📝 todo 14 | - [ ] todo ! 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 관련 이슈 2 | 3 | 4 | 5 | ## 배경 6 | 7 | 8 | 9 | ## 작업 내용 10 | 11 | 12 | 13 | ## 테스트 방법 14 | 15 | 16 | 17 | ## 리뷰 노트 18 | 19 | 20 | 21 | ## 스크린샷 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/auto_assign.yml: -------------------------------------------------------------------------------- 1 | # Set to true to add reviewers to pull requests 2 | addReviewers: true 3 | 4 | # Set to true to add assignees to pull requests 5 | addAssignees: false 6 | 7 | # A list of reviewers to be added to pull requests (GitHub user name) 8 | reviewers: 9 | - compuTasha 10 | - EuniceNam 11 | - GODNOEL 12 | - manju-minji 13 | - taehyeonk 14 | - siro96-01 15 | - skycat0212 16 | - Somin-DS 17 | - unuhqueen 18 | 19 | # A list of keywords to be skipped the process that add reviewers if pull requests include it 20 | skipKeywords: 21 | - wip 22 | 23 | # A number of reviewers added to the pull request 24 | # Set 0 to add all the reviewers (default: 0) 25 | numberOfReviewers: 0 -------------------------------------------------------------------------------- /.github/workflows/builder.yml: -------------------------------------------------------------------------------- 1 | name: GetARock Swift-Builder 2 | 3 | on: 4 | push: 5 | branches: [ develop, main ] 6 | pull_request: 7 | branches: [ develop, release, hotfix, feature ] 8 | 9 | jobs: 10 | build: 11 | name: Build and Test by Swift 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - name: 🟢 Checkout Source Code 🟢 16 | uses: actions/checkout@v1 17 | 18 | - name: 🔵 Build Test Step 🔵 19 | run: | 20 | xcodebuild test -project ./GetARock/GetARock.xcodeproj -scheme GetARockTests -destination "platform=iOS Simulator,name=iPhone 13 Pro,OS=latest" 21 | 22 | - name: Add PR Comment 23 | if: ${{ success() }} 24 | uses: mshick/add-pr-comment@v1 25 | env: 26 | DIFF_BASE: ${{ github.base_ref }} 27 | with: 28 | message: | 29 | ## All File Checked ✅ 30 | | ✅ | SwiftBuilder Success!!!! | 31 | |--- |---------------------------------------------- | 32 | repo-token: ${{ secrets.ACCESS_TOKEN }} 33 | repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens 34 | allow-repeats: false # This is the default 35 | 36 | -------------------------------------------------------------------------------- /GetARock/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace 3 | - function_body_length 4 | - line_length 5 | - orphaned_doc_comment 6 | - nesting 7 | 8 | opt_in_rules: 9 | - let_var_whitespace 10 | 11 | file_length: 12 | warning: 400 13 | error: 500 14 | 15 | type_name: 16 | min_length: 2 17 | 18 | included: 19 | - GetARock 20 | excluded: 21 | - GetARock/Global/Supports/AppDelegate.swift 22 | - GetARock/Global/Supports/SceneDelegate.swift 23 | - GetARock/Network/Mock/Data/* 24 | 25 | identifier_name: 26 | excluded: 27 | - id 28 | -------------------------------------------------------------------------------- /GetARock/GetARock.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GetARock/GetARock.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GetARock/GetARock.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "abseil-cpp-swiftpm", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", 7 | "state" : { 8 | "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", 9 | "version" : "0.20220203.2" 10 | } 11 | }, 12 | { 13 | "identity" : "boringssl-swiftpm", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/firebase/boringssl-SwiftPM.git", 16 | "state" : { 17 | "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", 18 | "version" : "0.9.1" 19 | } 20 | }, 21 | { 22 | "identity" : "firebase-ios-sdk", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/firebase/firebase-ios-sdk", 25 | "state" : { 26 | "branch" : "master", 27 | "revision" : "dce2e1abc6c0d5e830ff1cffe3f8633fda64001e" 28 | } 29 | }, 30 | { 31 | "identity" : "googleappmeasurement", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/google/GoogleAppMeasurement.git", 34 | "state" : { 35 | "revision" : "71eb6700dd53a851473c48d392f00a3ab26699a6", 36 | "version" : "10.1.0" 37 | } 38 | }, 39 | { 40 | "identity" : "googledatatransport", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/google/GoogleDataTransport.git", 43 | "state" : { 44 | "revision" : "5056b15c5acbb90cd214fe4d6138bdf5a740e5a8", 45 | "version" : "9.2.0" 46 | } 47 | }, 48 | { 49 | "identity" : "googleutilities", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/google/GoogleUtilities.git", 52 | "state" : { 53 | "revision" : "6db6edb48bdd9943426562c7f042a5492de5ba3d", 54 | "version" : "7.10.0" 55 | } 56 | }, 57 | { 58 | "identity" : "grpc-ios", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/grpc/grpc-ios.git", 61 | "state" : { 62 | "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", 63 | "version" : "1.44.3-grpc" 64 | } 65 | }, 66 | { 67 | "identity" : "gtm-session-fetcher", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/google/gtm-session-fetcher.git", 70 | "state" : { 71 | "revision" : "efda500b6d9858d38a76dbfbfa396bd644692e4a", 72 | "version" : "3.0.0" 73 | } 74 | }, 75 | { 76 | "identity" : "leveldb", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/firebase/leveldb.git", 79 | "state" : { 80 | "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", 81 | "version" : "1.22.2" 82 | } 83 | }, 84 | { 85 | "identity" : "nanopb", 86 | "kind" : "remoteSourceControl", 87 | "location" : "https://github.com/firebase/nanopb.git", 88 | "state" : { 89 | "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", 90 | "version" : "2.30909.0" 91 | } 92 | }, 93 | { 94 | "identity" : "promises", 95 | "kind" : "remoteSourceControl", 96 | "location" : "https://github.com/google/promises.git", 97 | "state" : { 98 | "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", 99 | "version" : "2.1.1" 100 | } 101 | }, 102 | { 103 | "identity" : "swift-protobuf", 104 | "kind" : "remoteSourceControl", 105 | "location" : "https://github.com/apple/swift-protobuf.git", 106 | "state" : { 107 | "revision" : "ab3a58b7209a17d781c0d1dbb3e1ff3da306bae8", 108 | "version" : "1.20.3" 109 | } 110 | } 111 | ], 112 | "version" : 2 113 | } 114 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Base/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/01. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | /* 19 | // MARK: - Navigation 20 | 21 | // In a storyboard-based application, you will often want to do a little preparation before navigation 22 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 23 | // Get the new view controller using segue.destination. 24 | // Pass the selected object to the new view controller. 25 | } 26 | */ 27 | 28 | } 29 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Extension/CollectionReference+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionReference+Extension.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/04. 6 | // 7 | 8 | import FirebaseFirestore 9 | 10 | extension CollectionReference { 11 | 12 | func addDocument(from value: T, 13 | encoder: Firestore.Encoder = Firestore.Encoder()) async throws 14 | -> DocumentReference { 15 | let encoded = try encoder.encode(value) 16 | 17 | typealias DocumentReferenceContinuation = CheckedContinuation 18 | return try await withCheckedThrowingContinuation({ (continuation: DocumentReferenceContinuation) in 19 | let result = addDocument(data: encoded) { error in 20 | if let error = error { 21 | continuation.resume(throwing: error) 22 | return 23 | } 24 | } 25 | continuation.resume(returning: result) 26 | }) 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Extension/Encodable+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Encodable+Extension.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/04. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Encodable { 11 | func encode() throws -> [String: Any] { 12 | let data = try JSONEncoder().encode(self) 13 | guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else { 14 | throw NSError() 15 | } 16 | return dictionary 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Extension/NSObject+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Extension.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/01. 6 | // 7 | 8 | import Foundation 9 | 10 | extension NSObject { 11 | static var className: String { 12 | return String(describing: self) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Extension/UIColor+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Extension.swift 3 | // GetARock 4 | // 5 | // Created by Yu ahyeon on 2022/11/02. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIColor { 11 | 12 | static var mainPurple: UIColor { 13 | return #colorLiteral(red: 0.6862745098, green: 0.3019607843, blue: 0.9254901961, alpha: 1) 14 | } 15 | 16 | static var lightPurple: UIColor { 17 | return #colorLiteral(red: 0.9254901961, green: 0.8039215686, blue: 1, alpha: 1) 18 | } 19 | 20 | static var mainBlue: UIColor { 21 | return #colorLiteral(red: 0.4941176471, green: 0.5254901961, blue: 0.6862745098, alpha: 1) 22 | } 23 | 24 | static var gradationBlue: UIColor { 25 | return #colorLiteral(red: 0.4274509804, green: 0.4941176471, blue: 0.968627451, alpha: 1) 26 | } 27 | 28 | static var subBlue: UIColor { 29 | return #colorLiteral(red: 0.4823529412, green: 0.9019607843, blue: 0.9333333333, alpha: 1) 30 | } 31 | 32 | static var lightBlue: UIColor { 33 | return #colorLiteral(red: 0.6470588235, green: 0.7019607843, blue: 1, alpha: 1) 34 | } 35 | 36 | static var dividerBlue: UIColor { 37 | return #colorLiteral(red: 0.5294117647, green: 0.5568627451, blue: 0.6823529412, alpha: 1) 38 | } 39 | 40 | static var backgroundBlue: UIColor { 41 | return #colorLiteral(red: 0.4039215686, green: 0.4274509804, blue: 0.5411764706, alpha: 1) 42 | } 43 | 44 | static var modalBackgroundBlue: UIColor { 45 | return #colorLiteral(red: 0.3490196078, green: 0.368627451, blue: 0.4705882353, alpha: 1) 46 | } 47 | 48 | static var lightGrey: UIColor { 49 | return #colorLiteral(red: 0.7843137255, green: 0.7843137255, blue: 0.7843137255, alpha: 1) 50 | } 51 | 52 | static var backgroundWhite: UIColor { 53 | return #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.2) 54 | } 55 | 56 | static var warningRed: UIColor { 57 | return #colorLiteral(red: 0.7921568627, green: 0, blue: 0, alpha: 1) 58 | } 59 | } 60 | 61 | extension UIColor { 62 | 63 | convenience init(red: Int, green: Int, blue: Int, alpha: Int = 1) { 64 | self.init( 65 | red: CGFloat(red) / 255.0, 66 | green: CGFloat(green) / 255.0, 67 | blue: CGFloat(blue) / 255.0, 68 | alpha: CGFloat(alpha) / 255.0 69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Extension/UIView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Extension.swift 3 | // GetARock 4 | // 5 | // Created by Seungwon Choi on 2022/12/01. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIView { 11 | func applyBandInfoBoxDesign(cornerRadius: CGFloat) { 12 | self.backgroundColor = UIColor.clear 13 | self.layer.cornerRadius = cornerRadius 14 | self.layer.borderWidth = 1 15 | self.layer.borderColor = UIColor.dividerBlue.cgColor 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Literals/ImageLiteral.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLiteral.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/01. 6 | // 7 | 8 | import UIKit 9 | 10 | enum ImageLiteral { 11 | 12 | // MARK: - image 13 | static var exampleIcon: UIImage { .load(systemName: "pencil") } 14 | } 15 | 16 | extension UIImage { 17 | static func load(name: String) -> UIImage { 18 | guard let image = UIImage(named: name, in: nil, compatibleWith: nil) else { 19 | return UIImage() 20 | } 21 | image.accessibilityIdentifier = name 22 | return image 23 | } 24 | 25 | static func load(systemName: String) -> UIImage { 26 | guard let image = UIImage(systemName: systemName, compatibleWith: nil) else { 27 | return UIImage() 28 | } 29 | image.accessibilityIdentifier = systemName 30 | return image 31 | } 32 | 33 | func resize(to size: CGSize) -> UIImage { 34 | let image = UIGraphicsImageRenderer(size: size).image { _ in 35 | draw(in: CGRect(origin: .zero, size: size)) 36 | } 37 | return image 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Literals/StringLiteral.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringLiteral.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/01. 6 | // 7 | 8 | import Foundation 9 | 10 | enum StringLiteral { 11 | 12 | static let exampleString = "안녕하세요" 13 | 14 | } 15 | 16 | enum DateFormatLiteral { 17 | static let standard = "yyyy.MM.dd hh:mm" 18 | } 19 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/BandLocation.imageset/BandLocation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/BandLocation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BandLocation.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Bass.imageset/Bass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/MacC-GetARock/e80a6be0cfd73aba7a04d63c8c4deea30dc814ee/GetARock/GetARock/Global/Resource/Assets.xcassets/Bass.imageset/Bass.png -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Bass.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Bass.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/CreateEvent.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "makeEvent.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/CreateEvent.imageset/makeEvent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/CurrentLocation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "CurrentLocation.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/CurrentLocation.imageset/CurrentLocation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Drum.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Drum.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Drum.imageset/Drum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/MacC-GetARock/e80a6be0cfd73aba7a04d63c8c4deea30dc814ee/GetARock/GetARock/Global/Resource/Assets.xcassets/Drum.imageset/Drum.png -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Etc.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Etc.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Etc.imageset/Etc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/MacC-GetARock/e80a6be0cfd73aba7a04d63c8c4deea30dc814ee/GetARock/GetARock/Global/Resource/Assets.xcassets/Etc.imageset/Etc.png -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/GatheringLocation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "GatheringLocation.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/GatheringLocatonIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "Group 284.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/GatheringLocatonIcon.imageset/Group 284.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/MacC-GetARock/e80a6be0cfd73aba7a04d63c8c4deea30dc814ee/GetARock/GetARock/Global/Resource/Assets.xcassets/GatheringLocatonIcon.imageset/Group 284.png -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Guitar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Guitar.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Guitar.imageset/Guitar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/MacC-GetARock/e80a6be0cfd73aba7a04d63c8c4deea30dc814ee/GetARock/GetARock/Global/Resource/Assets.xcassets/Guitar.imageset/Guitar.png -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Keyboard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Keyboard.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Keyboard.imageset/Keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/MacC-GetARock/e80a6be0cfd73aba7a04d63c8c4deea30dc814ee/GetARock/GetARock/Global/Resource/Assets.xcassets/Keyboard.imageset/Keyboard.png -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/MyEvent.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "MyEvent.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/MyEvent.imageset/MyEvent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/MyPage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "MyPage.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/MyPage.imageset/MyPage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Setting.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Setting.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "original" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Setting.imageset/Setting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/TimelineDot.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "TimelineDot.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/TimelineDot.imageset/TimelineDot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Vocal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Vocal.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Assets.xcassets/Vocal.imageset/Vocal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeveloperAcademy-POSTECH/MacC-GetARock/e80a6be0cfd73aba7a04d63c8c4deea30dc814ee/GetARock/GetARock/Global/Resource/Assets.xcassets/Vocal.imageset/Vocal.png -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Storyboards/BandTimeline.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Storyboards/Base.lproj/Landing.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Storyboards/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Storyboards/GatheringList.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Storyboards/ReportReasonList.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Storyboards/ReportReasonListCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Xibs/LocationCandidateCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Xibs/RepertoireTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Xibs/ReportReasonListCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Xibs/ReportReasonListHeader.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Resource/Xibs/SampleView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Supports/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/10/31. 6 | // 7 | 8 | import UIKit 9 | 10 | import FirebaseCore 11 | 12 | @main 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | FirebaseApp.configure() 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Supports/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Landing 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Supports/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/10/31. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/UIComponent/ActionSheet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionSheet.swift 3 | // GetARock 4 | // 5 | // Created by 진영재 on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol Reportable: UIViewController, AlertSheet { 11 | 12 | } 13 | extension Reportable { 14 | func showActionSheet() { 15 | let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 16 | 17 | let cancel = UIAlertAction(title: "취소", style: .cancel) 18 | let delete = UIAlertAction(title: "삭제하기", style: .destructive) { _ in 19 | self.showAlertSheet(alertTitle: "삭제하기", message: "삭제하시겠습니까?") 20 | } 21 | let report = UIAlertAction(title: "신고하기", style: .default) { _ in 22 | self.present(ReportReasonListController(), animated: true) 23 | 24 | } 25 | 26 | actionSheet.addAction(report) 27 | actionSheet.addAction(delete) 28 | actionSheet.addAction(cancel) 29 | 30 | present(actionSheet, animated: true) 31 | 32 | } 33 | } 34 | 35 | protocol AlertSheet: UIViewController { 36 | func alertActionButtonPressed() 37 | } 38 | 39 | extension AlertSheet { 40 | func showAlertSheet(alertTitle: String, message: String) { 41 | let alertSheet = UIAlertController(title: alertTitle, message: message, preferredStyle: .alert) 42 | 43 | let cancel = UIAlertAction(title: "취소", style: .default) 44 | let action = UIAlertAction(title: alertTitle, style: .destructive) { _ in 45 | self.alertActionButtonPressed() 46 | } 47 | 48 | alertSheet.addAction(action) 49 | alertSheet.addAction(cancel) 50 | 51 | present(alertSheet, animated: true) 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/UIComponent/Cell/CommentTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GusetBookTableViewCell.swift 3 | // GetARock 4 | // 5 | // Created by Yu ahyeon on 2022/11/19. 6 | // 7 | 8 | import UIKit 9 | 10 | class CommentTableViewCell: UITableViewCell { 11 | 12 | // MARK: - Properties 13 | 14 | weak var delegate: NotifyTapMoreButtonDelegate? 15 | 16 | // MARK: - View 17 | 18 | let bandNameLabel: UILabel = { 19 | $0.textColor = .white 20 | $0.font = UIFont.systemFont(ofSize: 16, weight: .bold) 21 | return $0 22 | }(UILabel()) 23 | 24 | private let moreButton: UIButton = { 25 | $0.setImage(UIImage(systemName: "ellipsis"), for: .normal) 26 | $0.tintColor = .white 27 | return $0 28 | }(UIButton()) 29 | 30 | let commentTextLabel: UILabel = { 31 | $0.numberOfLines = 0 32 | $0.textColor = .white 33 | $0.font = UIFont.systemFont(ofSize: 14) 34 | return $0 35 | }(UILabel()) 36 | 37 | let commentDateLabel: UILabel = { 38 | $0.textColor = .lightGrey 39 | $0.font = UIFont.systemFont(ofSize: 12) 40 | return $0 41 | }(UILabel()) 42 | 43 | private lazy var commentInfoStackView: UIStackView = { 44 | $0.axis = .horizontal 45 | $0.distribution = .equalSpacing 46 | return $0 47 | }(UIStackView(arrangedSubviews: [bandNameLabel, moreButton])) 48 | 49 | private lazy var commentStackView: UIStackView = { 50 | $0.axis = .vertical 51 | $0.spacing = 10 52 | $0.translatesAutoresizingMaskIntoConstraints = false 53 | return $0 54 | }(UIStackView(arrangedSubviews: [commentInfoStackView, commentTextLabel, commentDateLabel])) 55 | 56 | // MARK: - Init 57 | 58 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 59 | super.init(style: style, reuseIdentifier: reuseIdentifier) 60 | setupLayout() 61 | setupMoreButton() 62 | } 63 | 64 | required init?(coder aDecoder: NSCoder) { 65 | fatalError("init(coder:) has not been implemented") 66 | } 67 | 68 | // MARK: - Method 69 | 70 | private func setupLayout() { 71 | self.contentView.addSubview(commentStackView) 72 | NSLayoutConstraint.activate([ 73 | commentStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 30), 74 | commentStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), 75 | commentStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20), 76 | commentStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -30) 77 | ]) 78 | } 79 | 80 | private func setupMoreButton() { 81 | moreButton.addTarget(self, action: #selector(showActionSheet), for: .touchUpInside) 82 | } 83 | 84 | @objc func showActionSheet() { 85 | self.delegate?.notifyTapMoreButton(cell: self) 86 | } 87 | } 88 | 89 | // MARK: - NotifyTapMoreButtonDelegate 90 | 91 | protocol NotifyTapMoreButtonDelegate: AnyObject { 92 | func notifyTapMoreButton(cell: UITableViewCell) 93 | } 94 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/UIComponent/CommentCreateButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommentCreateButton.swift 3 | // GetARock 4 | // 5 | // Created by Yu ahyeon on 2022/11/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class CommentCreateButton: UIView { 11 | 12 | // MARK: - Property 13 | 14 | enum Size { 15 | static let spacing: CGFloat = 16.0 16 | static let height: CGFloat = 50.0 17 | static let width: CGFloat = UIScreen.main.bounds.size.width - Size.spacing * 2 18 | } 19 | 20 | // MARK: - View 21 | 22 | let titleButton: UIButton = { 23 | $0.translatesAutoresizingMaskIntoConstraints = false 24 | return $0 25 | }(UIButton()) 26 | 27 | // MARK: - Init 28 | 29 | override init(frame: CGRect) { 30 | super.init(frame: frame) 31 | attribute() 32 | setupLayout() 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | super.init(coder: aDecoder) 37 | } 38 | 39 | // MARK: - Method 40 | 41 | private func attribute() { 42 | self.backgroundColor = .mainPurple 43 | self.layer.masksToBounds = true 44 | self.layer.cornerRadius = 10 45 | guard let superView = superview else { return } 46 | NSLayoutConstraint.activate([ 47 | superView.centerXAnchor.constraint(equalTo: centerXAnchor), 48 | superView.centerYAnchor.constraint(equalTo: centerYAnchor) 49 | ]) 50 | } 51 | 52 | private func setupLayout() { 53 | self.addSubview(titleButton) 54 | titleButton.invalidateIntrinsicContentSize() 55 | NSLayoutConstraint.activate([ 56 | titleButton.topAnchor.constraint(equalTo: self.topAnchor), 57 | titleButton.leadingAnchor.constraint(equalTo: self.leadingAnchor), 58 | titleButton.trailingAnchor.constraint(equalTo: self.trailingAnchor), 59 | titleButton.bottomAnchor.constraint(equalTo: self.bottomAnchor), 60 | titleButton.heightAnchor.constraint(equalToConstant: Size.height) 61 | ]) 62 | } 63 | 64 | func setupButtonTitle(title: String) { 65 | titleButton.setTitle(title, for: .normal) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/UIComponent/CommentListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GuestBookUIView.swift 3 | // GetARock 4 | // 5 | // Created by Yu ahyeon on 2022/11/19. 6 | // 7 | 8 | import UIKit 9 | 10 | enum CommentMode { 11 | case visitorComment 12 | case gatheringComment 13 | } 14 | 15 | class CommentListView: UIView, NotifyTapMoreButtonDelegate { 16 | 17 | // MARK: - Properties 18 | 19 | private var commentMode: CommentMode 20 | weak var delegate: CheckCellIndexDelegate? 21 | 22 | // MARK: - View 23 | 24 | private let totalComentNumber: UILabel = { 25 | $0.textColor = .white 26 | $0.font = UIFont.systemFont(ofSize: 16, weight: .bold) 27 | return $0 28 | }(UILabel()) 29 | 30 | let commentWritingButton: CommentCreateButton = { 31 | return $0 32 | }(CommentCreateButton()) 33 | 34 | let tableView = { 35 | $0.showsVerticalScrollIndicator = false 36 | $0.separatorInset.right = 16 37 | $0.separatorColor = .dividerBlue 38 | $0.rowHeight = UITableView.automaticDimension 39 | $0.estimatedRowHeight = UITableView.automaticDimension 40 | return $0 41 | }(UITableView()) 42 | 43 | private lazy var commentStackView: UIStackView = { 44 | $0.spacing = 20 45 | $0.axis = .vertical 46 | $0.translatesAutoresizingMaskIntoConstraints = false 47 | return $0 48 | }(UIStackView(arrangedSubviews: [commentWritingButton, totalComentNumber, tableView])) 49 | 50 | // MARK: - Init 51 | 52 | init(commentMode: CommentMode) { 53 | self.commentMode = commentMode 54 | super.init(frame: .zero) 55 | attribute() 56 | setupLayout() 57 | } 58 | 59 | required init?(coder: NSCoder) { 60 | fatalError("init(coder:) has not been implemented") 61 | } 62 | 63 | // MARK: - Method 64 | 65 | private func attribute() { 66 | self.backgroundColor = .modalBackgroundBlue 67 | setupTotalListNumberLabel() 68 | setupCommentList() 69 | setupCommentWritingButton() 70 | } 71 | 72 | private func setupCommentList() { 73 | tableView.delegate = self 74 | tableView.dataSource = self 75 | tableView.register( 76 | CommentTableViewCell.self, 77 | forCellReuseIdentifier: CommentTableViewCell.className 78 | ) 79 | } 80 | 81 | private func setupLayout() { 82 | self.addSubview(commentStackView) 83 | NSLayoutConstraint.activate([ 84 | commentStackView.topAnchor.constraint(equalTo: self.topAnchor), 85 | commentStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), 86 | commentStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor), 87 | commentStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor) 88 | ]) 89 | tableView.backgroundColor = .modalBackgroundBlue 90 | 91 | } 92 | 93 | func setupTotalListNumberLabel() { 94 | switch commentMode { 95 | case .visitorComment: 96 | totalComentNumber.text = "총 \(MockData.visitorComments.count)개" 97 | case .gatheringComment: 98 | totalComentNumber.text = "총 \(MockData.gatheringComments.count)개" 99 | } 100 | } 101 | 102 | private func setupCommentWritingButton() { 103 | switch commentMode { 104 | case .visitorComment: 105 | commentWritingButton.setupButtonTitle(title: "방명록 작성") 106 | case .gatheringComment: 107 | commentWritingButton.setupButtonTitle(title: "댓글 작성") 108 | } 109 | } 110 | 111 | func notifyTapMoreButton(cell: UITableViewCell) { 112 | guard let indexPath = tableView.indexPath(for: cell) else { return } 113 | self.delegate?.checkCellIndex(indexPath: indexPath) 114 | } 115 | } 116 | 117 | // MARK: - UITableViewDelegate 118 | 119 | extension CommentListView: UITableViewDelegate { 120 | func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 121 | cell.backgroundColor = UIColor.clear 122 | } 123 | } 124 | 125 | // MARK: - UITableViewDataSource 126 | 127 | extension CommentListView: UITableViewDataSource { 128 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 129 | 130 | tableView.indexPath(for: UITableViewCell()) 131 | 132 | switch commentMode { 133 | case .visitorComment : 134 | return MockData.visitorComments.count 135 | case .gatheringComment: 136 | return MockData.gatheringComments.count 137 | } 138 | } 139 | 140 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 141 | guard let cell = tableView.dequeueReusableCell( 142 | withIdentifier: CommentTableViewCell.className, 143 | for: indexPath 144 | ) as? CommentTableViewCell 145 | else { 146 | return UITableViewCell() 147 | } 148 | cell.delegate = self 149 | cell.selectionStyle = .none 150 | 151 | switch commentMode { 152 | case .visitorComment: 153 | cell.bandNameLabel.text = MockData.visitorComments[indexPath.row].comment.author.band.name 154 | cell.commentTextLabel.text = MockData.visitorComments[indexPath.row].comment.content 155 | cell.commentDateLabel.text = MockData.visitorComments[indexPath.row].comment.createdAt.toString(format: DateFormatLiteral.standard) 156 | case .gatheringComment : 157 | cell.bandNameLabel.text = MockData.gatheringComments[indexPath.row].comment.author.band.name 158 | cell.commentTextLabel.text = MockData.gatheringComments[indexPath.row].comment.content 159 | cell.commentDateLabel.text = MockData.gatheringComments[indexPath.row].comment.createdAt.toString(format: DateFormatLiteral.standard) 160 | } 161 | return cell 162 | } 163 | } 164 | 165 | // MARK: - CheckCellIndexDelegate 166 | 167 | protocol CheckCellIndexDelegate: AnyObject { 168 | func checkCellIndex(indexPath: IndexPath) 169 | } 170 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/UIComponent/CommentWritingPopupViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestViewController.swift 3 | // GetARock 4 | // 5 | // Created by Yu ahyeon on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol CommentListUpdateDelegate: AnyObject { 11 | func refreshCommentList() 12 | } 13 | 14 | class CommentWritingPopupViewController: UIViewController { 15 | 16 | // MARK: - Properties 17 | 18 | private var commentMode: CommentMode 19 | private let textViewPlaceHolder = "텍스트를 입력해주세요" 20 | 21 | weak var delegate: CommentListUpdateDelegate? 22 | 23 | // MARK: - View 24 | 25 | private let popupTitleLabel: UILabel = { 26 | $0.text = "방명록 작성" 27 | $0.font = .systemFont(ofSize: 16, weight: .bold) 28 | $0.textColor = .white 29 | return $0 30 | }(UILabel()) 31 | 32 | private let closeButton: UIButton = { 33 | $0.setImage(UIImage(systemName: "xmark"), for: .normal) 34 | $0.tintColor = .white 35 | return $0 36 | }(UIButton()) 37 | 38 | private lazy var commentTextView: UITextView = { 39 | $0.textContainerInset = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0) 40 | $0.backgroundColor = .backgroundBlue 41 | $0.font = UIFont.systemFont(ofSize: 14.0) 42 | $0.textAlignment = NSTextAlignment.left 43 | $0.dataDetectorTypes = UIDataDetectorTypes.all 44 | $0.text = textViewPlaceHolder 45 | $0.textColor = .lightGray 46 | $0.delegate = self 47 | return $0 48 | }(UITextView()) 49 | 50 | private let confirmButton: CommentCreateButton = { 51 | $0.setupButtonTitle(title: "작성 완료") 52 | return $0 53 | }(CommentCreateButton()) 54 | 55 | private lazy var popUpHeaderStackView: UIStackView = { 56 | $0.axis = .horizontal 57 | $0.distribution = .equalSpacing 58 | return $0 59 | }(UIStackView(arrangedSubviews: [popupTitleLabel, closeButton])) 60 | 61 | private lazy var commentWritingPopupStackView: UIStackView = { 62 | $0.layoutMargins = UIEdgeInsets(top: 15.0, left: 15.0, bottom: 15.0, right: 15.0) 63 | $0.spacing = 15 64 | $0.axis = .vertical 65 | $0.isLayoutMarginsRelativeArrangement = true 66 | $0.translatesAutoresizingMaskIntoConstraints = false 67 | return $0 68 | }(UIStackView(arrangedSubviews: [popUpHeaderStackView, commentTextView, confirmButton])) 69 | 70 | // MARK: - Init 71 | 72 | init(commentMode: CommentMode) { 73 | self.commentMode = commentMode 74 | super.init(nibName: nil, bundle: nil) 75 | } 76 | 77 | required init?(coder: NSCoder) { 78 | fatalError("init(coder:) has not been implemented") 79 | } 80 | 81 | // MARK: - Life Cycle 82 | 83 | override func viewDidLoad() { 84 | super.viewDidLoad() 85 | attribute() 86 | setupLayout() 87 | } 88 | 89 | // MARK: - Method 90 | 91 | private func attribute() { 92 | view.backgroundColor = .black.withAlphaComponent(0.5) 93 | let tapBackgroundGesture = UITapGestureRecognizer(target: self, action: #selector(dismissPopup)) 94 | view.addGestureRecognizer(tapBackgroundGesture) 95 | setupPopupTitle() 96 | } 97 | 98 | private func setupLayout() { 99 | view.addSubview(commentWritingPopupStackView) 100 | commentWritingPopupStackView.backgroundColor = .modalBackgroundBlue 101 | NSLayoutConstraint.activate([ 102 | commentWritingPopupStackView.centerXAnchor.constraint(equalTo: view.centerXAnchor), 103 | commentWritingPopupStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor), 104 | commentWritingPopupStackView.widthAnchor.constraint(equalToConstant: CommentCreateButton.Size.width), 105 | commentWritingPopupStackView.heightAnchor.constraint(equalToConstant: 300) 106 | ]) 107 | setupButtons() 108 | } 109 | 110 | private func setupPopupTitle() { 111 | switch commentMode { 112 | case .visitorComment: 113 | popupTitleLabel.text = "방명록 작성" 114 | case .gatheringComment: 115 | popupTitleLabel.text = "댓글 작성" 116 | } 117 | } 118 | 119 | private func setupButtons() { 120 | closeButton.addTarget(self, action: #selector(dismissPopup), for: .touchUpInside) 121 | confirmButton.titleButton.addTarget(self, action: #selector(self.addNewComment(_:)), for: .touchUpInside) 122 | // confirmButton.addButtonAction(selector: #selector(addNewComment(_:))) 123 | } 124 | 125 | @objc private func dismissPopup(_ sender: Any) { 126 | dismiss(animated: false, completion: nil) 127 | } 128 | 129 | @objc private func addNewComment(_ sender: Any) { 130 | if commentTextView.text != textViewPlaceHolder { 131 | switch commentMode { 132 | case .visitorComment: 133 | if let text = commentTextView.text { 134 | let saveData = VisitorCommentInfo( 135 | commentID: "visitorCommentID-005", 136 | comment: VisitorComment( 137 | hostBand: MockData.bands[0], 138 | author: MockData.bands[2], 139 | content: text, 140 | createdAt: Date() 141 | ) 142 | ) 143 | MockData.visitorComments.append(saveData) 144 | self.delegate?.refreshCommentList() 145 | self.dismiss(animated: false, completion: nil) 146 | } 147 | case .gatheringComment: 148 | if let text = commentTextView.text { 149 | let saveData = GatheringCommentInfo( 150 | commentID: "gatheringID-005", 151 | comment: GatheringComment( 152 | gathering: MockData.gatheringComments[0].comment.gathering, 153 | author: MockData.gatheringComments[0].comment.author, 154 | content: text, 155 | createdAt: Date())) 156 | MockData.gatheringComments.append(saveData) 157 | self.delegate?.refreshCommentList() 158 | self.dismiss(animated: false, completion: nil) 159 | } 160 | } 161 | } 162 | } 163 | } 164 | 165 | // MARK: - UITextViewDelegate 166 | 167 | extension CommentWritingPopupViewController: UITextViewDelegate { 168 | func textViewDidBeginEditing(_ textView: UITextView) { 169 | if textView.text == textViewPlaceHolder { 170 | textView.text = nil 171 | textView.textColor = .white 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/UIComponent/EmptyListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyListView.swift 3 | // GetARock 4 | // 5 | // Created by Yu ahyeon on 2022/11/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class EmptyListView: UIView { 11 | 12 | // MARK: - Property 13 | 14 | private lazy var defaultTextLabel: UILabel = { 15 | $0.textColor = .white 16 | $0.font = UIFont.systemFont(ofSize: 14) 17 | $0.translatesAutoresizingMaskIntoConstraints = false 18 | return $0 19 | }(UILabel()) 20 | 21 | // MARK: - Init 22 | 23 | override init(frame: CGRect) { 24 | super.init(frame: frame) 25 | attribute() 26 | setupLayout() 27 | } 28 | 29 | required init?(coder aDecoder: NSCoder) { 30 | super.init(coder: aDecoder) 31 | attribute() 32 | setupLayout() 33 | } 34 | 35 | // MARK: - Method 36 | 37 | private func attribute() { 38 | self.backgroundColor = .modalBackgroundBlue 39 | } 40 | 41 | private func setupLayout() { 42 | self.addSubview(defaultTextLabel) 43 | NSLayoutConstraint.activate([ 44 | defaultTextLabel.centerXAnchor.constraint(equalTo: centerXAnchor), 45 | defaultTextLabel.centerYAnchor.constraint(equalTo: centerYAnchor) 46 | ]) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/UIComponent/SampleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleView.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/01. 6 | // 7 | 8 | import UIKit 9 | 10 | class SampleView: UIView { 11 | 12 | /* 13 | // Only override draw() if you perform custom drawing. 14 | // An empty implementation adversely affects performance during animation. 15 | override func draw(_ rect: CGRect) { 16 | // Drawing code 17 | } 18 | */ 19 | 20 | } 21 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/UIComponent/SwitchingViewSegmentedControl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchingViewSegmentedControl.swift 3 | // GetARock 4 | // 5 | // Created by Somin Park on 2022/11/20. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol SwitchingViewSegmentedControlDelegate: AnyObject { 11 | func segmentValueChanged(to index: Int) 12 | } 13 | 14 | final class SwitchingViewSegmentedControl: UIView { 15 | 16 | // MARK: - Properties 17 | 18 | weak var delegate: SwitchingViewSegmentedControlDelegate? 19 | private var buttonTitles = [String]() 20 | private var buttons = [UIButton]() 21 | let textColor: UIColor = .white 22 | let selectedColor: UIColor = .mainPurple 23 | 24 | // MARK: - View 25 | 26 | private var selectorView: UIView = { 27 | let view = UIView() 28 | return view 29 | }() 30 | private var staticLineView: UIView = { 31 | let view = UIView() 32 | view.backgroundColor = .dividerBlue 33 | return view 34 | }() 35 | 36 | convenience init(buttonTitles: [String]) { 37 | self.init(frame: .zero) 38 | self.buttonTitles = buttonTitles 39 | } 40 | 41 | override func draw(_ rect: CGRect) { 42 | super.draw(rect) 43 | updateView() 44 | } 45 | 46 | // MARK: - Method 47 | 48 | private func updateView() { 49 | setupButtons() 50 | setupLayout() 51 | } 52 | 53 | private func setupButtons() { 54 | buttons = [UIButton]() 55 | buttons.removeAll() 56 | subviews.forEach({$0.removeFromSuperview()}) 57 | for buttonTitle in buttonTitles { 58 | let button = UIButton(type: .system) 59 | button.setTitle(buttonTitle, for: .normal) 60 | button.setTitleColor(textColor, for: .normal) 61 | button.addTarget(self, action: #selector(SwitchingViewSegmentedControl.buttonAction(sender:)), 62 | for: .touchUpInside) 63 | buttons.append(button) 64 | } 65 | buttons[0].setTitleColor(selectedColor, for: .normal) 66 | } 67 | 68 | @objc func buttonAction(sender: UIButton) { 69 | for (buttonIndex, button) in buttons.enumerated() { 70 | button.setTitleColor(textColor, for: .normal) 71 | if button == sender { 72 | delegate?.segmentValueChanged(to: buttonIndex) 73 | let selectorPosition = frame.width / CGFloat(buttonTitles.count) * CGFloat(buttonIndex) 74 | UIView.animate(withDuration: 0.2) { 75 | self.selectorView.frame.origin.x = selectorPosition 76 | } 77 | button.setTitleColor(selectedColor, for: .normal) 78 | } 79 | } 80 | } 81 | 82 | } 83 | 84 | // MARK: - Layout 85 | 86 | extension SwitchingViewSegmentedControl { 87 | private func setupLayout() { 88 | configureStaticLineView() 89 | configureSelectorView() 90 | configureStackView() 91 | } 92 | 93 | private func configureStackView() { 94 | let stackView = UIStackView(arrangedSubviews: buttons) 95 | stackView.axis = .horizontal 96 | stackView.alignment = .fill 97 | stackView.distribution = .fillEqually 98 | stackView.backgroundColor = .clear 99 | addSubview(stackView) 100 | stackView.translatesAutoresizingMaskIntoConstraints = false 101 | NSLayoutConstraint.activate([ 102 | stackView.topAnchor.constraint(equalTo: self.topAnchor), 103 | stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), 104 | stackView.leftAnchor.constraint(equalTo: self.leftAnchor), 105 | stackView.rightAnchor.constraint(equalTo: self.rightAnchor) 106 | ]) 107 | } 108 | 109 | private func configureSelectorView() { 110 | let selectorWidth = frame.width / CGFloat(self.buttonTitles.count) 111 | 112 | selectorView = UIView(frame: CGRect(x: 0, y: self.frame.height, width: selectorWidth, height: 3)) 113 | selectorView.backgroundColor = selectedColor 114 | addSubview(selectorView) 115 | } 116 | 117 | private func configureStaticLineView() { 118 | staticLineView.translatesAutoresizingMaskIntoConstraints = false 119 | addSubview(staticLineView) 120 | NSLayoutConstraint.activate([ 121 | staticLineView.topAnchor.constraint(equalTo: self.bottomAnchor), 122 | staticLineView.trailingAnchor.constraint(equalTo: self.trailingAnchor), 123 | staticLineView.heightAnchor.constraint(equalToConstant: 1), 124 | staticLineView.leadingAnchor.constraint(equalTo: self.leadingAnchor), 125 | staticLineView.centerXAnchor.constraint(equalTo: self.centerXAnchor) 126 | ]) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Utils/Date+String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+String.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | func toString(format: String) -> String { 12 | let dateFormatter = DateFormatter() 13 | dateFormatter.dateFormat = format 14 | return dateFormatter.string(from: self) 15 | } 16 | } 17 | 18 | extension String { 19 | func toDate(format: String) -> Date? { 20 | let dateFormatter = DateFormatter() 21 | dateFormatter.dateFormat = format 22 | return dateFormatter.date(from: self) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /GetARock/GetARock/Global/Utils/UIView+Gradation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Gradation.swift 3 | // GetARock 4 | // 5 | // Created by Yu ahyeon on 2022/11/02. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIView { 11 | 12 | func setGradient(startColor: UIColor, endColor: UIColor, startPointY: Double, endPointY: Double) { 13 | let gradient = CAGradientLayer() 14 | gradient.colors = [startColor.cgColor, endColor.cgColor] 15 | gradient.startPoint = CGPoint(x: 0.0, y: startPointY) 16 | gradient.endPoint = CGPoint(x: 1.0, y: endPointY) 17 | gradient.locations = [0.0, 1.0] 18 | gradient.frame = self.bounds 19 | self.layer.addSublayer(gradient) 20 | } 21 | 22 | func fillMainGradient() { 23 | setGradient( 24 | startColor: .mainPurple, 25 | endColor: .gradationBlue, 26 | startPointY: 0.5, 27 | endPointY: 0.5 28 | ) 29 | } 30 | 31 | func fillActiveGradation() { 32 | setGradient( 33 | startColor: UIColor.mainPurple.withAlphaComponent(0.3), 34 | endColor: UIColor.black.withAlphaComponent(0.0), 35 | startPointY: 0.0, 36 | endPointY: 1.0 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /GetARock/GetARock/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 703035168270-n79de2lbimrq3titm524cug7d8eedpef.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.703035168270-n79de2lbimrq3titm524cug7d8eedpef 9 | API_KEY 10 | AIzaSyBPaqtuIcelLhikxCij87aPatWyN9fVZeg 11 | GCM_SENDER_ID 12 | 703035168270 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.ExtremeRock.GetARock 17 | PROJECT_ID 18 | get-a-rock 19 | STORAGE_BUCKET 20 | get-a-rock.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:703035168270:ios:8788f93eab639de22a8e5f 33 | 34 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/API/AuthAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthAPI.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/03. 6 | // 7 | 8 | import Foundation 9 | 10 | import FirebaseAuth 11 | 12 | enum AuthError: Error { 13 | case noEmailInfo 14 | } 15 | 16 | struct AuthAPI { 17 | 18 | func signIn(withEmail email: String, password: String) async throws -> User { 19 | let authData = try await Auth.auth().signIn(withEmail: email, password: password) 20 | UserDefaultHandler.setUserEmail(email: email) 21 | UserDefaultHandler.setUserPassword(password: password) 22 | return authData.user 23 | } 24 | 25 | func signOut() throws { 26 | try Auth.auth().signOut() 27 | } 28 | 29 | func getCurrentUser() -> User? { 30 | return Auth.auth().currentUser 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/API/BandAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BandAPI.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/03. 6 | // 7 | 8 | import Foundation 9 | 10 | import FirebaseFirestore 11 | import FirebaseFirestoreSwift 12 | 13 | struct BandAPI { 14 | typealias BandID = String 15 | typealias VisitorCommentID = String 16 | 17 | private let database = FirebaseFirestore.Firestore.firestore() 18 | 19 | func saveBand(band: Band) async throws { 20 | guard let email = AuthAPI().getCurrentUser()?.email else { throw AuthError.noEmailInfo } 21 | let bandData = try band.encode() 22 | let reference = database.collection("band").document(email) 23 | try await reference.setData(bandData) 24 | } 25 | 26 | func getBandInfo(bandID: String) async throws -> BandInfo { 27 | let snapShot = try await database.collection("band").document(bandID).getDocument() 28 | let bandData = try snapShot.data(as: Band.self) 29 | let bandInfo = BandInfo(bandID: bandID, band: bandData) 30 | 31 | return bandInfo 32 | } 33 | 34 | func getAllBandInfos() async throws -> [BandInfo] { 35 | let snapShot = try await database.collection("band").getDocuments() 36 | var bandInfos: [BandInfo] = [] 37 | 38 | for document in snapShot.documents { 39 | let bandID = document.documentID 40 | guard let bandData = try? document.data(as: Band.self) else { continue } 41 | let bandInfo = BandInfo(bandID: bandID, band: bandData) 42 | bandInfos.append(bandInfo) 43 | } 44 | 45 | return bandInfos 46 | } 47 | 48 | func saveComment(comment: VisitorComment) async throws -> VisitorCommentID { 49 | guard let email = AuthAPI().getCurrentUser()?.email else { throw AuthError.noEmailInfo } 50 | let visitorCommentDTO = comment 51 | .toVisitorCommentDTO() 52 | .changeValue( 53 | authorID: email, 54 | createdAt: Timestamp() 55 | ) 56 | let reference = database.collection("visitorComment") 57 | let result = try await reference.addDocument(from: visitorCommentDTO) 58 | return result.documentID 59 | } 60 | 61 | func getComments(of bandID: BandID) async throws -> [VisitorCommentInfo] { 62 | let snapShot = try await database.collection("visitorComment").whereField("hostBandID", isEqualTo: bandID).getDocuments() 63 | var commentInfos: [VisitorCommentInfo] = [] 64 | 65 | for document in snapShot.documents { 66 | let commentID = document.documentID 67 | guard let commentData = try? document.data(as: VisitorCommentDTO.self) else { continue } 68 | async let hostBand = BandAPI().getBandInfo(bandID: commentData.hostBandID) 69 | async let author = BandAPI().getBandInfo(bandID: commentData.authorID) 70 | 71 | guard let hostBand = try? await hostBand else { continue } 72 | guard let author = try? await author else { continue } 73 | 74 | let commentInfo = VisitorCommentInfo( 75 | commentID: commentID, 76 | comment: VisitorComment( 77 | hostBand: hostBand, 78 | author: author, 79 | content: commentData.content, 80 | createdAt: commentData.createdAt.dateValue() 81 | ) 82 | ) 83 | commentInfos.append(commentInfo) 84 | } 85 | 86 | return commentInfos.sorted { 87 | $0.comment.createdAt > $1.comment.createdAt 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/API/GatheringAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GatheringAPI.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/04. 6 | // 7 | 8 | import Foundation 9 | 10 | import FirebaseFirestore 11 | import FirebaseFirestoreSwift 12 | 13 | struct GatheringAPI { 14 | typealias GatheringID = String 15 | typealias GatheringCommentID = String 16 | 17 | private let database = FirebaseFirestore.Firestore.firestore() 18 | 19 | func saveGathering(gathering: Gathering) async throws -> GatheringID { 20 | guard let email = AuthAPI().getCurrentUser()?.email else { throw AuthError.noEmailInfo } 21 | let gatheringDTO = gathering 22 | .toGatheringDTO() 23 | .changeValue( 24 | hostBandID: email, 25 | createdAt: Timestamp() 26 | ) 27 | 28 | let reference = database.collection("gathering") 29 | let result = try await reference.addDocument(from: gatheringDTO) 30 | return result.documentID 31 | } 32 | 33 | func getGatheringInfo(gatheringID: String) async throws -> GatheringInfo { 34 | let snapShot = try await database.collection("gathering").document(gatheringID).getDocument() 35 | var gatheringData = try snapShot.data(as: GatheringDTO.self) 36 | gatheringData = gatheringData.changeValue( 37 | status: gatheringData.status.calculateStatus(date: gatheringData.date.dateValue()) 38 | ) 39 | let gatheringInfo = try await gatheringData.toGathering() 40 | 41 | return GatheringInfo(gatheringID: gatheringID, gathering: gatheringInfo) 42 | } 43 | 44 | func getAllGatheringInfos() async throws -> [GatheringInfo] { 45 | let snapShot = try await database.collection("gathering").getDocuments() 46 | var gatheringInfos: [GatheringInfo] = [] 47 | 48 | for document in snapShot.documents { 49 | let gatheringID = document.documentID 50 | guard var gatheringData = try? document.data(as: GatheringDTO.self) else { continue } 51 | gatheringData = gatheringData.changeValue( 52 | status: gatheringData.status.calculateStatus(date: gatheringData.date.dateValue()) 53 | ) 54 | 55 | guard let gathering = try? await gatheringData.toGathering() else { continue } 56 | let gatheringInfo = GatheringInfo(gatheringID: gatheringID, gathering: gathering) 57 | gatheringInfos.append(gatheringInfo) 58 | } 59 | 60 | return gatheringInfos.sorted { 61 | $0.gathering.date > $1.gathering.date 62 | } 63 | } 64 | 65 | func getAllOwnedGatheringInfos(owner bandID: String) async throws -> [GatheringInfo] { 66 | let snapShot = try await database.collection("gathering").whereField("hostBandID", isEqualTo: bandID).getDocuments() 67 | var gatheringInfos: [GatheringInfo] = [] 68 | 69 | for document in snapShot.documents { 70 | let gatheringID = document.documentID 71 | guard var gatheringData = try? document.data(as: GatheringDTO.self) else { continue } 72 | gatheringData = gatheringData.changeValue( 73 | status: gatheringData.status.calculateStatus(date: gatheringData.date.dateValue()) 74 | ) 75 | 76 | guard let gathering = try? await gatheringData.toGathering() else { continue } 77 | let gatheringInfo = GatheringInfo(gatheringID: gatheringID, gathering: gathering) 78 | gatheringInfos.append(gatheringInfo) 79 | } 80 | 81 | return gatheringInfos.sorted { 82 | $0.gathering.date > $1.gathering.date 83 | } 84 | } 85 | 86 | func getAllJoinedGatheringInfos(participant bandID: String) async throws -> [GatheringInfo] { 87 | 88 | let snapShot = try await database.collection("gatheringComment").whereField("authorID", isEqualTo: bandID).getDocuments() 89 | var gatheringIDs: [String] = [] 90 | var gatheringInfos: [GatheringInfo] = [] 91 | 92 | for document in snapShot.documents { 93 | guard let commentData = try? document.data(as: GatheringCommentDTO.self) else { continue } 94 | let gatheringID = commentData.gatheringID 95 | if gatheringIDs.firstIndex(of: gatheringID) != nil { 96 | continue 97 | } 98 | gatheringIDs.append(gatheringID) 99 | } 100 | for gatheringID in gatheringIDs { 101 | guard let gathering = try? await GatheringAPI().getGatheringInfo(gatheringID: gatheringID) else { continue } 102 | gatheringInfos.append(gathering) 103 | } 104 | 105 | return gatheringInfos.sorted { 106 | $0.gathering.date > $1.gathering.date 107 | } 108 | } 109 | 110 | func saveComment(comment: GatheringComment) async throws -> GatheringCommentID { 111 | guard let email = AuthAPI().getCurrentUser()?.email else { throw AuthError.noEmailInfo } 112 | let gatheringCommentDTO = comment 113 | .toGatheringCommentDTO() 114 | .changeValue( 115 | authorID: email, 116 | createdAt: Timestamp() 117 | ) 118 | let reference = database.collection("gatheringComment") 119 | let result = try await reference.addDocument(from: gatheringCommentDTO) 120 | return result.documentID 121 | } 122 | 123 | func getComments(of gatheringID: GatheringID) async throws -> [GatheringCommentInfo] { 124 | let snapShot = try await database.collection("gatheringComment").whereField("gatheringID", isEqualTo: gatheringID).getDocuments() 125 | var commentInfos: [GatheringCommentInfo] = [] 126 | 127 | for document in snapShot.documents { 128 | let commentID = document.documentID 129 | guard let commentData = try? document.data(as: GatheringCommentDTO.self) else { continue } 130 | async let gathering = getGatheringInfo(gatheringID: commentData.gatheringID) 131 | async let author = BandAPI().getBandInfo(bandID: commentData.authorID) 132 | 133 | guard let gathering = try? await gathering else { continue } 134 | guard let author = try? await author else { continue } 135 | 136 | let commentInfo = GatheringCommentInfo( 137 | commentID: commentID, 138 | comment: GatheringComment( 139 | gathering: gathering, 140 | author: author, 141 | content: commentData.content, 142 | createdAt: commentData.createdAt.dateValue() 143 | ) 144 | ) 145 | commentInfos.append(commentInfo) 146 | } 147 | 148 | return commentInfos 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/AgeGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AgeGroup.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/18. 6 | // 7 | 8 | import Foundation 9 | 10 | enum AgeGroup: Codable { 11 | case lessThanTwenty 12 | case twenties 13 | case thirties 14 | case forties 15 | case fifties 16 | case overThanSixty 17 | 18 | func toKorean() -> String { 19 | switch self { 20 | case .lessThanTwenty: return "20대 미만" 21 | case .twenties: return "20대" 22 | case .thirties: return "30대" 23 | case .forties: return "40대" 24 | case .fifties: return "50대" 25 | case .overThanSixty: return "60대 이상" 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/Band.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Band.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/17. 6 | // 7 | 8 | import Foundation 9 | 10 | struct BandInfo { 11 | let bandID: String 12 | var band: Band 13 | } 14 | 15 | struct Band: Codable { 16 | /// 포지션과 해당 포지션의 인원을 나타냅니다. 17 | struct PositionSet: Codable { 18 | var position: PlayPosition 19 | var numberOfPerson: Int 20 | } 21 | 22 | /// 밴드의 이름입니다. 23 | var name: String 24 | /// 밴드가 갖고있는 포지션입니다. 25 | var filledPosition: [PositionSet] 26 | /// 밴드의 합주곡입니다. 27 | var repertoire: [String] 28 | /// 밴드의 연령대입니다. 29 | var ageGroups: [AgeGroup] 30 | /// 밴드의 합주실 위치입니다. 31 | var location: Location 32 | /// 밴드의 자유소개 글입니다. 33 | var introduction: String 34 | } 35 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/Coordinate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinate.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/18. 6 | // 7 | 8 | import MapKit 9 | 10 | struct Coordinate: Codable { 11 | typealias LocationDegrees = Double 12 | 13 | var latitude: LocationDegrees 14 | var longitude: LocationDegrees 15 | } 16 | 17 | extension Coordinate { 18 | 19 | func toCLLocationCoordinate2D() -> CLLocationCoordinate2D { 20 | return CLLocationCoordinate2D(latitude: self.latitude, longitude: self.longitude) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/DTO/GatheringCommentDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GatheringCommentDTO.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/04. 6 | // 7 | 8 | import Foundation 9 | 10 | import Firebase 11 | 12 | struct GatheringCommentDTO: Codable { 13 | let gatheringID: String 14 | let authorID: String 15 | let content: String 16 | let createdAt: Timestamp 17 | } 18 | 19 | extension GatheringCommentDTO { 20 | 21 | func toGatheringComment() async throws -> GatheringComment { 22 | let gatheringAPI = GatheringAPI() 23 | let bandAPI = BandAPI() 24 | async let gathering = gatheringAPI.getGatheringInfo(gatheringID: self.gatheringID) 25 | async let author = bandAPI.getBandInfo(bandID: self.authorID) 26 | 27 | return GatheringComment( 28 | gathering: try await gathering, 29 | author: try await author, 30 | content: self.content, 31 | createdAt: self.createdAt.dateValue() 32 | ) 33 | 34 | } 35 | 36 | func changeValue( 37 | gatheringID: String? = nil, 38 | authorID: String? = nil, 39 | content: String? = nil, 40 | createdAt: Timestamp? = nil 41 | ) -> Self { 42 | GatheringCommentDTO( 43 | gatheringID: gatheringID ?? self.gatheringID, 44 | authorID: authorID ?? self.authorID, 45 | content: content ?? self.content, 46 | createdAt: createdAt ?? self.createdAt 47 | ) 48 | } 49 | 50 | } 51 | 52 | extension GatheringComment { 53 | 54 | func toGatheringCommentDTO() -> GatheringCommentDTO { 55 | return GatheringCommentDTO( 56 | gatheringID: self.gathering.gatheringID, 57 | authorID: self.author.bandID, 58 | content: self.content, 59 | createdAt: Timestamp(date: self.createdAt) 60 | ) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/DTO/GatheringDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GatheringDTO.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/04. 6 | // 7 | 8 | import Foundation 9 | 10 | import Firebase 11 | 12 | /// 밴드팅 모임입니다. 13 | struct GatheringDTO: Codable { 14 | /// 모임의 제목입니다. 15 | let title: String 16 | /// 모임의 주최 밴드입니다. 17 | let hostBandID: String 18 | /// 모임의 진행 전/중/후 및 취소를 나타내는 상태정보입니다. 19 | let status: GatheringStatus 20 | /// 모임이 이루어지는 날짜 및 시간입니다. 21 | let date: Timestamp 22 | /// 모임이 이루어지는 위치입니다. 23 | let location: Location 24 | /// 모임의 대한 소개글입니다. 25 | let introduction: String 26 | /// 모임 모집을 시작하는 시기 정보입니다. 27 | let createdAt: Timestamp 28 | 29 | } 30 | 31 | extension GatheringDTO { 32 | 33 | func toGathering() async throws -> Gathering { 34 | let bandAPI = BandAPI() 35 | let hostBand = try await bandAPI.getBandInfo(bandID: hostBandID) 36 | return Gathering( 37 | title: self.title, 38 | host: hostBand, 39 | status: self.status, 40 | date: self.date.dateValue(), 41 | location: self.location, 42 | introduction: self.introduction, 43 | createdAt: self.createdAt.dateValue() 44 | ) 45 | } 46 | 47 | func changeValue( 48 | title: String? = nil, 49 | hostBandID: String? = nil, 50 | status: GatheringStatus? = nil, 51 | date: Timestamp? = nil, 52 | location: Location? = nil, 53 | introduction: String? = nil, 54 | createdAt: Timestamp? = nil 55 | ) -> Self { 56 | GatheringDTO( 57 | title: title ?? self.title, 58 | hostBandID: hostBandID ?? self.hostBandID, 59 | status: status ?? self.status, 60 | date: date ?? self.date, 61 | location: location ?? self.location, 62 | introduction: introduction ?? self.introduction, 63 | createdAt: createdAt ?? self.createdAt 64 | ) 65 | } 66 | 67 | } 68 | 69 | extension Gathering { 70 | 71 | func toGatheringDTO() -> GatheringDTO { 72 | GatheringDTO( 73 | title: self.title, 74 | hostBandID: self.host.bandID, 75 | status: self.status, 76 | date: Timestamp(date: self.date), 77 | location: self.location, 78 | introduction: self.introduction, 79 | createdAt: Timestamp(date: self.createdAt) 80 | ) 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/DTO/VisitorCommentDTO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisitorCommentDTO.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/04. 6 | // 7 | 8 | import Foundation 9 | 10 | import Firebase 11 | 12 | struct VisitorCommentDTO: Codable { 13 | let hostBandID: String 14 | let authorID: String 15 | let content: String 16 | let createdAt: Timestamp 17 | } 18 | 19 | extension VisitorCommentDTO { 20 | 21 | func toVisitorComment() async throws -> VisitorComment { 22 | let bandAPI = BandAPI() 23 | 24 | async let hostBand = bandAPI.getBandInfo(bandID: self.hostBandID) 25 | async let author = bandAPI.getBandInfo(bandID: self.authorID) 26 | 27 | return VisitorComment( 28 | hostBand: try await hostBand, 29 | author: try await author, 30 | content: self.content, 31 | createdAt: self.createdAt.dateValue() 32 | ) 33 | 34 | } 35 | 36 | func changeValue( 37 | hostBandID: String? = nil, 38 | authorID: String? = nil, 39 | content: String? = nil, 40 | createdAt: Timestamp? = nil 41 | ) -> Self { 42 | VisitorCommentDTO( 43 | hostBandID: hostBandID ?? self.hostBandID, 44 | authorID: authorID ?? self.authorID, 45 | content: content ?? self.content, 46 | createdAt: createdAt ?? self.createdAt 47 | ) 48 | } 49 | 50 | } 51 | 52 | extension VisitorComment { 53 | 54 | func toVisitorCommentDTO() -> VisitorCommentDTO { 55 | return VisitorCommentDTO( 56 | hostBandID: self.hostBand.bandID, 57 | authorID: self.author.bandID, 58 | content: self.content, 59 | createdAt: Timestamp(date: self.createdAt) 60 | ) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/Gathering.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Event.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct GatheringInfo { 11 | let gatheringID: String 12 | let gathering: Gathering 13 | } 14 | 15 | enum GatheringStatus: String, Codable { 16 | 17 | static let progressingTerm: TimeInterval = 3600*3 18 | 19 | case recruiting 20 | case progressing 21 | case finished 22 | case canceled 23 | 24 | func toKorean() -> String { 25 | switch self { 26 | case .recruiting: return "모집중" 27 | case .progressing: return "진행중" 28 | case .finished: return "완료됨" 29 | case .canceled: return "취소됨" 30 | } 31 | } 32 | 33 | func calculateStatus(date: Date) -> Self { 34 | if date > Date() { return .recruiting } 35 | if date <= Date() && Date() < Date(timeInterval: Self.progressingTerm, since: date) { 36 | return .progressing 37 | } 38 | if Date() >= Date(timeInterval: Self.progressingTerm, since: date) { 39 | return .finished 40 | } 41 | return .canceled 42 | } 43 | 44 | } 45 | 46 | /// 모여락(밴드팅 모임)입니다. 47 | struct Gathering { 48 | 49 | /// 모여락의 제목입니다. 50 | let title: String 51 | /// 모여락의 주최 밴드입니다. 52 | let host: BandInfo 53 | /// 모여락의 진행 전/중/후 및 취소를 나타내는 상태정보입니다. 54 | let status: GatheringStatus 55 | /// 모여락이 이루어지는 날짜 및 시간입니다. 56 | let date: Date 57 | /// 모여락이 이루어지는 위치입니다. 58 | let location: Location 59 | /// 모여락에 대한 소개글입니다. 60 | let introduction: String 61 | /// 모여락 모집을 시작하는 시기 정보입니다. 62 | let createdAt: Date 63 | 64 | } 65 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/GatheringComment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventComment.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct GatheringCommentInfo { 11 | let commentID: String 12 | let comment: GatheringComment 13 | } 14 | 15 | struct GatheringComment { 16 | let gathering: GatheringInfo 17 | let author: BandInfo 18 | let content: String 19 | let createdAt: Date 20 | } 21 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/Location.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Location.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/18. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Location: Codable { 11 | /// 나타내고자하는 위치의 이름입니다. 12 | let name: String? 13 | /// 나타내고자하는 위치의 주소입니다. 14 | let address: String? 15 | /// 나타내고자하는 위치의 세부 주소입니다. 16 | /// ex) 건물의 동호수 17 | let additionalAddress: String? 18 | /// 나타내고자하는 위치의 좌표입니다. 19 | let coordinate: Coordinate 20 | } 21 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/PlayPosition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayPosition.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/18. 6 | // 7 | 8 | import Foundation 9 | 10 | enum PlayPosition: Codable { 11 | case vocal 12 | case guitar 13 | case keyboard 14 | case drum 15 | case bass 16 | case etc 17 | 18 | func toKorean() -> String { 19 | switch self { 20 | case .vocal: return "보컬" 21 | case .guitar: return "기타" 22 | case .keyboard: return "키보드" 23 | case .drum: return "드럼" 24 | case .bass: return "베이스" 25 | case .etc: return "그 외" 26 | } 27 | } 28 | 29 | func imageName() -> String { 30 | switch self { 31 | case .vocal: return "Vocal" 32 | case .guitar: return "Guitar" 33 | case .keyboard: return "Keyboard" 34 | case .drum: return "Drum" 35 | case .bass: return "Bass" 36 | case .etc: return "Etc" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Model/VisitorComment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisitorComment.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/18. 6 | // 7 | 8 | import Foundation 9 | 10 | struct VisitorCommentInfo { 11 | let commentID: String 12 | var comment: VisitorComment 13 | } 14 | 15 | struct VisitorComment { 16 | let hostBand: BandInfo 17 | let author: BandInfo 18 | let content: String 19 | let createdAt: Date 20 | } 21 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Storage/UserDefaultHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultHandler.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/03. 6 | // 7 | 8 | import Foundation 9 | 10 | struct UserDefaultHandler { 11 | static func clearAllData() { 12 | UserData.clearAll() 13 | } 14 | 15 | static func setUserEmail(email: String) { 16 | UserData.setValue(email, forKey: .userEmail) 17 | } 18 | 19 | static func setUserPassword(password: String) { 20 | UserData.setValue(password, forKey: .userPassword) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /GetARock/GetARock/Network/Storage/UserDefaultStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultStorage.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/12/03. 6 | // 7 | 8 | import Foundation 9 | 10 | enum DataKeys: String, CaseIterable { 11 | case userEmail 12 | case userPassword 13 | } 14 | 15 | struct UserDefaultStorage { 16 | static var userEmail: String { 17 | return UserData.getValue(forKey: .userEmail) ?? "" 18 | } 19 | 20 | static var userPassword: String { 21 | return UserData.getValue(forKey: .userPassword) ?? "" 22 | } 23 | } 24 | 25 | struct UserData { 26 | static func getValue(forKey key: DataKeys) -> T? { 27 | if let data = UserDefaults.standard.value(forKey: key.rawValue) as? T { 28 | return data 29 | } else { 30 | return nil 31 | } 32 | } 33 | 34 | static func setValue(_ value: T, forKey key: DataKeys) { 35 | UserDefaults.standard.set(value, forKey: key.rawValue) 36 | } 37 | 38 | static func clearAll() { 39 | DataKeys.allCases.forEach { key in 40 | UserDefaults.standard.removeObject(forKey: key.rawValue) 41 | } 42 | } 43 | 44 | static func clear(forKey key: DataKeys) { 45 | UserDefaults.standard.removeObject(forKey: key.rawValue) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/AddGathering/Cell/LocationCandidateCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationCandidateCell.swift 3 | // GetARock 4 | // 5 | // Created by Hyorim Nam on 2022/12/02. 6 | // 7 | 8 | import UIKit 9 | 10 | class LocationCandidateCell: UITableViewCell { 11 | 12 | @IBOutlet weak var locationNameLabel: UILabel! 13 | @IBOutlet weak var addressLabel: UILabel! 14 | 15 | } 16 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/AddGathering/VC/AddGatheringViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddGatheringViewController.swift 3 | // GetARock 4 | // 5 | // Created by Hyorim Nam on 2022/11/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class AddGatheringViewController: UIViewController { 11 | 12 | // MARK: - Property 13 | 14 | private var hostBandName: String? 15 | private var gatheringLocation: Location? 16 | 17 | // MARK: - View 18 | 19 | @IBOutlet weak var titleTextField: UITextField! 20 | @IBOutlet weak var hostBandNameLabel: UILabel! 21 | @IBOutlet weak var dateTimePicker: UIDatePicker! 22 | @IBOutlet weak var locationLabel: UILabel! 23 | @IBOutlet weak var introductionTextView: UITextView! 24 | @IBOutlet weak var scrollView: UIScrollView! 25 | 26 | private let placeHolderLabel: UILabel = { 27 | $0.text = "내용을 입력하세요" 28 | $0.textColor = .lightGray 29 | $0.sizeToFit() 30 | $0.font = .preferredFont(forTextStyle: .subheadline) 31 | return $0 32 | }(UILabel()) 33 | 34 | // MARK: - Life Cycle 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | 39 | attribute() 40 | setDelegate() 41 | setupLayout() 42 | } 43 | 44 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 45 | if let locationSearchViewController = segue.destination as? LocationSearchViewController { 46 | locationSearchViewController.delegate = self 47 | } 48 | } 49 | 50 | deinit { 51 | removeObserversForKeyboardShow() 52 | } 53 | 54 | // MARK: - Method 55 | 56 | @IBAction func cancelButtonAction(_ sender: UIBarButtonItem) { 57 | if introductionTextView.text.count > 0 || (titleTextField?.text ?? "").count > 0 || gatheringLocation != nil { 58 | let cancelAlert = UIAlertController( 59 | title: nil, 60 | message: "작성중인 내용이 있습니다. 작성을 취소하시겠습니까?", 61 | preferredStyle: .alert 62 | ) 63 | let cancel = UIAlertAction(title: "아니오", style: .cancel) 64 | let confirm = UIAlertAction(title: "예", style: .destructive, handler: {_ in 65 | self.dismiss(animated: true) 66 | }) 67 | cancelAlert.addAction(cancel) 68 | cancelAlert.addAction(confirm) 69 | self.present(cancelAlert, animated: true) 70 | } else { 71 | dismiss(animated: true) 72 | } 73 | } 74 | 75 | @IBAction func saveButtonAction(_ sender: UIBarButtonItem) { 76 | if let errorString = addGatheringInputErrorMessage() { 77 | let alert = UIAlertController(title: nil, message: errorString, preferredStyle: .alert) 78 | let confirm = UIAlertAction(title: "확인", style: .default) 79 | alert.addAction(confirm) 80 | self.present(alert, animated: true) 81 | return 82 | } 83 | guard let gatheringLocation = gatheringLocation else { 84 | return // 위의 errorString에서 확인한 부분입니다. 85 | } 86 | let gathering = Gathering( 87 | title: titleTextField.text ?? "이름없음", 88 | host: MockData.bands[0], // 테스트용 - 추후 변경 89 | status: .recruiting, 90 | date: dateTimePicker.date, 91 | location: gatheringLocation, 92 | introduction: introductionTextView.text, 93 | createdAt: Date() 94 | ) 95 | MockData.gatherings.append(GatheringInfo(gatheringID: "testID", gathering: gathering)) // 테스트용 - ID 추후 변경 96 | dismiss(animated: true) 97 | } 98 | 99 | @IBAction func scrollViewTapRecognizer(_ sender: UITapGestureRecognizer) { 100 | view.endEditing(true) 101 | } 102 | 103 | private func attribute() { 104 | setupNavigationBar() 105 | hostBandName = MockData.bands[0].band.name // 테스트용 - 추후 변경: 유저디폴트 사용 예정 106 | hostBandNameLabel.text = hostBandName 107 | dateTimePicker.minimumDate = Date() 108 | titleTextField.becomeFirstResponder() 109 | addObeserversForKeyboardShow() 110 | } 111 | 112 | private func setDelegate() { 113 | introductionTextView.delegate = self 114 | } 115 | 116 | private func setupNavigationBar() { 117 | navigationController?.navigationBar.shadowImage = UIImage() 118 | title = "" 119 | } 120 | 121 | private func setupLayout() { 122 | view.addSubview(placeHolderLabel) 123 | placeHolderLabel.translatesAutoresizingMaskIntoConstraints = false 124 | NSLayoutConstraint.activate([ 125 | placeHolderLabel.topAnchor.constraint(equalTo: introductionTextView.topAnchor, constant: 8), 126 | placeHolderLabel.leadingAnchor.constraint(equalTo: introductionTextView.leadingAnchor, constant: 4) 127 | ]) 128 | } 129 | } 130 | 131 | // MARK: - UITextViewDelegate 132 | 133 | extension AddGatheringViewController: UITextViewDelegate { 134 | func textViewDidChange(_ textView: UITextView) { 135 | self.placeHolderLabel.textColor = introductionTextView.text.isEmpty ? .lightGray : .clear 136 | } 137 | } 138 | 139 | // MARK: - Location Delegate 140 | 141 | extension AddGatheringViewController: LocationSearchViewControllerDelegate { 142 | func setLocation(name: String?, address: String?, additionalAddress: String?, coordinate: Coordinate) { 143 | gatheringLocation = Location(name: name, address: address, additionalAddress: additionalAddress, coordinate: coordinate) 144 | var gatheringAddress = address ?? "" 145 | if let detailAddress = additionalAddress { 146 | gatheringAddress += " " + detailAddress 147 | } 148 | if !gatheringAddress.isEmpty { 149 | locationLabel.text = gatheringAddress 150 | locationLabel.textColor = .white 151 | } 152 | } 153 | } 154 | 155 | // MARK: - custom errors 156 | 157 | extension AddGatheringViewController { 158 | enum AddGatheringInputError: Error { 159 | case noTitle 160 | case noLocation 161 | case noMultipleFields 162 | 163 | var errorMessage: String { 164 | switch self { 165 | case .noTitle: 166 | return "모여락의 이름을 입력해주세요" 167 | case .noLocation: 168 | return "모여락의 장소를 입력해주세요" 169 | case .noMultipleFields: 170 | return "모여락의 이름, 장소를 입력해주세요" 171 | } 172 | } 173 | } 174 | 175 | private func validateInputs() throws { 176 | var errors: [AddGatheringInputError] = [] 177 | if titleTextField.text == nil || (titleTextField.text ?? "").filter({$0 != " "}).isEmpty { 178 | errors.append(.noTitle) 179 | } 180 | if gatheringLocation == nil { 181 | errors.append(.noLocation) 182 | } 183 | 184 | if errors.count > 1 { 185 | throw AddGatheringInputError.noMultipleFields 186 | } else if errors.count == 1 { 187 | throw errors[0] 188 | } 189 | } 190 | 191 | private func addGatheringInputErrorMessage() -> String? { 192 | do { 193 | try validateInputs() 194 | } catch { 195 | if let error = error as? AddGatheringInputError { 196 | return error.errorMessage 197 | } 198 | return "입력을 확인하는 중 알 수 없는 문제가 발생했습니다" 199 | } 200 | return nil 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/AddGathering/VC/Extension/AddGatheringViewController+ScrollAdaptToKeyboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddGatheringViewController+ScrollAdaptToKeyboard.swift 3 | // GetARock 4 | // 5 | // Created by Hyorim Nam on 2023/01/16. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - Scroll Adaptation for Keyboard Show 11 | 12 | extension AddGatheringViewController { 13 | func addObeserversForKeyboardShow() { 14 | NotificationCenter.default.addObserver( 15 | self, 16 | selector: #selector(adaptScrollViewToKeyboardShow(_:)), 17 | name: UIResponder.keyboardWillShowNotification, 18 | object: nil 19 | ) 20 | NotificationCenter.default.addObserver( 21 | self, 22 | selector: #selector(adaptScrollViewToKeyboardHide(_:)), 23 | name: UIResponder.keyboardWillHideNotification, 24 | object: nil 25 | ) 26 | } 27 | 28 | func removeObserversForKeyboardShow() { 29 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) 30 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) 31 | } 32 | 33 | @objc func adaptScrollViewToKeyboardShow(_ sender: Notification) { 34 | guard let userInfo: NSDictionary = sender.userInfo as NSDictionary?, 35 | let keyboardFrame: NSValue = userInfo.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as? NSValue else { 36 | return 37 | } 38 | let keyboardHeight = keyboardFrame.cgRectValue.height 39 | let contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardHeight, right: 0.0) 40 | scrollView.contentInset = contentInset 41 | scrollView.scrollIndicatorInsets = contentInset 42 | } 43 | 44 | @objc func adaptScrollViewToKeyboardHide(_ sender: Notification) { 45 | scrollView.contentInset = UIEdgeInsets.zero 46 | scrollView.scrollIndicatorInsets = UIEdgeInsets.zero 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/AddGathering/VC/Extension/LocationSearchResultViewController+ScrollAdaptToKeyboard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationSearchResultViewController+ScrollAdaptToKeyboard.swift 3 | // GetARock 4 | // 5 | // Created by Hyorim Nam on 2023/01/16. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - Scroll Adaptation for Keyboard Show 11 | 12 | extension LocationSearchResultViewController { 13 | func addObeserversForKeyboardShow() { 14 | NotificationCenter.default.addObserver( 15 | self, 16 | selector: #selector(adaptScrollViewToKeyboardShow(_:)), 17 | name: UIResponder.keyboardWillShowNotification, 18 | object: nil 19 | ) 20 | NotificationCenter.default.addObserver( 21 | self, 22 | selector: #selector(adaptScrollViewToKeyboardHide(_:)), 23 | name: UIResponder.keyboardWillHideNotification, 24 | object: nil 25 | ) 26 | } 27 | 28 | func removeObserversForKeyboardShow() { 29 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) 30 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) 31 | } 32 | 33 | @objc func adaptScrollViewToKeyboardShow(_ sender: Notification) { 34 | guard let userInfo: NSDictionary = sender.userInfo as NSDictionary?, 35 | let keyboardFrame: NSValue = userInfo.value(forKey: UIResponder.keyboardFrameEndUserInfoKey) as? NSValue else { 36 | return 37 | } 38 | let keyboardHeight = keyboardFrame.cgRectValue.height 39 | let contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: keyboardHeight, right: 0.0) 40 | tableView.contentInset = contentInset 41 | tableView.scrollIndicatorInsets = contentInset 42 | } 43 | 44 | @objc func adaptScrollViewToKeyboardHide(_ sender: Notification) { 45 | tableView.contentInset = UIEdgeInsets.zero 46 | tableView.scrollIndicatorInsets = UIEdgeInsets.zero 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/AddGathering/VC/LocationSearchResultViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationSearchResultViewController.swift 3 | // GetARock 4 | // 5 | // Created by Hyorim Nam on 2022/12/02. 6 | // 7 | 8 | import MapKit 9 | import UIKit 10 | 11 | class LocationSearchResultViewController: UIViewController { 12 | 13 | // MARK: - Property 14 | 15 | var searchCompleter: MKLocalSearchCompleter? 16 | var suggestedPlaces: [MKLocalSearchCompletion] = [] { 17 | didSet { 18 | tableView.reloadData() 19 | } 20 | } 21 | 22 | // MARK: - View 23 | 24 | @IBOutlet weak var tableView: UITableView! 25 | 26 | // MARK: - Life Cycle 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | attribute() 31 | } 32 | 33 | override func viewWillAppear(_ animated: Bool) { 34 | super.viewWillAppear(animated) 35 | startProvidingCompletions() 36 | } 37 | 38 | override func viewDidDisappear(_ animated: Bool) { 39 | super.viewDidDisappear(animated) 40 | stopProvidingCompletions() 41 | } 42 | 43 | deinit { 44 | removeObserversForKeyboardShow() 45 | } 46 | 47 | // MARK: - Method 48 | 49 | private func attribute() { 50 | setupTableView() 51 | addObeserversForKeyboardShow() 52 | } 53 | 54 | private func setupTableView() { 55 | let nibName = UINib(nibName: LocationCandidateCell.className, bundle: nil) 56 | tableView.register(nibName, forCellReuseIdentifier: LocationCandidateCell.className) 57 | tableView.dataSource = self 58 | } 59 | 60 | private func startProvidingCompletions() { 61 | searchCompleter = MKLocalSearchCompleter() 62 | searchCompleter?.delegate = self 63 | } 64 | 65 | private func stopProvidingCompletions() { 66 | searchCompleter = nil 67 | } 68 | } 69 | 70 | // MARK: - MK Local Search Completer Delegate (지역검색 결과 업데이트) 71 | 72 | extension LocationSearchResultViewController: MKLocalSearchCompleterDelegate { 73 | func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { 74 | suggestedPlaces = completer.results.filter { $0.subtitle.contains("대한민국") } 75 | } 76 | } 77 | 78 | // MARK: - Search Results Updating (search controller 관련) 79 | 80 | extension LocationSearchResultViewController: UISearchResultsUpdating { 81 | func updateSearchResults(for searchController: UISearchController) { 82 | searchCompleter?.queryFragment = searchController.searchBar.text ?? "" 83 | } 84 | } 85 | 86 | // MARK: - Table View Data Source 87 | 88 | extension LocationSearchResultViewController: UITableViewDataSource { 89 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 90 | return suggestedPlaces.count 91 | } 92 | 93 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 94 | guard let cell = tableView.dequeueReusableCell(withIdentifier: LocationCandidateCell.className, for: indexPath) as? 95 | LocationCandidateCell else { 96 | return UITableViewCell() 97 | } 98 | cell.locationNameLabel?.text = suggestedPlaces[indexPath.row].title 99 | cell.addressLabel?.text = suggestedPlaces[indexPath.row].subtitle 100 | 101 | return cell 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/BandInfo/Cell/BandMemberCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BandMemberCollectionViewCell.swift 3 | // GetARock 4 | // 5 | // Created by Seungwon Choi on 2022/11/20. 6 | // 7 | 8 | import UIKit 9 | 10 | class BandMemberCollectionViewCell: UICollectionViewCell { 11 | 12 | @IBOutlet weak var positionNameLabel: UILabel! 13 | @IBOutlet weak var numberOfPositionLabel: UILabel! 14 | @IBOutlet weak var positionImageView: UIImageView! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | 19 | self.applyBandInfoBoxDesign(cornerRadius: 14) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/BandInfo/Cell/BandTimelineCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BandTimelineCell.swift 3 | // GetARock 4 | // 5 | // Created by Admin on 2022/11/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class BandTimelineCell: UITableViewCell { 11 | 12 | @IBOutlet weak var dateLabel: UILabel! 13 | @IBOutlet weak var titleLabel: UILabel! 14 | @IBOutlet weak var statusLabel: UILabel! 15 | @IBOutlet weak var topLine: UIView! 16 | @IBOutlet weak var bottomLine: UIView! 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | } 21 | 22 | override func setSelected(_ selected: Bool, animated: Bool) { 23 | super.setSelected(selected, animated: animated) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/BandInfo/Cell/RepertoireTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RepertoireTableViewCell.swift 3 | // GetARock 4 | // 5 | // Created by Seungwon Choi on 2022/11/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class RepertoireTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var repertoireCellView: UIView! 13 | @IBOutlet weak var repertoireLabel: UILabel! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | 18 | repertoireCellView.applyBandInfoBoxDesign(cornerRadius: 15) 19 | } 20 | 21 | override func setSelected(_ selected: Bool, animated: Bool) { 22 | super.setSelected(selected, animated: animated) 23 | 24 | // Configure the view for the selected state 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/BandInfo/TopViewOfInfoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopViewOfInfoView.swift 3 | // GetARock 4 | // 5 | // Created by Somin Park on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | 10 | final class TopViewOfInfoView: UIView { 11 | 12 | // MARK: - Properties 13 | 14 | private var bandNameLabel: UILabel = { 15 | $0.textColor = .white 16 | $0.font = UIFont.systemFont(ofSize: 22, weight: .bold) 17 | $0.translatesAutoresizingMaskIntoConstraints = false 18 | return $0 19 | }(UILabel()) 20 | 21 | private var bandLocationLabel: UILabel = { 22 | $0.textColor = .white 23 | $0.font = UIFont.systemFont(ofSize: 14) 24 | $0.translatesAutoresizingMaskIntoConstraints = false 25 | return $0 26 | }(UILabel()) 27 | 28 | private var divider: UIView = { 29 | $0.backgroundColor = .backgroundBlue 30 | $0.translatesAutoresizingMaskIntoConstraints = false 31 | return $0 32 | }(UIView()) 33 | 34 | convenience init(bandName: String, bandLocation: String) { 35 | self.init(frame: .zero) 36 | self.bandNameLabel.text = bandName 37 | self.bandLocationLabel.text = bandLocation 38 | attribute() 39 | setupLayout() 40 | } 41 | 42 | } 43 | 44 | // MARK: - Layout 45 | 46 | extension TopViewOfInfoView { 47 | private func attribute() { 48 | addSubview(bandNameLabel) 49 | addSubview(bandLocationLabel) 50 | addSubview(divider) 51 | } 52 | private func configureBandNameLabel() { 53 | NSLayoutConstraint.activate([ 54 | bandNameLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 40), 55 | bandNameLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16) 56 | ]) 57 | } 58 | private func configureBandLocationLabel() { 59 | NSLayoutConstraint.activate([ 60 | bandLocationLabel.topAnchor.constraint(equalTo: bandNameLabel.bottomAnchor, constant: 10), 61 | bandLocationLabel.leadingAnchor.constraint(equalTo: bandNameLabel.leadingAnchor) 62 | ]) 63 | } 64 | private func configureDividedLine() { 65 | NSLayoutConstraint.activate([ 66 | divider.topAnchor.constraint(equalTo: bandLocationLabel.bottomAnchor, constant: 20), 67 | divider.leadingAnchor.constraint(equalTo: self.leadingAnchor), 68 | divider.trailingAnchor.constraint(equalTo: self.trailingAnchor), 69 | divider.heightAnchor.constraint(equalToConstant: 5) 70 | ]) 71 | } 72 | private func setupLayout() { 73 | configureBandNameLabel() 74 | configureBandLocationLabel() 75 | configureDividedLine() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/BandInfo/VC/BandInfoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BandInfoViewController.swift 3 | // GetARock 4 | // 5 | // Created by Seungwon Choi on 2022/11/19. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - 밴드 정보 영역 View Controller 11 | 12 | class BandInfoViewController: UIViewController { 13 | // MARK: - View 14 | 15 | @IBOutlet weak var bandMemberLabel: UILabel! 16 | @IBOutlet weak var bandMemberCollectionView: UICollectionView! 17 | @IBOutlet weak var bandIntroduceLabel: UILabel! 18 | @IBOutlet weak var bandIntroduceView: UIView! 19 | @IBOutlet weak var bandAgeLabel: UILabel! 20 | @IBOutlet weak var repertoireTableView: UITableView! 21 | @IBOutlet weak var tableHeightConstraint: NSLayoutConstraint! 22 | 23 | // MARK: - Property 24 | 25 | private let selectedBand: Band = MockData.bands[0].band 26 | 27 | private lazy var numberOfBandMember: Int = selectedBand.filledPosition.reduce(0) { $0 + $1.numberOfPerson } 28 | private lazy var positionNameArray: [String] = selectedBand.filledPosition.map { $0.position.toKorean() } 29 | private lazy var positionImageNameArray: [String] = selectedBand.filledPosition.map { $0.position.imageName() } 30 | private lazy var numberOfPostionArray: [Int] = selectedBand.filledPosition.map { $0.numberOfPerson } 31 | private lazy var bandAgeArray: [String] = selectedBand.ageGroups.map { $0.toKorean() } 32 | private lazy var repertoireArray: [String] = selectedBand.repertoire 33 | private lazy var bandIntroduceText = selectedBand.introduction 34 | 35 | // MARK: - Life Cycle 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | setUI() 41 | } 42 | 43 | override func updateViewConstraints() { 44 | tableHeightConstraint.constant = repertoireTableView.contentSize.height 45 | super.updateViewConstraints() 46 | } 47 | 48 | override func viewWillLayoutSubviews() { 49 | bandIntroduceLabel.sizeToFit() 50 | } 51 | } 52 | 53 | // MARK: - UI 설정 관련 54 | 55 | extension BandInfoViewController { 56 | private func setUI() { 57 | setBandMemberAreaUI() 58 | setBandAgeAreaUI() 59 | setRepertoireAreaUI() 60 | setBandIntroduceAreaUI() 61 | } 62 | 63 | private func setBandMemberAreaUI() { 64 | bandMemberLabel.text = "밴드 멤버 (\(numberOfBandMember)인)" 65 | 66 | bandMemberCollectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 67 | bandMemberCollectionView.dataSource = self 68 | bandMemberCollectionView.collectionViewLayout = createCompositionalLayout() 69 | } 70 | 71 | private func setBandAgeAreaUI() { 72 | bandAgeLabel.text = bandAgeArray.joined(separator: ", ") 73 | } 74 | 75 | private func setRepertoireAreaUI() { 76 | let repertoireTableViewCellNib = UINib(nibName: RepertoireTableViewCell.className, bundle: nil) 77 | 78 | repertoireTableView.register(repertoireTableViewCellNib, forCellReuseIdentifier: RepertoireTableViewCell.className) 79 | repertoireTableView.rowHeight = UITableView.automaticDimension 80 | repertoireTableView.estimatedRowHeight = 60 81 | repertoireTableView.dataSource = self 82 | } 83 | 84 | private func setBandIntroduceAreaUI() { 85 | bandIntroduceLabel.text = bandIntroduceText 86 | bandIntroduceView.applyBandInfoBoxDesign(cornerRadius: 15) 87 | } 88 | } 89 | 90 | // MARK: - 컬렉션뷰 compositional layout 관련 91 | 92 | extension BandInfoViewController { 93 | private func createCompositionalLayout() -> UICollectionViewLayout { 94 | let layout = UICollectionViewCompositionalLayout { (_: Int, _: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in 95 | 96 | let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalHeight(1)) 97 | let item = NSCollectionLayoutItem(layoutSize: itemSize) 98 | item.contentInsets = NSDirectionalEdgeInsets(top: 3, leading: 3, bottom: 3, trailing: 3) 99 | 100 | let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1/2)) 101 | let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) 102 | 103 | let section = NSCollectionLayoutSection(group: group) 104 | section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) 105 | 106 | return section 107 | } 108 | 109 | return layout 110 | } 111 | } 112 | 113 | // MARK: - 컬렉션뷰 데이터 삽입 관련 114 | 115 | extension BandInfoViewController: UICollectionViewDataSource { 116 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 117 | return self.positionNameArray.count 118 | } 119 | 120 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 121 | guard let cell = collectionView.dequeueReusableCell( 122 | withReuseIdentifier: BandMemberCollectionViewCell.className, 123 | for: indexPath 124 | ) as? BandMemberCollectionViewCell 125 | else { 126 | return UICollectionViewCell() 127 | } 128 | 129 | cell.positionNameLabel.text = self.positionNameArray[indexPath.item] 130 | cell.numberOfPositionLabel.text = "\(self.numberOfPostionArray[indexPath.item])명" 131 | cell.positionImageView.image = UIImage(named: self.positionImageNameArray[indexPath.item]) 132 | 133 | return cell 134 | } 135 | } 136 | 137 | // MARK: - 테이블뷰 데이터 삽입 관련 138 | 139 | extension BandInfoViewController: UITableViewDataSource { 140 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 141 | return self.repertoireArray.count 142 | } 143 | 144 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 145 | guard let cell = repertoireTableView.dequeueReusableCell( 146 | withIdentifier: RepertoireTableViewCell.className, 147 | for: indexPath 148 | ) as? RepertoireTableViewCell 149 | else { 150 | return UITableViewCell() 151 | } 152 | 153 | cell.repertoireLabel.text = repertoireArray[indexPath.row] 154 | 155 | return cell 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/BandInfo/VC/BandPageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BandPageViewController.swift 3 | // GetARock 4 | // 5 | // Created by Somin Park on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class BandPageViewController: UIViewController { 11 | 12 | // MARK: - View 13 | 14 | private let topView = TopViewOfInfoView(bandName: "블랙로즈", bandLocation: "주소다주소야주소다주소야") 15 | 16 | // MARK: - View Life Cycle 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | setupLayout() 21 | } 22 | 23 | } 24 | 25 | // MARK: - Layout 26 | 27 | extension BandPageViewController { 28 | private func configureTopView() { 29 | topView.translatesAutoresizingMaskIntoConstraints = false 30 | view.addSubview(topView) 31 | NSLayoutConstraint.activate([ 32 | topView.topAnchor.constraint(equalTo: view.topAnchor), 33 | topView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 34 | topView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 35 | topView.heightAnchor.constraint(equalToConstant: 130) 36 | ]) 37 | } 38 | private func setupLayout() { 39 | view.backgroundColor = .modalBackgroundBlue 40 | configureTopView() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/BandInfo/VC/BandTimelineViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BandTimelineViewController.swift 3 | // GetARock 4 | // 5 | // Created by Admin on 2022/11/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class BandTimelineViewController: UIViewController { 11 | 12 | // MARK: - Property 13 | 14 | @IBOutlet private weak var tableView: UITableView! 15 | 16 | var gatheringInfos: [GatheringInfo] = [] 17 | 18 | // MARK: - View Life Cycle 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | let nibName = UINib(nibName: "BandTimelineCell", bundle: nil) 24 | tableView.register(nibName, forCellReuseIdentifier: BandTimelineCell.className) 25 | } 26 | 27 | // MARK: - Method 28 | 29 | func reloadTableView() { 30 | tableView.reloadData() 31 | } 32 | } 33 | 34 | // MARK: - UITableViewDataSource 35 | 36 | extension BandTimelineViewController: UITableViewDataSource { 37 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 38 | return gatheringInfos.count 39 | } 40 | 41 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 42 | guard let cell = tableView.dequeueReusableCell(withIdentifier: BandTimelineCell.className, for: indexPath) as? BandTimelineCell else { return UITableViewCell() } 43 | 44 | let gathering = gatheringInfos[indexPath.row].gathering 45 | cell.titleLabel.text = gathering.title 46 | cell.dateLabel.text = gathering.date.toString(format: DateFormatLiteral.standard) 47 | 48 | let statusType = gathering.status 49 | if statusType == .recruiting || statusType == .progressing { 50 | cell.statusLabel.text = statusType.toKorean() 51 | } else { 52 | cell.statusLabel.isHidden = true 53 | } 54 | 55 | if indexPath.row == 0 { 56 | cell.topLine.isHidden = true 57 | } 58 | if indexPath.row == (gatheringInfos.count) - 1 { 59 | cell.bottomLine.isHidden = true 60 | } 61 | 62 | return cell 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/BandInfo/VC/VisitorCommentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisitorCommentViewController.swift 3 | // GetARock 4 | // 5 | // Created by Yu ahyeon on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | 10 | final class VisitorCommentViewController: UIViewController { 11 | 12 | // MARK: - View 13 | 14 | private let visitorCommentList = { 15 | $0.translatesAutoresizingMaskIntoConstraints = false 16 | return $0 17 | }(CommentListView(commentMode: .visitorComment)) 18 | 19 | var cellIndex: IndexPath = [] 20 | 21 | // MARK: - Life Cycle 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | attribute() 26 | setupLayout() 27 | setDelegateForCommentList() 28 | } 29 | 30 | // MARK: - Method 31 | 32 | private func attribute() { 33 | view.backgroundColor = .modalBackgroundBlue 34 | } 35 | 36 | private func setupLayout() { 37 | view.addSubview(visitorCommentList) 38 | NSLayoutConstraint.activate([ 39 | visitorCommentList.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20), 40 | visitorCommentList.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), 41 | visitorCommentList.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), 42 | visitorCommentList.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) 43 | ]) 44 | setupWritingButton() 45 | } 46 | 47 | private func setupWritingButton() { 48 | visitorCommentList.commentWritingButton.titleButton.addTarget(self, action: #selector(didTapVisitorCommentButton), for: .touchUpInside) 49 | } 50 | 51 | private func setDelegateForCommentList() { 52 | visitorCommentList.delegate = self 53 | } 54 | 55 | @objc func didTapVisitorCommentButton() { 56 | let popupViewController = CommentWritingPopupViewController(commentMode: .visitorComment) 57 | popupViewController.modalPresentationStyle = .overFullScreen 58 | self.present(popupViewController, animated: false) 59 | popupViewController.delegate = self 60 | } 61 | } 62 | 63 | // MARK: - CommentListUpdateDelegate 64 | 65 | extension VisitorCommentViewController: CommentListUpdateDelegate { 66 | 67 | func refreshCommentList() { 68 | visitorCommentList.tableView.reloadData() 69 | visitorCommentList.setupTotalListNumberLabel() 70 | } 71 | } 72 | 73 | // MARK: - CheckCellIndexDelegate, Reportable 74 | 75 | extension VisitorCommentViewController: CheckCellIndexDelegate, Reportable { 76 | 77 | func checkCellIndex(indexPath: IndexPath) { 78 | cellIndex = indexPath 79 | showActionSheet() 80 | } 81 | 82 | func alertActionButtonPressed() { 83 | MockData.visitorComments.remove(at: cellIndex.row) 84 | visitorCommentList.tableView.reloadData() 85 | visitorCommentList.setupTotalListNumberLabel() 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/Gathering/GatheringListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GatherockListCell.swift 3 | // GetARock 4 | // 5 | // Created by Admin on 2022/11/18. 6 | // 7 | 8 | import UIKit 9 | 10 | class GatheringListCell: UITableViewCell { 11 | 12 | @IBOutlet weak var dateLabel: UILabel! 13 | @IBOutlet weak var titleLabel: UILabel! 14 | @IBOutlet weak var statusLabel: UILabel! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | 19 | } 20 | 21 | override func setSelected(_ selected: Bool, animated: Bool) { 22 | super.setSelected(selected, animated: animated) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/Gathering/GatheringListTapViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GatheringListTapViewController.swift 3 | // GetARock 4 | // 5 | // Created by Somin Park on 2022/11/23. 6 | // 7 | 8 | import UIKit 9 | 10 | enum GatheringListType: CaseIterable { 11 | case createdGathering 12 | case joinedGathering 13 | 14 | func toKorean() -> String { 15 | switch self { 16 | case .createdGathering: return "내가 만든 모여락" 17 | case .joinedGathering: return "댓글 단 모여락" 18 | } 19 | } 20 | } 21 | 22 | class GatheringListTapViewController: UIViewController { 23 | 24 | // MARK: - View 25 | 26 | private let segmentedControlButtons = SwitchingViewSegmentedControl( 27 | buttonTitles: [ 28 | GatheringListType.createdGathering.toKorean(), 29 | GatheringListType.joinedGathering.toKorean() 30 | ]) 31 | let gatheringListContentViewController: GatheringListViewController = UIStoryboard( 32 | name: "GatheringList", 33 | bundle: nil 34 | ).instantiateViewController( 35 | withIdentifier: GatheringListViewController.className 36 | ) as? GatheringListViewController ?? GatheringListViewController() 37 | 38 | // MARK: - View Life Cycle 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | setupLayout() 43 | } 44 | 45 | } 46 | 47 | // MARK: - Layout 48 | 49 | extension GatheringListTapViewController { 50 | private func setupLayout() { 51 | attributes() 52 | configureButton() 53 | configureGatheringList() 54 | } 55 | 56 | private func attributes() { 57 | view.addSubview(segmentedControlButtons) 58 | view.addSubview(gatheringListContentViewController.view) 59 | view.backgroundColor = .modalBackgroundBlue 60 | } 61 | 62 | private func configureButton() { 63 | segmentedControlButtons.translatesAutoresizingMaskIntoConstraints = false 64 | NSLayoutConstraint.activate([ 65 | segmentedControlButtons.topAnchor.constraint(equalTo: view.topAnchor, constant: 50), 66 | segmentedControlButtons.leadingAnchor.constraint(equalTo: view.leadingAnchor), 67 | segmentedControlButtons.trailingAnchor.constraint(equalTo: view.trailingAnchor), 68 | segmentedControlButtons.heightAnchor.constraint(equalToConstant: 40) 69 | ]) 70 | } 71 | 72 | private func configureGatheringList() { 73 | gatheringListContentViewController.view.translatesAutoresizingMaskIntoConstraints = false 74 | NSLayoutConstraint.activate([ 75 | gatheringListContentViewController.view.topAnchor.constraint(equalTo: segmentedControlButtons.bottomAnchor, constant: 3), 76 | gatheringListContentViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), 77 | gatheringListContentViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), 78 | gatheringListContentViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) 79 | ]) 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/Gathering/GatheringListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GatherockListCreatedViewController.swift 3 | // GetARock 4 | // 5 | // Created by Admin on 2022/11/18. 6 | // 7 | 8 | import UIKit 9 | 10 | class GatheringListViewController: UIViewController { 11 | 12 | // MARK: - Properties 13 | 14 | @IBOutlet private weak var tableView: UITableView! 15 | 16 | var gatheringInfos: [GatheringInfo] = [] 17 | 18 | // MARK: - View Life Cycle 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | let nibName = UINib(nibName: "GatheringListCell", bundle: nil) 24 | tableView.register(nibName, forCellReuseIdentifier: GatheringListCell.className) 25 | } 26 | 27 | // MARK: - Method 28 | 29 | func reloadTableView() { 30 | tableView.reloadData() 31 | } 32 | } 33 | 34 | // MARK: - UITableViewDataSource 35 | 36 | extension GatheringListViewController: UITableViewDataSource { 37 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 38 | return gatheringInfos.count 39 | } 40 | 41 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 42 | guard let cell = tableView.dequeueReusableCell(withIdentifier: GatheringListCell.className, for: indexPath) as? GatheringListCell else { return UITableViewCell() } 43 | 44 | let gathering = gatheringInfos[indexPath.row].gathering 45 | cell.titleLabel.text = gathering.title 46 | cell.dateLabel.text = gathering.date.toString(format: DateFormatLiteral.standard) 47 | cell.statusLabel.text = gathering.status.toKorean() 48 | 49 | return cell 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/GatheringInfo/GatheringInfoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GatheringInfoViewController.swift 3 | // GetARock 4 | // 5 | // Created by 진영재 on 2023/01/05. 6 | // 7 | 8 | import UIKit 9 | 10 | final class GatheringInfoViewController: UIViewController { 11 | 12 | // MARK: - properties 13 | 14 | @IBOutlet weak var statusView: UIView! 15 | @IBOutlet weak var statusLabel: UILabel! 16 | @IBOutlet weak var ellipsisButton: UIButton! 17 | @IBOutlet weak var gatheringTitleLabel: UILabel! 18 | @IBOutlet weak var bandNameLabel: UILabel! 19 | @IBOutlet weak var gatheringDateLabel: UILabel! 20 | @IBOutlet weak var gatheringLocationLabel: UILabel! 21 | @IBOutlet weak var descriptionLabel: UILabel! 22 | @IBOutlet weak var commentsView: UIView! 23 | 24 | private var gatheringCommentsListView = { 25 | $0.translatesAutoresizingMaskIntoConstraints = false 26 | return $0 27 | }(CommentListView(commentMode: .gatheringComment)) 28 | 29 | // MARK: - View Life Cycle 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | setupLayout() 34 | commentsView.heightAnchor.constraint(equalToConstant: CGFloat(MockData.gatheringComments.count * 170)).isActive = true 35 | } 36 | 37 | // MARK: - Method 38 | 39 | @IBAction func touchUpInsideEllipsis(_ sender: UIButton) { 40 | showActionSheet() 41 | } 42 | 43 | private func setupLayout() { 44 | commentsView.addSubview(gatheringCommentsListView) 45 | gatheringCommentsListView.tableView.backgroundColor = .modalBackgroundBlue 46 | NSLayoutConstraint.activate([ 47 | gatheringCommentsListView.topAnchor.constraint(equalTo: commentsView.topAnchor, constant: 20), 48 | gatheringCommentsListView.leadingAnchor.constraint(equalTo: commentsView.leadingAnchor, constant: 16), 49 | gatheringCommentsListView.trailingAnchor.constraint(equalTo: commentsView.trailingAnchor, constant: -16), 50 | gatheringCommentsListView.bottomAnchor.constraint(equalTo: commentsView.bottomAnchor) 51 | ]) 52 | setupWritingButton() 53 | } 54 | 55 | private func setupWritingButton() { 56 | gatheringCommentsListView.commentWritingButton.titleButton.addTarget(self, action: #selector(didTapGatheringCommentButton), for: .touchUpInside) 57 | } 58 | } 59 | 60 | // MARK: - extension Reportable Method 61 | 62 | extension GatheringInfoViewController: Reportable { 63 | func alertActionButtonPressed() { 64 | print("삭제에 성공했습니다.") 65 | } 66 | } 67 | 68 | // MARK: - extension CommentListUpdate deledgate 69 | extension GatheringInfoViewController: CommentListUpdateDelegate { 70 | func refreshCommentList() { 71 | gatheringCommentsListView.setupTotalListNumberLabel() 72 | gatheringCommentsListView.tableView.reloadData() 73 | } 74 | 75 | @objc func didTapGatheringCommentButton() { 76 | let popupViewController = CommentWritingPopupViewController(commentMode: .gatheringComment) 77 | popupViewController.delegate = self 78 | popupViewController.modalPresentationStyle = .overFullScreen 79 | self.present(popupViewController, animated: false) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/GatheringInfo/ReportReasonListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReportReasonListCell.swift 3 | // GetARock 4 | // 5 | // Created by 진영재 on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | 10 | final class ReportReasonListCell: UITableViewCell { 11 | 12 | // MARK: - property 13 | 14 | @IBOutlet weak var reportReason: UILabel! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | override func setSelected(_ selected: Bool, animated: Bool) { 22 | super.setSelected(selected, animated: animated) 23 | 24 | // Configure the view for the selected state 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/GatheringInfo/ReportReasonListController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReportReasonListController.swift 3 | // GetARock 4 | // 5 | // Created by 진영재 on 2022/11/22. 6 | // 7 | 8 | import UIKit 9 | 10 | final class ReportReasonListController: UITableViewController, AlertSheet { 11 | 12 | // MARK: - Property 13 | 14 | private let reportReason = ["폭력 또는 위험한 단체", "거짓 정보", "따돌림 또는 괴롭힘", "지적 재산권 침해", "불법 또는 규제 상품 판매", "기타 문제"] 15 | 16 | // MARK: - Life Cycle 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | view.backgroundColor = .modalBackgroundBlue 22 | tableView.tableHeaderView = makingHeader() 23 | 24 | let nib = UINib(nibName: ReportReasonListCell.className, bundle: nil) 25 | tableView.register(nib, forCellReuseIdentifier: ReportReasonListCell.className) 26 | } 27 | 28 | // MARK: - Method 29 | 30 | func alertActionButtonPressed() { 31 | print("신고에 성공했습니다.") 32 | } 33 | 34 | private func makingHeader() -> UIView { 35 | let header = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50)) 36 | header.backgroundColor = .modalBackgroundBlue 37 | 38 | let headerLabel = UILabel(frame: header.bounds) 39 | headerLabel.text = "신고" 40 | headerLabel.textAlignment = .center 41 | headerLabel.textColor = .white 42 | headerLabel.translatesAutoresizingMaskIntoConstraints = false 43 | 44 | header.addSubview(headerLabel) 45 | 46 | NSLayoutConstraint.activate([ 47 | headerLabel.centerXAnchor.constraint(equalTo: header.centerXAnchor), 48 | headerLabel.centerYAnchor.constraint(equalTo: header.centerYAnchor) 49 | ]) 50 | return header 51 | } 52 | 53 | // MARK: - TableView data source 54 | 55 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 56 | return reportReason.count 57 | } 58 | 59 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 60 | guard let cell = tableView.dequeueReusableCell(withIdentifier: ReportReasonListCell.className) as? ReportReasonListCell else { return UITableViewCell() } 61 | cell.reportReason.text = reportReason[indexPath.row] 62 | return cell 63 | } 64 | 65 | // MARK: - TableView delegate 66 | 67 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 68 | tableView.deselectRow(at: indexPath, animated: true) 69 | showAlertSheet(alertTitle: "신고하기", message: "\(reportReason[indexPath.row])을/(를) 사유로 신고하시겠습니까?") 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/Landing/LandingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LandingViewController.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/01. 6 | // 7 | 8 | import UIKit 9 | 10 | class LandingViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | /* 19 | // MARK: - Navigation 20 | 21 | // In a storyboard-based application, you will often want to do a little preparation before navigation 22 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 23 | // Get the new view controller using segue.destination. 24 | // Pass the selected object to the new view controller. 25 | } 26 | */ 27 | 28 | } 29 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/MainMap/Component/BandAnnotationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomAnnotationView.swift 3 | // GetARock 4 | // 5 | // Created by Mijoo Kim on 2022/11/23. 6 | // 7 | 8 | import MapKit 9 | import UIKit 10 | 11 | class BandAnnotationView: MKMarkerAnnotationView { 12 | 13 | override func draw(_ rect: CGRect) { 14 | guard annotation is BandAnnotation else { 15 | super.draw(rect) 16 | return 17 | } 18 | 19 | glyphImage = UIImage(systemName: "music.note") 20 | glyphTintColor = .white 21 | markerTintColor = .mainPurple 22 | 23 | super.draw(rect) 24 | } 25 | 26 | } 27 | 28 | class BandAnnotation: NSObject, MKAnnotation { 29 | 30 | let title: String? 31 | let coordinate: CLLocationCoordinate2D 32 | let bandInfo: BandInfo? 33 | 34 | init(title: String, coordinate: CLLocationCoordinate2D, bandInfo: BandInfo) { 35 | self.title = title 36 | self.coordinate = coordinate 37 | self.bandInfo = bandInfo 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/MainMap/Component/GatheringAnnotationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GatheringAnnotationView.swift 3 | // GetARock 4 | // 5 | // Created by Mijoo Kim on 2023/01/06. 6 | // 7 | 8 | import MapKit 9 | import UIKit 10 | 11 | class GatheringAnnotationView: MKAnnotationView { 12 | 13 | override init(annotation: MKAnnotation?, reuseIdentifier: String?) { 14 | super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) 15 | frame = CGRect(x: 0, y: 0, width: 40, height: 50) 16 | centerOffset = CGPoint(x: 0, y: -frame.size.height / 2) 17 | 18 | setupUI() 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { 22 | fatalError("init(coder:) has not been implemented") 23 | } 24 | 25 | private func setupUI() { 26 | backgroundColor = .clear 27 | 28 | let pinImage = UIImage(named: "GatheringLocation") 29 | let size = CGSize(width: 62, height: 70) 30 | UIGraphicsBeginImageContext(size) 31 | pinImage?.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) 32 | self.image = UIGraphicsGetImageFromCurrentImageContext() 33 | } 34 | 35 | } 36 | 37 | class GatheringAnnotation: NSObject, MKAnnotation { 38 | let coordinate: CLLocationCoordinate2D 39 | let title: String? 40 | let gatheringInfo: GatheringInfo? 41 | 42 | init(title: String, coordinate: CLLocationCoordinate2D, gatheringInfo: GatheringInfo) { 43 | self.title = title 44 | self.coordinate = coordinate 45 | self.gatheringInfo = gatheringInfo 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/MainMap/MainMapViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainMapViewController.swift 3 | // GetARock 4 | // 5 | // Created by Mijoo Kim on 2022/11/17. 6 | // 7 | 8 | import CoreLocation 9 | import MapKit 10 | import UIKit 11 | 12 | final class MainMapViewController: UIViewController { 13 | 14 | // MARK: - Properties 15 | 16 | @IBOutlet weak var locationLabel: UILabel! 17 | @IBOutlet weak var mapView: MKMapView! 18 | @IBOutlet weak var createEventButton: UIButton! 19 | @IBOutlet weak var attendedEventListButton: UIButton! 20 | @IBOutlet weak var myPageButton: UIButton! 21 | 22 | let coordinate = CLLocationCoordinate2D( 23 | latitude: 36.014, 24 | longitude: 129.32 25 | ) 26 | 27 | let coordinateRange = 0.03 28 | let locationManager = CLLocationManager() 29 | 30 | // MARK: - View Life Cycle 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | setMapView() 36 | addAnnotationOnMapView() 37 | self.locationManager.requestWhenInUseAuthorization() 38 | } 39 | 40 | // MARK: - Method 41 | 42 | private func setMapView() { 43 | mapView.delegate = self 44 | locationManager.delegate = self 45 | mapView.register(BandAnnotationView.self, 46 | forAnnotationViewWithReuseIdentifier: BandAnnotationView.className) 47 | mapView.register(GatheringAnnotationView.self, 48 | forAnnotationViewWithReuseIdentifier: GatheringAnnotationView.className) 49 | } 50 | 51 | private func addAnnotationOnMapView() { 52 | addBandAnnotationOnMapView() 53 | addGatheringAnnotationOnMapView() 54 | } 55 | 56 | private func addBandAnnotationOnMapView() { 57 | let points = MockData.bands.map { 58 | BandAnnotation( 59 | title: $0.band.name, 60 | coordinate: $0.band.location.coordinate.toCLLocationCoordinate2D(), 61 | bandInfo: $0 62 | ) 63 | } 64 | mapView.addAnnotations(points) 65 | } 66 | 67 | private func addGatheringAnnotationOnMapView() { 68 | let points = MockData.gatherings.map { 69 | GatheringAnnotation( 70 | title: $0.gathering.host.band.name, 71 | coordinate: $0.gathering.location.coordinate.toCLLocationCoordinate2D(), 72 | gatheringInfo: $0 73 | ) 74 | } 75 | mapView.addAnnotations(points) 76 | } 77 | 78 | private func setDefaultLocation() { 79 | mapView.setRegion( 80 | MKCoordinateRegion( 81 | center: coordinate, 82 | span: MKCoordinateSpan( 83 | latitudeDelta: coordinateRange, 84 | longitudeDelta: coordinateRange 85 | ) 86 | ), 87 | animated: true 88 | ) 89 | } 90 | 91 | private func centerUserLocation() { 92 | mapView.showsUserLocation = true 93 | mapView.setUserTrackingMode(.follow, animated: true) 94 | } 95 | 96 | private func requestLocationAuthorizationWhenUserDenied() { 97 | let alertController = UIAlertController( 98 | title: "설정에서 위치 정보 권한을 변경해주세요", 99 | message: "위치정보 제공을 허용하면 현재 위치를 기준으로 장소를 보여줄 수 있어요", 100 | preferredStyle: .alert 101 | ) 102 | 103 | let settingsAction = UIAlertAction(title: "설정", style: .default) { (_) -> Void in 104 | guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { 105 | return 106 | } 107 | if UIApplication.shared.canOpenURL(settingsUrl) { 108 | UIApplication.shared.open(settingsUrl, completionHandler: { (_) in 109 | }) 110 | } 111 | } 112 | alertController.addAction(settingsAction) 113 | let cancelAction = UIAlertAction(title: "취소", style: .default, handler: nil) 114 | alertController.addAction(cancelAction) 115 | 116 | present(alertController, animated: true, completion: nil) 117 | } 118 | 119 | @IBAction func moveToUserLocation(_ sender: Any) { 120 | switch locationManager.authorizationStatus { 121 | case .authorizedAlways, .authorizedWhenInUse: 122 | centerUserLocation() 123 | case .restricted, .denied: 124 | requestLocationAuthorizationWhenUserDenied() 125 | default: 126 | break 127 | } 128 | } 129 | 130 | } 131 | 132 | // MARK: - CLLocationManagerDelegate 133 | 134 | extension MainMapViewController: CLLocationManagerDelegate { 135 | 136 | func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { 137 | switch manager.authorizationStatus { 138 | case .authorizedAlways, .authorizedWhenInUse: 139 | centerUserLocation() 140 | manager.startUpdatingLocation() 141 | case .notDetermined: 142 | manager.requestWhenInUseAuthorization() 143 | default: 144 | setDefaultLocation() 145 | } 146 | } 147 | 148 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 149 | let currentLocation = locations.last 150 | 151 | CLGeocoder().reverseGeocodeLocation( 152 | currentLocation!, 153 | completionHandler: {(placemarks, _) -> Void in 154 | guard let currentPlacemark = placemarks?.first else { return } 155 | var address: String = "" 156 | if currentPlacemark.locality != nil { 157 | address += " " 158 | address += currentPlacemark.locality! 159 | } 160 | if currentPlacemark.thoroughfare != nil { 161 | address += " " 162 | address += currentPlacemark.thoroughfare! 163 | } 164 | 165 | self.locationLabel.text = address 166 | } 167 | ) 168 | } 169 | 170 | } 171 | 172 | // MARK: - MKMapViewDelegate 173 | 174 | extension MainMapViewController: MKMapViewDelegate { 175 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 176 | 177 | if annotation is MKUserLocation { 178 | return MKUserLocationView() 179 | } else if annotation is BandAnnotation { 180 | return mapView.dequeueReusableAnnotationView(withIdentifier: BandAnnotationView.className) 181 | } else if annotation is GatheringAnnotation { 182 | return mapView.dequeueReusableAnnotationView(withIdentifier: GatheringAnnotationView.className) 183 | } else { 184 | return nil 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /GetARock/GetARock/Screen/MyPage/MyPageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyPageViewController.swift 3 | // GetARock 4 | // 5 | // Created by 김수진 on 2022/11/01. 6 | // 7 | 8 | import UIKit 9 | 10 | class MyPageViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | /* 19 | // MARK: - Navigation 20 | 21 | // In a storyboard-based application, you will often want to do a little preparation before navigation 22 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 23 | // Get the new view controller using segue.destination. 24 | // Pass the selected object to the new view controller. 25 | } 26 | */ 27 | 28 | } 29 | -------------------------------------------------------------------------------- /GetARock/GetARockTests/GetARockTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetARockTests.swift 3 | // GetARockTests 4 | // 5 | // Created by 김수진 on 2022/10/31. 6 | // 7 | 8 | import XCTest 9 | @testable import GetARock 10 | 11 | class GetARockTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /GetARock/GetARockUITests/GetARockUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetARockUITests.swift 3 | // GetARockUITests 4 | // 5 | // Created by 김수진 on 2022/10/31. 6 | // 7 | 8 | import XCTest 9 | 10 | class GetARockUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /GetARock/GetARockUITests/GetARockUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GetARockUITestsLaunchTests.swift 3 | // GetARockUITests 4 | // 5 | // Created by 김수진 on 2022/10/31. 6 | // 7 | 8 | import XCTest 9 | 10 | class GetARockUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | image 4 | 5 |

6 | 7 |
8 | 9 | 15 | 16 |
17 | 18 | ### :sparkles: Skills & Tech Stack 19 | * UIKit 20 | * MapKit 21 | * Storyboard + Code base 22 | * Github 23 | * Github Action 24 | * SwiftLint 25 | 26 |
27 | 28 | ### 🛠 Development Environment 29 | 30 | ![Generic badge](https://img.shields.io/badge/iOS-15.0+-lightgrey.svg) ![Generic badge](https://img.shields.io/badge/Xcode-14.1-blue.svg) 31 | 32 |
33 | 34 | ### 🔀 Git branch & Git Flow 35 | 36 | ``` 37 | develop(default) 38 | 39 | feature/47-get-user-location 40 | 41 | release/v1.0.0 42 | 43 | hotfix/71-update-to-adapt-color-extension 44 | ``` 45 |
46 | 47 | ### 🗂 Folder Structure 48 | ``` 49 | GetARock 50 | │ 51 | ├── Network 52 | │ ├── Mock 53 | │ └── Model 54 | │ 55 | ├── Global 56 | │ ├── Base 57 | │ ├── Supports 58 | │ │ ├── AppDelegate 59 | │ │ └── Info.plist 60 | │ ├── Literals 61 | │ ├── Extension 62 | │ ├── Util 63 | │ ├── UIComponent 64 | │ └── Resource 65 | │ ├── Assets.xcassets 66 | │ ├── Storyboards 67 | │ └── Xibs 68 | │ 69 | └── Screen 70 | ├── MainMap 71 | └── BandInfo 72 | ├── Component 73 | ├── VC 74 | └── Cell 75 | 76 | ``` 77 | 78 |
79 | 80 | 81 | ### 🧑‍💻 Authors 82 | 83 |
84 | 85 | | [알로라](https://github.com/compuTasha) | [노엘](https://github.com/GODNOEL) | [시로](https://github.com/siro96-01) | [태니](https://github.com/taehyeonk) | [머피](https://github.com/Somin-DS) | [이브](https://github.com/unuhqueen) | [스누피](https://github.com/skycat0212) | [유니스](https://github.com/EuniceNam) | 86 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| 87 | |알로라|노엘|시로|태니|머피|이브|스누피|유니스| 88 | 89 | 90 |
91 | 92 |
93 | 94 | ### :lock_with_ink_pen: License 95 | 96 | [MIT](https://choosealicense.com/licenses/mit/) 97 | --------------------------------------------------------------------------------