├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── FogFog-iOS ├── .swiftlint.yml ├── FogFog-iOS.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── FogFog-iOS.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── FogFog-iOS │ ├── App │ │ ├── AppDelegate.swift │ │ └── SceneDelegate.swift │ ├── FogFog-iOS.entitlements │ ├── Manager │ │ └── AppleLogin │ │ │ ├── ASAuthorizationControllerProxy.swift │ │ │ └── AppleLoginManager.swift │ ├── Models │ │ ├── BottomView │ │ │ └── BottomViewType.swift │ │ ├── Map │ │ │ ├── Coordinates.swift │ │ │ └── SmokingAreaEntity.swift │ │ ├── Message │ │ │ └── MessageModel.swift │ │ ├── Place │ │ │ └── SmokingAreaResponseModel.swift │ │ └── User │ │ │ └── AppleUserModel.swift │ ├── Networking │ │ ├── APIServices │ │ │ ├── AuthAPIService.swift │ │ │ ├── MapAPIService.swift │ │ │ ├── QuitAPIService.swift │ │ │ ├── ReissueAPIService.swift │ │ │ └── UserAPIService.swift │ │ ├── APIs │ │ │ ├── AuthAPI.swift │ │ │ ├── FogAPI.swift │ │ │ ├── MapAPI.swift │ │ │ └── UserAPI.swift │ │ ├── Foundation │ │ │ ├── AuthInterceptor.swift │ │ │ ├── NetworkEnv.swift │ │ │ ├── NetworkError.swift │ │ │ └── Networking.swift │ │ ├── Models │ │ │ ├── CommonResponse.swift │ │ │ ├── EmptyData.swift │ │ │ └── NicknameResponseModel.swift │ │ └── Monitoring │ │ │ ├── NetworkConnectionView.swift │ │ │ └── NetworkMonitor.swift │ ├── OAuth │ │ ├── AppleOAuthService.swift │ │ ├── KakaoOAuthService.swift │ │ ├── OAuthAuthentication.swift │ │ ├── OAuthProviderType.swift │ │ ├── OAuthService.swift │ │ └── OAuthServiceType.swift │ ├── Presentation │ │ ├── Common │ │ │ ├── Coordinator │ │ │ │ ├── DefaultAppCoordinator.swift │ │ │ │ ├── Delegate │ │ │ │ │ └── CoordinatorFinishDelegate.swift │ │ │ │ └── Protocol │ │ │ │ │ ├── AppCoordinator.swift │ │ │ │ │ └── Coordinator.swift │ │ │ └── Protocol │ │ │ │ └── ViewModelType.swift │ │ ├── ExternalMap │ │ │ ├── ExternalMapModalView.swift │ │ │ ├── ExternalMapModalViewController.swift │ │ │ ├── ExternalMapModalViewModel.swift │ │ │ └── ExternalMapType.swift │ │ ├── Login │ │ │ ├── Coordinator │ │ │ │ ├── DefaultLoginCoordinator.swift │ │ │ │ └── Protocol │ │ │ │ │ └── LoginCoordinator.swift │ │ │ ├── MakeNicknameViewType.swift │ │ │ ├── ViewController │ │ │ │ ├── LoginViewController.swift │ │ │ │ └── MakeNicknameViewController.swift │ │ │ └── ViewModel │ │ │ │ ├── LoginViewModel.swift │ │ │ │ └── MakeNicknameViewModel.swift │ │ ├── Map │ │ │ ├── Coordinator │ │ │ │ ├── DefaultMapCoordinator.swift │ │ │ │ └── Protocol │ │ │ │ │ └── MapCoordinator.swift │ │ │ ├── DefaultLoactionService.swift │ │ │ ├── LocationService.swift │ │ │ ├── MapViewController.swift │ │ │ ├── MapViewModel.swift │ │ │ └── NavigationView.swift │ │ ├── Setting │ │ │ ├── Cell │ │ │ │ ├── SettingListTableViewCell.swift │ │ │ │ ├── SettingNicknameTableViewCell.swift │ │ │ │ └── SettingTitleTableViewCell.swift │ │ │ ├── Coordinator │ │ │ │ ├── DefaultSettingCoordinator.swift │ │ │ │ └── Protocol │ │ │ │ │ └── SettingCoordinator.swift │ │ │ ├── ViewController │ │ │ │ └── SettingViewController.swift │ │ │ └── ViewModel │ │ │ │ └── SettingViewModel.swift │ │ ├── SideBar │ │ │ └── SideBarView.swift │ │ ├── SmokingArea │ │ │ ├── Protocol │ │ │ │ ├── Contents.swift │ │ │ │ └── Presentable.swift │ │ │ ├── View │ │ │ │ ├── SmokingAreaCardView.swift │ │ │ │ └── SmokingAreaMessageView.swift │ │ │ └── ViewModel │ │ │ │ └── SmokingAreaDetailViewModel.swift │ │ └── Splash │ │ │ └── View │ │ │ └── SplashViewController.swift │ ├── Resources │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── Colors │ │ │ └── Color.xcassets │ │ │ │ └── Contents.json │ │ ├── Fonts │ │ │ ├── .gitkeep │ │ │ ├── FogFont.swift │ │ │ ├── Pretendard-Black.otf │ │ │ ├── Pretendard-Bold.otf │ │ │ ├── Pretendard-ExtraBold.otf │ │ │ ├── Pretendard-ExtraLight.otf │ │ │ ├── Pretendard-Light.otf │ │ │ ├── Pretendard-Medium.otf │ │ │ ├── Pretendard-Regular.otf │ │ │ ├── Pretendard-SemiBold.otf │ │ │ └── Pretendard-Thin.otf │ │ └── Image │ │ │ ├── Asset.xcassets │ │ │ ├── AccentColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ │ ├── FogImage.swift │ │ │ └── Image.xcassets │ │ │ ├── Contents.json │ │ │ ├── appleLogo.imageset │ │ │ ├── Contents.json │ │ │ ├── appleLogo.png │ │ │ ├── appleLogo@2x.png │ │ │ └── appleLogo@3x.png │ │ │ ├── btnBack.imageset │ │ │ ├── Contents.json │ │ │ ├── btnBack.png │ │ │ ├── btnBack@2x.png │ │ │ └── btnBack@3x.png │ │ │ ├── btnCheck.imageset │ │ │ ├── Contents.json │ │ │ ├── btnCheck.png │ │ │ ├── btnCheck@2x.png │ │ │ └── btnCheck@3x.png │ │ │ ├── btnMap.imageset │ │ │ ├── Contents.json │ │ │ ├── btnMap.png │ │ │ ├── btnMap@2x.png │ │ │ └── btnMap@3x.png │ │ │ ├── btnNotice.imageset │ │ │ ├── Contents.json │ │ │ ├── btnNotice.png │ │ │ ├── btnNotice@2x.png │ │ │ └── btnNotice@3x.png │ │ │ ├── btnPen.imageset │ │ │ ├── Contents.json │ │ │ ├── btnPen.png │ │ │ ├── btnPen@2x.png │ │ │ └── btnPen@3x.png │ │ │ ├── btnSet.imageset │ │ │ ├── Contents.json │ │ │ ├── btnSet.png │ │ │ ├── btnSet@2x.png │ │ │ └── btnSet@3x.png │ │ │ ├── btnX.imageset │ │ │ ├── Contents.json │ │ │ ├── btnX.png │ │ │ ├── btnX@2x.png │ │ │ └── btnX@3x.png │ │ │ ├── fluentLocation16Filled.imageset │ │ │ ├── Contents.json │ │ │ ├── fluentLocation16Filled.png │ │ │ ├── fluentLocation16Filled@2x.png │ │ │ └── fluentLocation16Filled@3x.png │ │ │ ├── fogfog_logo.imageset │ │ │ ├── Contents.json │ │ │ ├── fogfog_logo.png │ │ │ ├── fogfog_logo@2x.png │ │ │ └── fogfog_logo@3x.png │ │ │ ├── ic_ham.imageset │ │ │ ├── Contents.json │ │ │ ├── ic_ham.png │ │ │ ├── ic_ham@2x.png │ │ │ └── ic_ham@3x.png │ │ │ ├── ic_location.imageset │ │ │ ├── Contents.json │ │ │ ├── loca.png │ │ │ ├── loca@2x.png │ │ │ └── loca@3x.png │ │ │ ├── ic_minus.imageset │ │ │ ├── Contents.json │ │ │ ├── ic_minus.png │ │ │ ├── ic_minus@2x.png │ │ │ └── ic_minus@3x.png │ │ │ ├── ic_plus.imageset │ │ │ ├── Contents.json │ │ │ ├── ic_plus.png │ │ │ ├── ic_plus@2x.png │ │ │ └── ic_plus@3x.png │ │ │ ├── ic_siren.imageset │ │ │ ├── Contents.json │ │ │ ├── ph_siren.png │ │ │ ├── ph_siren@2x.png │ │ │ └── ph_siren@3x.png │ │ │ ├── iconDeletetext.imageset │ │ │ ├── Contents.json │ │ │ ├── iconDeletetext.png │ │ │ ├── iconDeletetext@2x.png │ │ │ └── iconDeletetext@3x.png │ │ │ ├── iconPopupMap.imageset │ │ │ ├── Contents.json │ │ │ ├── map.png │ │ │ ├── map@2x.png │ │ │ └── map@3x.png │ │ │ ├── inputTextIocnNotice.imageset │ │ │ ├── Contents.json │ │ │ ├── inputTextIocnNotice.png │ │ │ ├── inputTextIocnNotice@2x.png │ │ │ └── inputTextIocnNotice@3x.png │ │ │ ├── invalidName.imageset │ │ │ ├── Contents.json │ │ │ ├── invalidName.png │ │ │ ├── invalidName@2x.png │ │ │ └── invalidName@3x.png │ │ │ ├── kakaoLogol.imageset │ │ │ ├── Contents.json │ │ │ ├── kakaoLogol.png │ │ │ ├── kakaoLogol@2x.png │ │ │ └── kakaoLogol@3x.png │ │ │ ├── loca_inactive.imageset │ │ │ ├── Contents.json │ │ │ ├── loca_inactive.png │ │ │ ├── loca_inactive@2x.png │ │ │ └── loca_inactive@3x.png │ │ │ ├── mapImage.imageset │ │ │ ├── Contents.json │ │ │ ├── mapImage.png │ │ │ ├── mapImage@2x.png │ │ │ └── mapImage@3x.png │ │ │ ├── pinActive.imageset │ │ │ ├── Contents.json │ │ │ ├── pinActive.png │ │ │ ├── pinActive@2x.png │ │ │ └── pinActive@3x.png │ │ │ ├── speechBubbleBig.imageset │ │ │ ├── Contents.json │ │ │ ├── speech bubble_big.png │ │ │ ├── speech bubble_big@2x.png │ │ │ └── speech bubble_big@3x.png │ │ │ ├── speechBubbleSmall.imageset │ │ │ ├── Contents.json │ │ │ ├── speechBubbleSmall.png │ │ │ ├── speechBubbleSmall@2x.png │ │ │ └── speechBubbleSmall@3x.png │ │ │ └── wordmark.imageset │ │ │ ├── Contents.json │ │ │ ├── wordmark.png │ │ │ ├── wordmark@2x.png │ │ │ └── wordmark@3x.png │ ├── Supports │ │ └── Info.plist │ └── Utils │ │ ├── Analytics │ │ └── .gitkeep │ │ ├── Base │ │ ├── BaseNavigationController.swift │ │ ├── BaseTableViewCell.swift │ │ ├── BaseView.swift │ │ └── BaseViewController.swift │ │ ├── Class │ │ └── Keychain.swift │ │ ├── Contstant │ │ ├── Config.swift │ │ ├── CoordinatorCase.swift │ │ ├── NotificationCenterKey.swift │ │ └── UserDefaultsKey.swift │ │ ├── Extension │ │ ├── .gitkeep │ │ ├── Adjusted + Extension.swift │ │ ├── Bundle + Extension.swift │ │ ├── Keychain + Extension.swift │ │ ├── Reactive + Extension.swift │ │ ├── RxMoya + Extension.swift │ │ ├── UIColor + Extension.swift │ │ ├── UIFont + Extension.swift │ │ ├── UIImage + Extension.swift │ │ ├── UILabel + Extension.swift │ │ ├── UITextField + Extension.swift │ │ ├── UIView + Extension.swift │ │ ├── UIViewController + Extension.swift │ │ └── UserDefaults + Extension.swift │ │ ├── Logging │ │ ├── .gitkeep │ │ └── Logger.swift │ │ ├── UIComponents │ │ ├── FogButton │ │ │ ├── FogButton.swift │ │ │ └── FogButtonStyle.swift │ │ ├── FogNavigationView.swift │ │ ├── FogTextField.swift │ │ └── FogToast │ │ │ └── FogToast.swift │ │ └── Wrapper │ │ ├── Published+Rx.swift │ │ └── UserDefault.swift ├── FogFog-iOSTests │ └── FogFog_iOSTests.swift ├── FogFog-iOSUITests │ ├── FogFog_iOSUITests.swift │ └── FogFog_iOSUITestsLaunchTests.swift ├── Podfile └── Podfile.lock └── README.md /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 버그 및 수정사항을 알려주세요. 4 | title: "[Fix] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 🚨 버그 설명 11 | 스크린 샷, 작동 환경 (OS, device 등)을 적어주세요. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: 새로운 기능을 추가합니다. 4 | title: "[Feat] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 🚬 Feature Issue 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 🔍 What is this PR? 2 | 3 | 4 | ## 📝 Changes 5 | 6 | 7 | ## 📸 Screenshot 8 | 9 | 10 | 11 | | 구현 내용 | 스크린샷 | 12 | | :-------------: | :----------: | 13 | 14 | ## ☑️ Test Checklist 15 | 16 | ## 📮 관련 이슈 17 | 18 | 19 | 20 | - Resolved: #1 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Created by https://www.toptal.com/developers/gitignore/api/swift,xcode,macos,cocoapods 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift,xcode,macos,cocoapods 3 | 4 | ### CocoaPods ### 5 | ## CocoaPods GitIgnore Template 6 | 7 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 8 | # - Also handy if you have a large number of dependant pods 9 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 10 | Pods/ 11 | 12 | ### macOS ### 13 | # General 14 | .DS_Store 15 | .AppleDouble 16 | .LSOverride 17 | 18 | # Icon must end with two \r 19 | Icon 20 | 21 | 22 | # Thumbnails 23 | ._* 24 | 25 | # Files that might appear in the root of a volume 26 | .DocumentRevisions-V100 27 | .fseventsd 28 | .Spotlight-V100 29 | .TemporaryItems 30 | .Trashes 31 | .VolumeIcon.icns 32 | .com.apple.timemachine.donotpresent 33 | 34 | # Directories potentially created on remote AFP share 35 | .AppleDB 36 | .AppleDesktop 37 | Network Trash Folder 38 | Temporary Items 39 | .apdisk 40 | 41 | ### Swift ### 42 | # Xcode 43 | # 44 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 45 | GoogleMap.plist 46 | 47 | ## User settings 48 | xcuserdata/ 49 | 50 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 51 | *.xcscmblueprint 52 | *.xccheckout 53 | 54 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 55 | build/ 56 | DerivedData/ 57 | *.moved-aside 58 | *.pbxuser 59 | !default.pbxuser 60 | *.mode1v3 61 | !default.mode1v3 62 | *.mode2v3 63 | !default.mode2v3 64 | *.perspectivev3 65 | !default.perspectivev3 66 | 67 | ## Obj-C/Swift specific 68 | *.hmap 69 | 70 | ## App packaging 71 | *.ipa 72 | *.dSYM.zip 73 | *.dSYM 74 | 75 | ## Playgrounds 76 | timeline.xctimeline 77 | playground.xcworkspace 78 | 79 | # Swift Package Manager 80 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 81 | # Packages/ 82 | # Package.pins 83 | # Package.resolved 84 | # *.xcodeproj 85 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 86 | # hence it is not needed unless you have added a package configuration file to your project 87 | # .swiftpm 88 | 89 | .build/ 90 | 91 | # CocoaPods 92 | # We recommend against adding the Pods directory to your .gitignore. However 93 | # you should judge for yourself, the pros and cons are mentioned at: 94 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 95 | # Pods/ 96 | # Add this line if you want to avoid checking in source code from the Xcode workspace 97 | # *.xcworkspace 98 | 99 | # Carthage 100 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 101 | # Carthage/Checkouts 102 | 103 | Carthage/Build/ 104 | 105 | # Accio dependency management 106 | Dependencies/ 107 | .accio/ 108 | 109 | # fastlane 110 | # It is recommended to not store the screenshots in the git repo. 111 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 112 | # For more information about the recommended setup visit: 113 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 114 | 115 | fastlane/report.xml 116 | fastlane/Preview.html 117 | fastlane/screenshots/**/*.png 118 | fastlane/test_output 119 | 120 | # Code Injection 121 | # After new code Injection tools there's a generated folder /iOSInjectionProject 122 | # https://github.com/johnno1962/injectionforxcode 123 | 124 | iOSInjectionProject/ 125 | 126 | ### Xcode ### 127 | # Xcode 128 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 129 | 130 | 131 | 132 | 133 | ## Gcc Patch 134 | /*.gcno 135 | 136 | ### Xcode Patch ### 137 | *.xcodeproj/* 138 | !*.xcodeproj/project.pbxproj 139 | !*.xcodeproj/xcshareddata/ 140 | !*.xcworkspace/contents.xcworkspacedata 141 | **/xcshareddata/WorkspaceSettings.xcsettings 142 | 143 | # End of https://www.toptal.com/developers/gitignore/api/swift,xcode,macos,cocoapods 144 | 145 | ## Config 146 | *.xcconfig -------------------------------------------------------------------------------- /FogFog-iOS/.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 | excluded: 12 | - FogFog-iOS/App/AppDelegate.swift 13 | - FogFog-iOS/App/SceneDelegate.swift 14 | - FogFog-iOSTests 15 | - FogFog-iOSUITests 16 | - Pods 17 | 18 | file_length: 19 | warning: 400 20 | error: 500 21 | 22 | type_name: 23 | min_length: 2 24 | 25 | identifier_name: 26 | min_length: 2 27 | 28 | force_cast: warning 29 | 30 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "alamofire", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/Alamofire/Alamofire.git", 7 | "state" : { 8 | "revision" : "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", 9 | "version" : "5.6.2" 10 | } 11 | }, 12 | { 13 | "identity" : "kakao-ios-sdk", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/kakao/kakao-ios-sdk.git", 16 | "state" : { 17 | "revision" : "d788f9a5a888f16253b9c3d9f3bd794835708c3f", 18 | "version" : "2.15.0" 19 | } 20 | }, 21 | { 22 | "identity" : "kakao-ios-sdk-rx", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/kakao/kakao-ios-sdk-rx", 25 | "state" : { 26 | "revision" : "ba12deed6bf97f6ac275cc1257fe7dd411e96afc", 27 | "version" : "2.15.0" 28 | } 29 | }, 30 | { 31 | "identity" : "kingfisher", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/onevcat/Kingfisher.git", 34 | "state" : { 35 | "revision" : "44e891bdb61426a95e31492a67c7c0dfad1f87c5", 36 | "version" : "7.4.1" 37 | } 38 | }, 39 | { 40 | "identity" : "moya", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/Moya/Moya.git", 43 | "state" : { 44 | "branch" : "master", 45 | "revision" : "77e67c0b25ebd785c6fe56cad544885f67c8c2c4" 46 | } 47 | }, 48 | { 49 | "identity" : "ohhttpstubs", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", 52 | "state" : { 53 | "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", 54 | "version" : "9.1.0" 55 | } 56 | }, 57 | { 58 | "identity" : "reactiveswift", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git", 61 | "state" : { 62 | "revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c", 63 | "version" : "6.7.0" 64 | } 65 | }, 66 | { 67 | "identity" : "rxalamofire", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/RxSwiftCommunity/RxAlamofire.git", 70 | "state" : { 71 | "revision" : "9535b58695b91fb67f56d58d6fd0c76462d7743a", 72 | "version" : "6.1.2" 73 | } 74 | }, 75 | { 76 | "identity" : "rxgesture", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/RxSwiftCommunity/RxGesture.git", 79 | "state" : { 80 | "revision" : "1b137c576b4aaaab949235752278956697c9e4a0", 81 | "version" : "4.0.4" 82 | } 83 | }, 84 | { 85 | "identity" : "rxkeyboard", 86 | "kind" : "remoteSourceControl", 87 | "location" : "https://github.com/RxSwiftCommunity/RxKeyboard.git", 88 | "state" : { 89 | "revision" : "63f6377975c962a1d89f012a6f1e5bebb2c502b7", 90 | "version" : "2.0.1" 91 | } 92 | }, 93 | { 94 | "identity" : "rxswift", 95 | "kind" : "remoteSourceControl", 96 | "location" : "https://github.com/ReactiveX/RxSwift.git", 97 | "state" : { 98 | "revision" : "b4307ba0b6425c0ba4178e138799946c3da594f8", 99 | "version" : "6.5.0" 100 | } 101 | }, 102 | { 103 | "identity" : "snapkit", 104 | "kind" : "remoteSourceControl", 105 | "location" : "https://github.com/SnapKit/SnapKit.git", 106 | "state" : { 107 | "revision" : "f222cbdf325885926566172f6f5f06af95473158", 108 | "version" : "5.6.0" 109 | } 110 | }, 111 | { 112 | "identity" : "then", 113 | "kind" : "remoteSourceControl", 114 | "location" : "https://github.com/devxoul/Then.git", 115 | "state" : { 116 | "revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a", 117 | "version" : "3.0.0" 118 | } 119 | } 120 | ], 121 | "version" : 2 122 | } 123 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "alamofire", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/Alamofire/Alamofire.git", 7 | "state" : { 8 | "revision" : "8dd85aee02e39dd280c75eef88ffdb86eed4b07b", 9 | "version" : "5.6.2" 10 | } 11 | }, 12 | { 13 | "identity" : "flexlayout", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/layoutBox/FlexLayout.git", 16 | "state" : { 17 | "revision" : "f3187749d2d31a01aa18f8b107e603e210f9bce9", 18 | "version" : "1.3.33" 19 | } 20 | }, 21 | { 22 | "identity" : "kakao-ios-sdk", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/kakao/kakao-ios-sdk.git", 25 | "state" : { 26 | "revision" : "d788f9a5a888f16253b9c3d9f3bd794835708c3f", 27 | "version" : "2.15.0" 28 | } 29 | }, 30 | { 31 | "identity" : "kakao-ios-sdk-rx", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/kakao/kakao-ios-sdk-rx", 34 | "state" : { 35 | "revision" : "ba12deed6bf97f6ac275cc1257fe7dd411e96afc", 36 | "version" : "2.15.0" 37 | } 38 | }, 39 | { 40 | "identity" : "kingfisher", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/onevcat/Kingfisher.git", 43 | "state" : { 44 | "revision" : "af4be924ad984cf4d16f4ae4df424e79a443d435", 45 | "version" : "7.6.2" 46 | } 47 | }, 48 | { 49 | "identity" : "moya", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/Moya/Moya.git", 52 | "state" : { 53 | "branch" : "master", 54 | "revision" : "77e67c0b25ebd785c6fe56cad544885f67c8c2c4" 55 | } 56 | }, 57 | { 58 | "identity" : "ohhttpstubs", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", 61 | "state" : { 62 | "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", 63 | "version" : "9.1.0" 64 | } 65 | }, 66 | { 67 | "identity" : "pinlayout", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/layoutBox/PinLayout.git", 70 | "state" : { 71 | "revision" : "ce929a6ecdc6701adee9bdf9d949e0036f567ca7", 72 | "version" : "1.10.4" 73 | } 74 | }, 75 | { 76 | "identity" : "reactiveswift", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git", 79 | "state" : { 80 | "revision" : "c43bae3dac73fdd3cb906bd5a1914686ca71ed3c", 81 | "version" : "6.7.0" 82 | } 83 | }, 84 | { 85 | "identity" : "rxalamofire", 86 | "kind" : "remoteSourceControl", 87 | "location" : "https://github.com/RxSwiftCommunity/RxAlamofire.git", 88 | "state" : { 89 | "revision" : "9535b58695b91fb67f56d58d6fd0c76462d7743a", 90 | "version" : "6.1.2" 91 | } 92 | }, 93 | { 94 | "identity" : "rxgesture", 95 | "kind" : "remoteSourceControl", 96 | "location" : "https://github.com/RxSwiftCommunity/RxGesture.git", 97 | "state" : { 98 | "revision" : "1b137c576b4aaaab949235752278956697c9e4a0", 99 | "version" : "4.0.4" 100 | } 101 | }, 102 | { 103 | "identity" : "rxkeyboard", 104 | "kind" : "remoteSourceControl", 105 | "location" : "https://github.com/RxSwiftCommunity/RxKeyboard.git", 106 | "state" : { 107 | "revision" : "085fd99d7bb4f98e671904fcfe7ff561e66574ad", 108 | "version" : "2.0.0" 109 | } 110 | }, 111 | { 112 | "identity" : "rxswift", 113 | "kind" : "remoteSourceControl", 114 | "location" : "https://github.com/ReactiveX/RxSwift.git", 115 | "state" : { 116 | "revision" : "b4307ba0b6425c0ba4178e138799946c3da594f8", 117 | "version" : "6.5.0" 118 | } 119 | }, 120 | { 121 | "identity" : "snapkit", 122 | "kind" : "remoteSourceControl", 123 | "location" : "https://github.com/SnapKit/SnapKit.git", 124 | "state" : { 125 | "revision" : "f222cbdf325885926566172f6f5f06af95473158", 126 | "version" : "5.6.0" 127 | } 128 | }, 129 | { 130 | "identity" : "then", 131 | "kind" : "remoteSourceControl", 132 | "location" : "https://github.com/devxoul/Then.git", 133 | "state" : { 134 | "revision" : "d41ef523faef0f911369f79c0b96815d9dbb6d7a", 135 | "version" : "3.0.0" 136 | } 137 | } 138 | ], 139 | "version" : 2 140 | } 141 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/14. 6 | // 7 | 8 | import UIKit 9 | 10 | import GoogleMaps 11 | import RxKakaoSDKAuth 12 | import KakaoSDKAuth 13 | import RxKakaoSDKCommon 14 | import KakaoSDKCommon 15 | 16 | @main 17 | class AppDelegate: UIResponder, UIApplicationDelegate { 18 | 19 | func application(_ application: UIApplication, 20 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 21 | 22 | // MARK: 구글 맵 설정 23 | GMSServices.provideAPIKey(Bundle.main.apiKey) 24 | 25 | // MARK: 카카오 SDK 초기화 26 | RxKakaoSDK.initSDK(appKey: Config.kakaoNativeAppKey) 27 | 28 | // MARK: 키체인 정보 초기화 29 | resetKeychainAtFirstLaunch() 30 | 31 | return true 32 | } 33 | 34 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 35 | if (AuthApi.isKakaoTalkLoginUrl(url)) { 36 | return AuthController.rx.handleOpenUrl(url: url) 37 | } 38 | 39 | return false 40 | } 41 | 42 | // 첫 실행 시 키체인 정보 초기화하는 메서드 43 | private func resetKeychainAtFirstLaunch() { 44 | let quitApiService = QuitAPIService() 45 | 46 | if UserDefaults.isFirstLaunch { 47 | quitApiService.removeAllKeychainKeys() 48 | } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/14. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxSwift 11 | import RxKakaoSDKAuth 12 | import KakaoSDKAuth 13 | 14 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 15 | 16 | var window: UIWindow? 17 | var appCoordinator: AppCoordinator? 18 | var networkMonitor: NetworkMonitor? 19 | var networkWindow: UIWindow? 20 | 21 | private var disposeBag = DisposeBag() 22 | 23 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 24 | guard let windowScene = (scene as? UIWindowScene) else { return } 25 | let navigationController = BaseNavigationController() 26 | self.appCoordinator = DefaultAppCoordinator(navigationController) 27 | self.appCoordinator?.start() 28 | self.window = UIWindow(windowScene: windowScene) 29 | self.window?.rootViewController = navigationController 30 | self.window?.makeKeyAndVisible() 31 | 32 | networkMonitor = NetworkMonitor() 33 | networkMonitor?.$status 34 | .subscribe(onNext: { [weak self] status in 35 | switch status { 36 | case .satisfied: 37 | self?.removeNetworkConnectionView() 38 | 39 | case .unsatisfied: 40 | self?.loadNetworkConnectionView(on: scene) 41 | 42 | default: 43 | break 44 | } 45 | }) 46 | .disposed(by: disposeBag) 47 | } 48 | 49 | func sceneDidDisconnect(_ scene: UIScene) { 50 | networkMonitor?.stopPathMonitor() 51 | } 52 | 53 | private func loadNetworkConnectionView(on scene: UIScene) { 54 | if let windowScene = scene as? UIWindowScene { 55 | let window = UIWindow(windowScene: windowScene) 56 | window.windowLevel = .statusBar 57 | window.makeKeyAndVisible() 58 | 59 | let networkConnectionView = NetworkConnectionView(frame: window.bounds) 60 | window.addSubview(networkConnectionView) 61 | self.networkWindow = window 62 | } 63 | } 64 | 65 | private func removeNetworkConnectionView() { 66 | networkWindow?.resignKey() 67 | networkWindow?.isHidden = true 68 | networkWindow = nil 69 | } 70 | 71 | func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { 72 | if let url = URLContexts.first?.url { 73 | if (AuthApi.isKakaoTalkLoginUrl(url)) { 74 | _ = AuthController.rx.handleOpenUrl(url: url) 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/FogFog-iOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.applesignin 6 | 7 | Default 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Manager/AppleLogin/ASAuthorizationControllerProxy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ASAuthorizationControllerProxy.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/07/07. 6 | // 7 | 8 | import UIKit 9 | 10 | import AuthenticationServices 11 | import RxCocoa 12 | import RxSwift 13 | 14 | extension ASAuthorizationController: HasDelegate { 15 | public typealias Delegate = ASAuthorizationControllerDelegate 16 | } 17 | 18 | class ASAuthorizationControllerProxy: DelegateProxy, DelegateProxyType, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { 19 | var presentationWindow: UIWindow = UIWindow() 20 | 21 | public init(controller: ASAuthorizationController) { 22 | super.init(parentObject: controller, delegateProxy: ASAuthorizationControllerProxy.self) 23 | } 24 | 25 | // MARK: DelegateProxyType 26 | public static func registerKnownImplementations() { 27 | register { ASAuthorizationControllerProxy(controller: $0) } 28 | } 29 | 30 | // MARK: Proxy Subject 31 | lazy var didComplete = PublishSubject() 32 | 33 | // MARK: ASAuthorizationControllerDelegate 34 | func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { 35 | didComplete.onNext(authorization) 36 | didComplete.onCompleted() 37 | } 38 | 39 | func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { 40 | didComplete.onCompleted() 41 | } 42 | 43 | // MARK: ASAuthorizationControllerPresentationContextProviding 44 | func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { 45 | return presentationWindow 46 | } 47 | 48 | // MARK: Completed 49 | deinit { 50 | self.didComplete.onCompleted() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Manager/AppleLogin/AppleLoginManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleLoginManager.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/02/07. 6 | // 7 | 8 | import AuthenticationServices 9 | import RxCocoa 10 | import RxSwift 11 | 12 | /// 애플 로그인 매니저 13 | final class AppleLoginManager: NSObject { 14 | 15 | override init() { 16 | super.init() 17 | } 18 | 19 | // 로그인 버튼 클릭 시 로직 수행 (request를 보내줄 controller를 생성) 20 | func handleAuthorizationAppleIDButtonPress() -> Observable { 21 | let appleIDProvider = ASAuthorizationAppleIDProvider() 22 | let request = appleIDProvider.createRequest() 23 | request.requestedScopes = [.fullName, .email] 24 | 25 | let authorizationController = ASAuthorizationController(authorizationRequests: [request]) 26 | let proxy = ASAuthorizationControllerProxy.proxy(for: authorizationController) 27 | 28 | authorizationController.presentationContextProvider = proxy 29 | authorizationController.performRequests() 30 | 31 | return proxy.didComplete 32 | } 33 | 34 | // 애플 로그인 상태 확인 35 | static func getCredentialState(completion: ((ASAuthorizationAppleIDProvider.CredentialState) -> Void)? = nil) { 36 | let appleIDProvider = ASAuthorizationAppleIDProvider() 37 | 38 | // TODO: 이후에 UserID 가져와서 체크 39 | appleIDProvider.getCredentialState(forUserID: "") { (credentialState, error) in 40 | switch credentialState { 41 | case .authorized: 42 | print("The Apple ID credential is valid.") 43 | break 44 | 45 | case .revoked: 46 | print("The Apple ID credential is revoked. need logout progress.") 47 | completion?(credentialState) 48 | break 49 | 50 | case .notFound: 51 | print("User identifier value is wrong or Apple login system has a problem.") 52 | completion?(credentialState) 53 | break 54 | default: 55 | break 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Models/BottomView/BottomViewType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BottomViewType.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/04/20. 6 | // 7 | 8 | import Foundation 9 | 10 | enum BottomViewType { 11 | case cardView 12 | case messageView 13 | 14 | var view: any Presentable { 15 | switch self { 16 | case .cardView: 17 | return SmokingAreaCardView() 18 | 19 | case .messageView: 20 | return SmokingAreaMessageView() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Models/Map/Coordinates.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinates.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/09/13. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Coordinates: Codable { 11 | let lat: CGFloat 12 | let long: CGFloat 13 | } 14 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Models/Map/SmokingAreaEntity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmokingAreaResponse.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/09/13. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SmokingAreaResponseEntity: Decodable { 11 | let total: Int 12 | let areas: [SmokingAreaEntity] 13 | 14 | struct SmokingAreaEntity: Decodable { 15 | let id: Int 16 | let latitude: CGFloat 17 | let longitude: CGFloat 18 | } 19 | } 20 | 21 | struct SmokingAreaDetailResponseEntity: Decodable { 22 | let name: String 23 | let address: String 24 | let image: String 25 | let distance: String 26 | } 27 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Models/Message/MessageModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageModel.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/04/20. 6 | // 7 | 8 | import Foundation 9 | 10 | // 토스트 메시지에 사용할 데이터 11 | struct MessageModel { 12 | let contents: String 13 | } 14 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Models/Place/SmokingAreaResponseModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmokingAreaResponseModel.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2022/11/20. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SmokingAreaResponseModel: Decodable { 11 | let name: String 12 | let address: String 13 | let imageURLString: String 14 | let distance: String 15 | 16 | enum CodingKeys: String, CodingKey { 17 | case name 18 | case address 19 | case imageURLString = "image" 20 | case distance 21 | } 22 | 23 | init(name: String, address: String, imageURLString: String, distance: String) { 24 | self.name = name 25 | self.address = address 26 | self.imageURLString = imageURLString 27 | self.distance = distance 28 | } 29 | 30 | init(from decoder: Decoder) throws { 31 | let container = try decoder.container(keyedBy: CodingKeys.self) 32 | self.name = try container.decode(String.self, forKey: .name) 33 | self.address = try container.decode(String.self, forKey: .address) 34 | self.imageURLString = try container.decodeIfPresent(String.self, forKey: .imageURLString) ?? "NO IMAGE" 35 | self.distance = try container.decode(String.self, forKey: .distance) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Models/User/AppleUserModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleUserModel.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/07/13. 6 | // 7 | 8 | import Foundation 9 | 10 | /* 11 | 애플 로그인 시 사용되는 유저 데이터 12 | - id: 애플 idToken(로그인/회원가입 시 사용) 13 | - code: authorizationCode 14 | - email: 이메일 15 | */ 16 | struct AppleUserModel { 17 | var id: String 18 | var code: String 19 | var email: String 20 | } 21 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/APIServices/AuthAPIService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthAPIService.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/05/30. 6 | // 7 | 8 | import Foundation 9 | 10 | import Moya 11 | import RxCocoa 12 | import RxSwift 13 | 14 | protocol AuthAPIServiceType { 15 | func login() -> Single 16 | } 17 | 18 | final class AuthAPIService: Networking, AuthAPIServiceType { 19 | 20 | // MARK: - Type Alias 21 | typealias API = AuthAPI 22 | 23 | // MARK: - Rx 24 | private let disposeBag = DisposeBag() 25 | 26 | // MARK: - Property 27 | private let provider = NetworkProvider() 28 | private var oauthService: OAuthServiceType 29 | 30 | // MARK: - Initialization 31 | init(oauthService: OAuthServiceType) { 32 | self.oauthService = oauthService 33 | } 34 | 35 | /// 로그인 메서드 36 | /// OAuth 인증 --> 포그포그 서버 로그인/회원가입 인증 수행 37 | /// OAuthService의 OAuthServiceType만 바꿔 끼워주더라도 인증 로직은 변경없이 수행됩니다. 38 | func login() -> Single { 39 | return oauthService 40 | .authorize() 41 | .do { oauthAuthentication in 42 | #if DEBUG 43 | print("✨ OAuth 인증 성공) \(oauthAuthentication)") 44 | #endif 45 | 46 | let authType = oauthAuthentication.oauthType.rawValue 47 | Keychain.create(key: Keychain.Keys.socialType, data: authType) 48 | } 49 | .map { $0.toSignInRequestDTO() } 50 | .flatMap(signIn) 51 | .do { dto in 52 | if let dto { 53 | UserDefaults.isFirstLaunch = false 54 | UserDefaults.userId = dto.id ?? -1 55 | Keychain.create(key: Keychain.Keys.accessToken, data: dto.accessToken) 56 | Keychain.create(key: Keychain.Keys.refreshToken, data: dto.refreshToken) 57 | } else { 58 | #if DEBUG 59 | print("포그포그 서버 인증 실패") 60 | #endif 61 | // TODO: 네트워크 오류 팝업 띄워주기 62 | } 63 | } 64 | .map { _ in () } 65 | } 66 | 67 | // 실제 로그인/회원가입 Request 68 | private func signIn(_ request: SignInRequestDTO) -> Single { 69 | return provider 70 | .request(.signIn(request: request)) 71 | .map(SignInResponseDTO.self) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/APIServices/MapAPIService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapAPIService.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/09/13. 6 | // 7 | 8 | import Moya 9 | import RxSwift 10 | 11 | protocol MapAPIServiceType { 12 | func fetchPlaceAll(coordinates: CoordinatesRequest) -> Single 13 | func fetchPlaceDetail(id: Int, coordinates: CoordinatesRequest) -> Single 14 | } 15 | 16 | final class MapAPIService: MapAPIServiceType { 17 | private let provider = MoyaProvider() 18 | 19 | public init() {} 20 | 21 | func fetchPlaceAll(coordinates: CoordinatesRequest) -> Single { 22 | return provider.rx.request(.fetchPlace(coordinates)).map(SmokingAreaResponseEntity.self) 23 | } 24 | 25 | func fetchPlaceDetail(id: Int, coordinates: CoordinatesRequest) -> Single { 26 | return provider.rx.request(.fetchPlaceDetail(id, coordinates)).map(SmokingAreaDetailResponseEntity.self) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/APIServices/QuitAPIService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuitAPIService.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/09/13. 6 | // 7 | 8 | import Foundation 9 | 10 | import Moya 11 | import RxSwift 12 | 13 | protocol QuitAPIServiceType { 14 | func quit(id: Int) -> Single 15 | } 16 | 17 | final class QuitAPIService: QuitAPIServiceType { 18 | 19 | // MARK: - Rx 20 | private let disposeBag = DisposeBag() 21 | 22 | // MARK: - Property 23 | private let provider = NetworkProvider() 24 | 25 | // MARK: - Initialization 26 | init() {} 27 | 28 | /// 회원탙퇴를 진행합니다. 29 | func quit(id: Int) -> Single { 30 | return provider 31 | .request(.quit(id: id)) 32 | .do(onSuccess: { [weak self] _ in 33 | UserDefaults.standard.removeAllUserDefaulsKeys() // 유저 정보 초기화 34 | self?.removeAllKeychainKeys() // 키체인 값 삭제 35 | }) 36 | .map { _ in () } 37 | } 38 | 39 | // 회원탈퇴 시에 모든 키체인 값을 제거합니다. 40 | func removeAllKeychainKeys() { 41 | Keychain.delete(key: Keychain.Keys.accessToken) 42 | Keychain.delete(key: Keychain.Keys.refreshToken) 43 | Keychain.delete(key: Keychain.Keys.socialType) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/APIServices/ReissueAPIService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReissueAPIService.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/08/20. 6 | // 7 | 8 | import Foundation 9 | 10 | import Moya 11 | import RxCocoa 12 | import RxSwift 13 | 14 | final class ReissueAPIService: Networking { 15 | 16 | // MARK: - Type Alias 17 | typealias API = AuthAPI 18 | 19 | // MARK: - Property 20 | private let provider = NetworkProvider() 21 | 22 | // MARK: - Instance 23 | static let shared = ReissueAPIService() 24 | 25 | // MARK: - Initialization 26 | private init() {} 27 | 28 | // MARK: - Custom Methods 29 | 30 | // 401 error 시 토큰 재발급 Request 31 | func reissueAuthentication() -> Single { 32 | return provider 33 | .request(.reissueToken) 34 | .map(SignInResponseDTO.self) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/APIServices/UserAPIService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserAPIService.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/03/26. 6 | // 7 | 8 | import Foundation 9 | 10 | import Moya 11 | import RxCocoa 12 | import RxSwift 13 | 14 | class UserAPIService: Networking { 15 | 16 | // MARK: - Type Alias 17 | typealias API = UserAPI 18 | 19 | // MARK: - Property 20 | private let provider = NetworkProvider() 21 | 22 | // MARK: - Instance 23 | static let shared = UserAPIService() 24 | 25 | // MARK: - Initialization 26 | private init() {} 27 | 28 | // MARK: - Custom Methods 29 | 30 | // 유저 닉네임 조회 31 | func getUserNickname(userId: Int) -> Single { 32 | return provider.request(.getNickname(userId: userId)) 33 | .map(NicknameResponseModel.self) 34 | } 35 | 36 | // 유저 닉네임 수정 37 | func editUserNickname(userId: Int, nickname: String) -> Single { 38 | return provider.request(.editNickname(userId: userId, nickname: nickname)) 39 | .map(NicknameResponseModel.self) 40 | } 41 | 42 | // 선호 지도 설정 43 | func setPreferredMap(userId: Int, mapId: Int) -> Single { 44 | return provider 45 | .request(.preferredMap(userId: userId, mapId: mapId)) 46 | .map(EmptyData.self) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/APIs/AuthAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthAPI.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/05/30. 6 | // 7 | 8 | import Moya 9 | 10 | struct SignInRequestDTO: Codable { 11 | let socialType: String 12 | let kakaoAccessToken: String? 13 | let idToken: String? 14 | let code: String? 15 | } 16 | 17 | struct SignInResponseDTO: Codable { 18 | let accessToken: String 19 | let refreshToken: String 20 | let id: Int? 21 | } 22 | 23 | enum AuthAPI { 24 | case signIn(request: SignInRequestDTO) 25 | case reissueToken 26 | case quit(id: Int) 27 | } 28 | 29 | extension AuthAPI: FogAPI { 30 | 31 | var domain: FogDomain { 32 | return .auth 33 | } 34 | 35 | var urlPath: String { 36 | switch self { 37 | case .signIn: 38 | return "/signin" 39 | case .reissueToken: 40 | return "/reissue/token" 41 | case .quit(let id): 42 | return "/\(id)" 43 | } 44 | } 45 | 46 | var method: Method { 47 | switch self { 48 | case .signIn: 49 | return .post 50 | case .reissueToken: 51 | return .get 52 | case .quit: 53 | return .delete 54 | } 55 | } 56 | 57 | var parameters: [String: Any]? { 58 | switch self { 59 | case let .signIn(request): 60 | return [ 61 | "socialType": request.socialType, 62 | "kakaoAccessToken": request.kakaoAccessToken ?? "", 63 | "idToken": request.idToken ?? "", 64 | "code": request.code ?? "" 65 | ] 66 | case .reissueToken: 67 | return nil 68 | case .quit: 69 | return nil 70 | } 71 | } 72 | 73 | var error: [Int: NetworkError]? { 74 | switch self { 75 | case .signIn, .reissueToken, .quit: 76 | return nil 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/APIs/FogAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogAPI.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/01/17. 6 | // 7 | 8 | import Foundation 9 | 10 | import Moya 11 | 12 | /// FogFog 도메인 13 | enum FogDomain { 14 | case auth 15 | case maps 16 | case users 17 | } 18 | 19 | extension FogDomain { 20 | /// 도메인에 따른 기본 url 21 | var url: String { 22 | switch self { 23 | case .auth: 24 | return "/auth" 25 | case .maps: 26 | return "/maps" 27 | case .users: 28 | return "/users" 29 | } 30 | } 31 | } 32 | 33 | /// FogFog API가 기본적으로 준수해야 하는 정보 34 | /// 35 | /// domain : FogFog Domain(ex. users, maps, auth ...) 36 | /// urlPath : Domain 뒤에 붙는 상세 경로(path) 37 | /// error : 상태코드에 따른 NetworkError 구분하는데 사용되는 딕셔너리 38 | /// parameters : Request에 사용될 Paramter - 기본적으로 JSONEncoding 방식으로 인코딩 39 | protocol FogAPI: TargetType { 40 | var domain: FogDomain { get } 41 | var urlPath: String { get } 42 | var error: [Int: NetworkError]? { get } 43 | var parameters: [String: Any]? { get } 44 | } 45 | 46 | extension FogAPI { 47 | var baseURL: URL { 48 | return URL(string: NetworkEnv.baseURL)! 49 | } 50 | 51 | var path: String { 52 | return domain.url + urlPath 53 | } 54 | 55 | var validationType: ValidationType { 56 | return .successCodes 57 | } 58 | 59 | var headers: [String: String]? { 60 | return .none 61 | } 62 | 63 | var task: Task { 64 | if let parameters = parameters { 65 | return .requestParameters(parameters: parameters, encoding: JSONEncoding.default) 66 | } 67 | return .requestPlain 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/APIs/MapAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapAPI.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/09/13. 6 | // 7 | 8 | import UIKit 9 | import Moya 10 | 11 | struct CoordinatesRequest: Equatable { 12 | let lat: CGFloat 13 | let long: CGFloat 14 | } 15 | 16 | enum MapAPI { 17 | case fetchPlace(CoordinatesRequest) 18 | case fetchPlaceDetail(Int, CoordinatesRequest) 19 | } 20 | 21 | extension MapAPI: TargetType { 22 | var baseURL: URL { 23 | return URL(string: NetworkEnv.baseURL)! 24 | } 25 | 26 | var path: String { 27 | switch self { 28 | case .fetchPlace: 29 | return "/maps" 30 | case .fetchPlaceDetail(let id, _): 31 | return "/maps/\(id)" 32 | } 33 | } 34 | 35 | var headers: [String: String]? { 36 | return ["Authorization": "Bearer " + (Keychain.read(key: Keychain.Keys.accessToken) ?? "") , 37 | "Content-Type": "application/json"] 38 | } 39 | 40 | var method: Moya.Method { 41 | switch self { 42 | case .fetchPlace: 43 | return .get 44 | case .fetchPlaceDetail: 45 | return .get 46 | } 47 | } 48 | 49 | var task: Task { 50 | var params: [String: Any] = [:] 51 | switch self { 52 | case .fetchPlace(let coordinates): 53 | params["lat"] = coordinates.lat 54 | params["long"] = coordinates.long 55 | return .requestParameters(parameters: params, encoding: URLEncoding.queryString) 56 | case .fetchPlaceDetail(_, let coordinates): 57 | params["lat"] = coordinates.lat 58 | params["long"] = coordinates.long 59 | return .requestParameters(parameters: params, encoding: URLEncoding.queryString) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/APIs/UserAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserAPI.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/01/22. 6 | // 7 | 8 | import Moya 9 | 10 | enum UserAPI { 11 | case preferredMap(userId: Int, mapId: Int) 12 | case withdrawal(userId: String) 13 | case getNickname(userId: Int) 14 | case editNickname(userId: Int, nickname: String) 15 | } 16 | 17 | extension UserAPI: FogAPI { 18 | 19 | var domain: FogDomain { 20 | return .users 21 | } 22 | 23 | var urlPath: String { 24 | switch self { 25 | case let .preferredMap(userId, _): 26 | return "/\(userId)/preferred-map" 27 | case .withdrawal(let userId): 28 | return "/\(userId)" 29 | case .getNickname(let userId), .editNickname(let userId, _): 30 | return "/\(userId)/nickname" 31 | } 32 | } 33 | 34 | var method: Method { 35 | switch self { 36 | case .preferredMap, .editNickname: 37 | return .patch 38 | case .withdrawal: 39 | return .delete 40 | case .getNickname: 41 | return .get 42 | } 43 | } 44 | 45 | var parameters: [String: Any]? { 46 | switch self { 47 | case let .preferredMap(_, mapId): 48 | return ["preferredMap": mapId] 49 | 50 | case .withdrawal, .getNickname: 51 | return nil 52 | case .editNickname(_, let nickname): 53 | return ["nickname": nickname] 54 | } 55 | } 56 | 57 | var error: [Int: NetworkError]? { 58 | switch self { 59 | case .preferredMap, .withdrawal, .getNickname, .editNickname: 60 | return nil 61 | /* 62 | case .nickname: 63 | return [403: .unauthorized] 64 | */ 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/Foundation/AuthInterceptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthInterceptor.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/08/18. 6 | // 7 | 8 | import Foundation 9 | 10 | import Alamofire 11 | import RxSwift 12 | 13 | final class AuthInterceptor: RequestInterceptor { 14 | 15 | private let disposeBag = DisposeBag() 16 | 17 | func adapt(_ urlRequest: URLRequest, 18 | for session: Session, 19 | completion: @escaping (Result) -> Void) { 20 | guard let accessToken = Keychain.read(key: Keychain.Keys.accessToken), 21 | let refreshToken = Keychain.read(key: Keychain.Keys.refreshToken) 22 | else { 23 | completion(.success(urlRequest)) 24 | return 25 | } 26 | 27 | // request header 설정 28 | var urlRequest = urlRequest 29 | urlRequest.headers.add(.authorization(bearerToken: accessToken)) 30 | if let urlString = urlRequest.url?.absoluteString, urlString.hasSuffix("/reissue/token") { 31 | urlRequest.headers.add(.authorization(bearerToken: refreshToken)) 32 | } 33 | completion(.success(urlRequest)) 34 | } 35 | 36 | func retry(_ request: Request, 37 | for _: Session, 38 | dueTo error: Error, 39 | completion: @escaping (RetryResult) -> Void) { 40 | guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 41 | else { 42 | completion(.doNotRetryWithError(error)) 43 | return 44 | } 45 | 46 | if let urlString = response.url?.absoluteString, urlString.hasSuffix("/reissue/token") { 47 | completion(.doNotRetry) 48 | return 49 | } 50 | 51 | // 토큰 재발급 요청 (401일 떄) 52 | ReissueAPIService.shared.reissueAuthentication() 53 | .subscribe(onSuccess: { result in 54 | Keychain.create(key: Keychain.Keys.accessToken, data: result?.accessToken ?? "") 55 | Keychain.create(key: Keychain.Keys.refreshToken, data: result?.refreshToken ?? "") 56 | completion(.retry) 57 | }, onFailure: { error in 58 | // refreshToken 만료 시 로그인 화면으로 전환 59 | NotificationCenter.default.post(name: NotificationCenterKey.refreshTokenHasExpired, object: nil) 60 | completion(.doNotRetryWithError(error)) 61 | }) 62 | .disposed(by: disposeBag) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/Foundation/NetworkEnv.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkEnv.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/01/17. 6 | // 7 | 8 | import Foundation 9 | 10 | enum NetworkEnv { 11 | // BaseURL 12 | static let baseURL = "http://fogfogdev-env-1.eba-9u3ghscu.ap-northeast-2.elasticbeanstalk.com" 13 | } 14 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/Foundation/NetworkError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkError.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/01/15. 6 | // 7 | 8 | import Foundation 9 | 10 | /// FogFog 네트워크 통신에서 공통적으로 발생하는 에러 11 | enum NetworkError: Int { 12 | case invalidRequest = 400 // Bad Request (Client Error) 13 | case unauthorized = 401 // 소셜 로그인 토큰이 없거나 유효하지 않은 경우 14 | case forbidden = 403 // 요청 id와 accessToken 정보가 매칭되지 않는 경우 15 | case notFound = 404 // 유효한 유저 정보가 없는 경우 16 | case duplicated = 409 // 중복된 닉네임일 경우 17 | case serverError = 500 // Internal Server Error 18 | } 19 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/Foundation/Networking.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Networking.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/01/17. 6 | // 7 | 8 | import RxSwift 9 | import RxMoya 10 | import Moya 11 | 12 | protocol Networking { 13 | associatedtype API: FogAPI 14 | 15 | func request(_ api: API, file: StaticString, function: StaticString, line: UInt) -> Single 16 | } 17 | 18 | extension Networking { 19 | 20 | func request( 21 | _ api: API, 22 | file: StaticString = #file, 23 | function: StaticString = #function, 24 | line: UInt = #line 25 | ) -> Single { 26 | self.request(api, file: file, function: function, line: line) 27 | } 28 | } 29 | 30 | final class NetworkProvider: Networking { 31 | 32 | private let provider: MoyaProvider 33 | private let interceptor = AuthInterceptor() 34 | 35 | init(plugins: [PluginType] = []) { 36 | let session = Session(interceptor: interceptor) 37 | session.sessionConfiguration.timeoutIntervalForRequest = 10 38 | 39 | self.provider = MoyaProvider(session: session, plugins: plugins) 40 | } 41 | 42 | func request( 43 | _ api: API, 44 | file: StaticString = #file, 45 | function: StaticString = #function, 46 | line: UInt = #line 47 | ) -> Single { 48 | let requestString = "\(api.urlPath)" 49 | return provider.rx.request(api) 50 | .filterSuccessfulStatusCodes() 51 | .do( 52 | onSuccess: { response in 53 | print("SUCCESS: \(requestString) (\(response.statusCode))") 54 | }, 55 | onError: { _ in 56 | print("ERROR: \(requestString)") 57 | }, 58 | onSubscribed: { 59 | print("REQUEST: \(requestString)") 60 | } 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/Models/CommonResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonResponse.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/01/17. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Generic Response 11 | struct CommonResponse: Decodable { 12 | let statusCode: Int 13 | let message: String 14 | let success: Bool 15 | let data: T? 16 | 17 | enum CodingKeys: CodingKey { 18 | case statusCode 19 | case message 20 | case success 21 | case data 22 | } 23 | 24 | init(from decoder: Decoder) throws { 25 | let container = try decoder.container(keyedBy: CodingKeys.self) 26 | self.statusCode = (try? container.decode(Int.self, forKey: .statusCode)) ?? 500 27 | self.message = (try? container.decode(String.self, forKey: .message)) ?? "" 28 | self.success = (try? container.decode(Bool.self, forKey: .success)) ?? false 29 | self.data = try container.decodeIfPresent(T.self, forKey: .data) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/Models/EmptyData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyData.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/08/14. 6 | // 7 | 8 | import Foundation 9 | 10 | struct EmptyData: Codable {} 11 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/Models/NicknameResponseModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NicknameResModel.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/03/26. 6 | // 7 | 8 | import Foundation 9 | 10 | struct NicknameResponseModel: Codable { 11 | let nickname: String 12 | } 13 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/Monitoring/NetworkConnectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkConnectionView.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/06/07. 6 | // 7 | 8 | import UIKit 9 | 10 | final class NetworkConnectionView: UIView { 11 | 12 | private let containerView: UIStackView = { 13 | let stackView = UIStackView() 14 | stackView.translatesAutoresizingMaskIntoConstraints = false 15 | stackView.axis = .vertical 16 | stackView.distribution = .fill 17 | stackView.alignment = .center 18 | stackView.spacing = 8 19 | return stackView 20 | }() 21 | 22 | private let imageView: UIImageView = { 23 | let imageView = UIImageView() 24 | imageView.translatesAutoresizingMaskIntoConstraints = false 25 | imageView.contentMode = .scaleAspectFill 26 | imageView.image = UIImage(systemName: "xmark") 27 | return imageView 28 | }() 29 | 30 | private let titleLabel: UILabel = { 31 | let label = UILabel() 32 | label.text = "데이터를 불러올 수 없습니다." 33 | label.textColor = .black 34 | return label 35 | }() 36 | 37 | override init(frame: CGRect) { 38 | super.init(frame: frame) 39 | 40 | backgroundColor = .white 41 | containerView.addArrangedSubview(imageView) 42 | containerView.addArrangedSubview(titleLabel) 43 | addSubview(containerView) 44 | NSLayoutConstraint.activate([ 45 | containerView.centerXAnchor.constraint(equalTo: centerXAnchor), 46 | containerView.centerYAnchor.constraint(equalTo: centerYAnchor), 47 | containerView.widthAnchor.constraint(equalToConstant: 200), 48 | containerView.heightAnchor.constraint(equalToConstant: 200) 49 | ]) 50 | } 51 | 52 | required init?(coder: NSCoder) { 53 | fatalError("init(coder:) has not been implemented") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Networking/Monitoring/NetworkMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkMonitor.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/06/07. 6 | // 7 | 8 | import Foundation 9 | import Network 10 | 11 | final class NetworkMonitor { 12 | 13 | @Published private(set) var status: NWPath.Status 14 | private let queue = DispatchQueue(label: "NetworkMonitor") 15 | private let pathMonitor = NWPathMonitor() 16 | 17 | init(status: NWPath.Status = .unsatisfied) { 18 | self.status = status 19 | startPathMonitor() 20 | } 21 | 22 | func startPathMonitor() { 23 | pathMonitor.pathUpdateHandler = { path in 24 | DispatchQueue.main.async { 25 | self.status = path.status 26 | } 27 | } 28 | pathMonitor.start(queue: queue) 29 | } 30 | 31 | func stopPathMonitor() { 32 | pathMonitor.cancel() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/OAuth/AppleOAuthService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleOAuthService.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/07/04. 6 | // 7 | 8 | import UIKit 9 | 10 | import AuthenticationServices 11 | import RxSwift 12 | 13 | final class AppleOAuthService: OAuthServiceType { 14 | 15 | private let disposeBag = DisposeBag() 16 | private let appleLoginManager = AppleLoginManager() 17 | 18 | func authorize() -> Single { 19 | return login().map { OAuthAuthentication(oauthType: .apple, idToken: $0.id, code: $0.code) } 20 | } 21 | 22 | private func login() -> Single { 23 | return Single.create { observer in 24 | self.appleLoginManager 25 | .handleAuthorizationAppleIDButtonPress() 26 | .subscribe(onNext: { result in 27 | guard 28 | let auth = result.credential as? ASAuthorizationAppleIDCredential, 29 | let idToken = auth.identityToken, 30 | let code = auth.authorizationCode 31 | else { return } 32 | 33 | let appleUser = AppleUserModel(id: String(decoding: idToken, as: UTF8.self), 34 | code: String(decoding: code, as: UTF8.self), 35 | email: auth.email ?? "") 36 | observer(.success(appleUser)) 37 | }, onError: { error in 38 | observer(.failure(error)) 39 | }) 40 | .disposed(by: self.disposeBag) 41 | 42 | return Disposables.create() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/OAuth/KakaoOAuthService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KakaoOAuthService.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/05/21. 6 | // 7 | 8 | import KakaoSDKAuth 9 | import KakaoSDKUser 10 | import RxKakaoSDKAuth 11 | import RxKakaoSDKUser 12 | import RxSwift 13 | 14 | final class KakaoOAuthService: OAuthServiceType { 15 | 16 | private let disposeBag = DisposeBag() 17 | 18 | func authorize() -> Single { 19 | return login().map { OAuthAuthentication(oauthType: .kakao, kakaoAccessToken: $0.accessToken) } 20 | } 21 | 22 | private func login() -> Single { 23 | let isKakaoTalkLoginAvailable = UserApi.isKakaoTalkLoginAvailable() 24 | 25 | return Single.create { observer in 26 | if isKakaoTalkLoginAvailable { 27 | UserApi.shared.rx.loginWithKakaoTalk() 28 | .subscribe(onNext: { oAuthToken in 29 | observer(.success(oAuthToken)) 30 | }, onError: { error in 31 | observer(.failure(error)) 32 | }) 33 | .disposed(by: self.disposeBag) 34 | } else { 35 | UserApi.shared.rx.loginWithKakaoAccount() 36 | .subscribe(onNext: { oAuthToken in 37 | observer(.success(oAuthToken)) 38 | }, onError: { error in 39 | observer(.failure(error)) 40 | }) 41 | .disposed(by: self.disposeBag) 42 | } 43 | 44 | return Disposables.create() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/OAuth/OAuthAuthentication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthAuthentication.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/05/29. 6 | // 7 | 8 | import Foundation 9 | 10 | struct OAuthAuthentication { 11 | var oauthType: OAuthProviderType 12 | var kakaoAccessToken: String? 13 | var idToken: String? 14 | var code: String? 15 | } 16 | 17 | extension OAuthAuthentication { 18 | 19 | func toSignInRequestDTO() -> SignInRequestDTO { 20 | return .init( 21 | socialType: oauthType.rawValue, 22 | kakaoAccessToken: kakaoAccessToken, 23 | idToken: idToken, 24 | code: code 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/OAuth/OAuthProviderType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthProviderType.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/05/29. 6 | // 7 | 8 | import Foundation 9 | 10 | /** 11 | - description: OAuth 서비스를 제공하는 제공자 종류 12 | 우리 서비스의 경우 Apple, Kakao 서비스 사용 13 | */ 14 | 15 | enum OAuthProviderType: String, Hashable, CaseIterable { 16 | case kakao 17 | case apple 18 | 19 | var service: OAuthServiceType { 20 | switch self { 21 | case .kakao: 22 | return KakaoOAuthService() 23 | case .apple: 24 | return AppleOAuthService() 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/OAuth/OAuthService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthService.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/05/24. 6 | // 7 | 8 | import RxSwift 9 | 10 | final class OAuthService { 11 | 12 | private let oAuthService: OAuthServiceType 13 | 14 | init(oAuthService: OAuthServiceType) { 15 | self.oAuthService = oAuthService 16 | } 17 | 18 | func authorize() -> Single { 19 | return oAuthService.authorize() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/OAuth/OAuthServiceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthServiceType.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/05/29. 6 | // 7 | 8 | /** 9 | - description: OAuthService가 채택할 프로토콜 10 | */ 11 | 12 | import RxSwift 13 | 14 | protocol OAuthServiceType { 15 | func authorize() -> Single 16 | } 17 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Common/Coordinator/DefaultAppCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultAppCoordinator.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/17. 6 | // 7 | 8 | import UIKit 9 | 10 | final class DefaultAppCoordinator: AppCoordinator { 11 | 12 | weak var finishDelegate: CoordinatorFinishDelegate? 13 | var navigationController: UINavigationController 14 | var childCoordinators = [Coordinator]() 15 | var type: CoordinatorCase { .app } 16 | 17 | required init(_ navigationController: UINavigationController) { 18 | self.navigationController = navigationController 19 | 20 | // BaseViewController에서 ViewWillAppear에 해당하는 부분인데 21 | // BaseViewController에서 코드를 지울지, 아래의 코드를 지울지 논의 필요 22 | navigationController.setNavigationBarHidden(true, animated: true) 23 | NotificationCenter.default.addObserver( 24 | self, 25 | selector: #selector(popToRoot), 26 | name: NotificationCenterKey.refreshTokenHasExpired, 27 | object: nil 28 | ) 29 | } 30 | 31 | func start() { 32 | 33 | // 앱의 시작점 설정(keychain의 accessToken 저장 여부 확인) 34 | if Keychain.read(key: Keychain.Keys.accessToken) != nil { 35 | showMapFlow() 36 | } else { 37 | showLoginFlow() 38 | } 39 | } 40 | 41 | func showLoginFlow() { 42 | let loginCoordinator = DefaultLoginCoordinator(self.navigationController) 43 | loginCoordinator.finishDelegate = self 44 | loginCoordinator.start() 45 | childCoordinators.append(loginCoordinator) 46 | } 47 | 48 | func showMapFlow() { 49 | let mapCoordinator = DefaultMapCoordinator(self.navigationController) 50 | mapCoordinator.finishDelegate = self 51 | mapCoordinator.start() 52 | childCoordinators.append(mapCoordinator) 53 | } 54 | 55 | @objc 56 | func popToRoot(_ notification: Notification) { 57 | showLoginFlow() 58 | } 59 | } 60 | 61 | extension DefaultAppCoordinator: CoordinatorFinishDelegate { 62 | 63 | func didFinish(childCoordinator: Coordinator) { 64 | 65 | self.childCoordinators = self.childCoordinators.filter({ $0.type != childCoordinator.type }) 66 | self.navigationController.viewControllers.removeAll() 67 | 68 | switch childCoordinator.type { 69 | case .login: 70 | self.showMapFlow() 71 | case .map: 72 | self.showLoginFlow() 73 | default: 74 | break 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Common/Coordinator/Delegate/CoordinatorFinishDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoordinatorFinishDelegate.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/16. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol CoordinatorFinishDelegate: AnyObject { 11 | 12 | func didFinish(childCoordinator: Coordinator) 13 | } 14 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Common/Coordinator/Protocol/AppCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppCoordinator.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/17. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol AppCoordinator: Coordinator { 11 | 12 | func showLoginFlow() 13 | func showMapFlow() 14 | } 15 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Common/Coordinator/Protocol/Coordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinator.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/16. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol Coordinator: AnyObject { 11 | var finishDelegate: CoordinatorFinishDelegate? { get set } 12 | var navigationController: UINavigationController { get set } 13 | var childCoordinators: [Coordinator] { get set } 14 | var type: CoordinatorCase { get } 15 | 16 | func start() 17 | func finish() 18 | 19 | init(_ navigationController: UINavigationController) 20 | } 21 | 22 | extension Coordinator { 23 | 24 | func finish() { 25 | childCoordinators.removeAll() 26 | finishDelegate?.didFinish(childCoordinator: self) 27 | } 28 | 29 | func changeAnimation() { 30 | let scenes = UIApplication.shared.connectedScenes 31 | let windowScene = scenes.first as? UIWindowScene 32 | let window = windowScene?.windows.first 33 | 34 | if let window = window { 35 | UIView.transition( 36 | with: window, 37 | duration: 0.5, 38 | options: .transitionCrossDissolve, 39 | animations: nil 40 | ) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Common/Protocol/ViewModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModelType.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/11/20. 6 | // 7 | 8 | import Foundation 9 | 10 | import RxSwift 11 | 12 | protocol ViewModelType { 13 | 14 | associatedtype Input 15 | associatedtype Output 16 | 17 | var disposeBag: DisposeBag { get set } 18 | 19 | func transform(input: Input) -> Output 20 | } 21 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/ExternalMap/ExternalMapModalViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExternalMapModalViewController.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2022/11/25. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxCocoa 11 | import RxSwift 12 | 13 | final class ExternalMapModalViewController: BaseViewController { 14 | 15 | // MARK: Properties 16 | private let rootView = ExternalMapModalView() 17 | private var disposeBag = DisposeBag() 18 | private let viewModel: any ViewModelType 19 | 20 | // MARK: Init 21 | init(viewModel: some ViewModelType) { 22 | self.viewModel = viewModel 23 | super.init(nibName: nil, bundle: nil) 24 | } 25 | 26 | // MARK: Life Cycle 27 | override func loadView() { 28 | self.view = rootView 29 | } 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | bind(viewModel: viewModel) 35 | } 36 | 37 | func bind(viewModel: some ViewModelType) { 38 | guard let viewModel = viewModel as? ExternalMapModalViewModel else { 39 | assertionFailure("Could not found ExternalMapModalViewModel.") 40 | return 41 | } 42 | 43 | let input = bindInput(viewModel: viewModel) 44 | bindOutput(viewModel: viewModel, input: input) 45 | } 46 | 47 | func bindInput(viewModel: ExternalMapModalViewModel) -> ExternalMapModalViewModel.Input { 48 | return ExternalMapModalViewModel.Input( 49 | kakaoMapButtonDidTap: rootView.rx.kakaoMapButtonDidTap, 50 | googleMapButtonDidTap: rootView.rx.googleMapButtonDidTap, 51 | naverMapButtonDidTap: rootView.rx.naverMapButtonDidTap, 52 | confirmButtonDidTap: rootView.rx.confirmButtonDidTap, 53 | closeButtonDidTap: rootView.rx.closeButtonDidTap 54 | ) 55 | } 56 | 57 | func bindOutput( 58 | viewModel: ExternalMapModalViewModel, 59 | input: ExternalMapModalViewModel.Input 60 | ) { 61 | let output = viewModel.transform(input: input) 62 | 63 | output.selectedMap 64 | .bind(to: rootView.rx.updateViews) 65 | .disposed(by: disposeBag) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/ExternalMap/ExternalMapModalViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExternalMapModalViewModel.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2022/11/25. 6 | // 7 | 8 | import Foundation 9 | 10 | import RxCocoa 11 | import RxSwift 12 | 13 | final class ExternalMapModalViewModel: ViewModelType { 14 | 15 | var disposeBag = DisposeBag() 16 | 17 | struct Input { 18 | let kakaoMapButtonDidTap: ControlEvent 19 | let googleMapButtonDidTap: ControlEvent 20 | let naverMapButtonDidTap: ControlEvent 21 | let confirmButtonDidTap: ControlEvent 22 | let closeButtonDidTap: ControlEvent 23 | } 24 | 25 | struct Output { 26 | let selectedMap: PublishRelay 27 | } 28 | 29 | func transform(input: Input) -> Output { 30 | let selectedMap = PublishRelay() 31 | 32 | input.kakaoMapButtonDidTap 33 | .asSignal() 34 | .map { ExternalMapType.kakao } 35 | .emit(to: selectedMap) 36 | .disposed(by: disposeBag) 37 | 38 | input.googleMapButtonDidTap 39 | .asSignal() 40 | .map { ExternalMapType.google } 41 | .emit(to: selectedMap) 42 | .disposed(by: disposeBag) 43 | 44 | input.naverMapButtonDidTap 45 | .asSignal() 46 | .map { ExternalMapType.naver } 47 | .emit(to: selectedMap) 48 | .disposed(by: disposeBag) 49 | 50 | input.confirmButtonDidTap 51 | .withLatestFrom(selectedMap) 52 | .withUnretained(self) 53 | .flatMap { owner, mapType in 54 | let userId = UserDefaults.userId 55 | let mapId = mapType.rawValue 56 | return owner.setPreferredMap(userId: userId, mapId: mapId) 57 | } 58 | .subscribe() 59 | .disposed(by: disposeBag) 60 | 61 | return Output(selectedMap: selectedMap) 62 | } 63 | 64 | func setPreferredMap(userId: Int, mapId: Int) -> Observable { 65 | return UserAPIService.shared 66 | .setPreferredMap(userId: userId, mapId: mapId) 67 | .compactMap { _ in () } 68 | .asObservable() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/ExternalMap/ExternalMapType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExternalMapType.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2022/11/25. 6 | // 7 | 8 | import Foundation 9 | 10 | /// 외부 맵 열거형 11 | enum ExternalMapType: Int, CaseIterable { 12 | case kakao 13 | case google 14 | case naver 15 | 16 | var title: String { 17 | switch self { 18 | case .kakao: return "카카오맵" 19 | case .google: return "구글지도" 20 | case .naver: return "네이버지도" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Login/Coordinator/DefaultLoginCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultLoginCoordinator.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/17. 6 | // 7 | 8 | import UIKit 9 | 10 | final class DefaultLoginCoordinator: LoginCoordinator { 11 | 12 | weak var finishDelegate: CoordinatorFinishDelegate? 13 | var navigationController: UINavigationController 14 | var childCoordinators = [Coordinator]() 15 | var type: CoordinatorCase { .login } 16 | 17 | init(_ navigationController: UINavigationController) { 18 | self.navigationController = navigationController 19 | } 20 | 21 | func start() { 22 | showLoginViewController() 23 | } 24 | 25 | func showLoginViewController() { 26 | let viewModel = LoginViewModel(coordinator: self) { oauthProviderType in 27 | let oauthService = oauthProviderType.service 28 | let authService = AuthAPIService(oauthService: oauthService) 29 | return authService 30 | } 31 | let loginViewController = LoginViewController(viewModel: viewModel) 32 | navigationController.pushViewController(loginViewController, animated: false) 33 | } 34 | 35 | func showMakeNicknameViewController() { 36 | let makeNicknameViewModel = MakeNicknameViewModel(coordinator: self) 37 | let makeNicknameController = MakeNicknameViewController(viewModel: makeNicknameViewModel) 38 | navigationController.pushViewController(makeNicknameController, animated: true) 39 | makeNicknameController.setNaviTitle(.create) 40 | } 41 | 42 | func finish() { 43 | finishDelegate? 44 | .didFinish(childCoordinator: self) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Login/Coordinator/Protocol/LoginCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginCoordinator.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/17. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol LoginCoordinator: Coordinator { 11 | func showLoginViewController() 12 | func showMakeNicknameViewController() 13 | } 14 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Login/MakeNicknameViewType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MakeNicknameViewType.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/08/03. 6 | // 7 | 8 | import Foundation 9 | 10 | // 닉네임 설정 뷰 타입 11 | enum MakeNicknameViewType { 12 | case create 13 | case edit 14 | 15 | var title: String { 16 | switch self { 17 | case .create: return "닉네임 설정" 18 | case .edit: return "닉네임 수정" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Login/ViewModel/LoginViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewModel.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/17. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxCocoa 11 | import RxSwift 12 | 13 | final class LoginViewModel: ViewModelType { 14 | 15 | struct Input { 16 | let kakaoButtonDidTap: Observable 17 | let appleButtonDidTap: Observable 18 | } 19 | 20 | struct Output { 21 | let loginResult: Driver 22 | } 23 | 24 | // MARK: - Property 25 | 26 | var disposeBag = DisposeBag() 27 | 28 | private let coordinator: LoginCoordinator 29 | private let factory: (OAuthProviderType) -> AuthAPIServiceType 30 | 31 | // MARK: - Initialization 32 | 33 | init( 34 | coordinator: LoginCoordinator, 35 | factory: @escaping (OAuthProviderType) -> AuthAPIServiceType 36 | ) { 37 | self.coordinator = coordinator 38 | self.factory = factory 39 | } 40 | 41 | func transform(input: Input) -> Output { 42 | let result = PublishSubject() 43 | 44 | input.kakaoButtonDidTap 45 | .flatMap(loginWithKakao) 46 | .subscribe { _ in 47 | if UserDefaults.nickname.isEmpty { 48 | self.coordinator.showMakeNicknameViewController() 49 | } else { 50 | self.coordinator.finish() 51 | } 52 | } onError: { error in 53 | result.onNext("error: \(error.localizedDescription)") 54 | } 55 | .disposed(by: disposeBag) 56 | 57 | input.appleButtonDidTap 58 | .flatMap(loginWithApple) 59 | .subscribe { _ in 60 | if UserDefaults.nickname.isEmpty { 61 | self.coordinator.showMakeNicknameViewController() 62 | } else { 63 | self.coordinator.finish() 64 | } 65 | } onError: { error in 66 | result.onNext("error: \(error.localizedDescription)") 67 | } 68 | .disposed(by: disposeBag) 69 | 70 | return Output(loginResult: result.asDriver(onErrorDriveWith: .empty())) 71 | } 72 | 73 | private func loginWithKakao() -> Observable { 74 | return factory(.kakao).login().asObservable() 75 | } 76 | 77 | private func loginWithApple() -> Observable { 78 | return factory(.apple).login().asObservable() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Login/ViewModel/MakeNicknameViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MakeNicknameViewModel.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/11/25. 6 | // 7 | 8 | import UIKit 9 | 10 | import Moya 11 | import RxSwift 12 | import RxCocoa 13 | 14 | final class MakeNicknameViewModel: ViewModelType { 15 | 16 | var disposeBag = DisposeBag() 17 | 18 | private weak var coordinator: Coordinator? 19 | 20 | init(coordinator: Coordinator?) { 21 | self.coordinator = coordinator 22 | } 23 | 24 | struct Input { 25 | let didNicknameTextFieldChange: Observable 26 | let tapConfirmButton: Signal 27 | let tapBackButton: Signal 28 | } 29 | 30 | struct Output { 31 | let didBackButtonTapped: Signal 32 | let didConfirmButtonTapped: Signal 33 | let isValid = PublishRelay() 34 | let nickname = BehaviorRelay(value: "") 35 | } 36 | 37 | let output = Output(didBackButtonTapped: PublishRelay().asSignal(), 38 | didConfirmButtonTapped: PublishRelay().asSignal()) 39 | 40 | func transform(input: Input) -> Output { 41 | input.didNicknameTextFieldChange 42 | .subscribe(onNext: { text in 43 | self.output.isValid.accept(true) 44 | self.output.nickname.accept(self.checkMaxLength(text: text)) 45 | }) 46 | .disposed(by: disposeBag) 47 | 48 | input.tapConfirmButton 49 | .withUnretained(self) 50 | .emit { owner, _ in 51 | owner.editUserNicknameAPI(userId: UserDefaults.userId, nickname: self.output.nickname.value) 52 | } 53 | .disposed(by: disposeBag) 54 | 55 | input.tapBackButton 56 | .withUnretained(self) 57 | .emit { owner, _ in 58 | owner.coordinator?.navigationController.popViewController(animated: true) 59 | } 60 | .disposed(by: disposeBag) 61 | 62 | return output 63 | } 64 | } 65 | 66 | extension MakeNicknameViewModel { 67 | 68 | /// 최대 8글자로 제한하는 메서드 69 | private func checkMaxLength(text: String, maxLength: Int = 8) -> String { 70 | if text.count > maxLength { 71 | let index = text.index(text.startIndex, offsetBy: 8) 72 | return String(text[..(value: .notDetermined) 19 | var location = BehaviorRelay<[CLLocation]>(value: [CLLocation(latitude: 0, longitude: 0)]) 20 | 21 | override init() { 22 | super.init() 23 | self.locationManager = CLLocationManager() 24 | self.locationManager?.distanceFilter = CLLocationDistance(3) 25 | self.locationManager?.delegate = self 26 | self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest 27 | } 28 | 29 | func start() { 30 | self.locationManager?.startUpdatingLocation() 31 | } 32 | 33 | func stop() { 34 | self.locationManager?.stopUpdatingLocation() 35 | } 36 | 37 | func requestAuthorization() { 38 | self.locationManager?.requestWhenInUseAuthorization() 39 | } 40 | 41 | func observeUpdatedAuthorization() -> Observable { 42 | return self.authorizationStatus.asObservable() 43 | } 44 | 45 | func observeUpdatedLocation() -> Observable<[CLLocation]> { 46 | return PublishRelay<[CLLocation]>.create({ emitter in 47 | self.rx.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didUpdateLocations:))) 48 | .compactMap({ $0.last as? [CLLocation] }) 49 | .subscribe(onNext: { location in 50 | emitter.onNext(location) 51 | }) 52 | .disposed(by: self.disposeBag) 53 | return Disposables.create() 54 | }) 55 | } 56 | } 57 | 58 | extension DefaultLocationService: CLLocationManagerDelegate { 59 | func locationManager( 60 | _ manager: CLLocationManager, 61 | didUpdateLocations locations: [CLLocation]) { 62 | if let location = locations.first { 63 | print(self.location.value) 64 | } 65 | self.location.accept(locations) 66 | } 67 | 68 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { 69 | self.authorizationStatus.accept(status) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Map/LocationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationService.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2023/07/24. 6 | // 7 | 8 | import CoreLocation 9 | import Foundation 10 | 11 | import RxSwift 12 | import RxRelay 13 | 14 | protocol LocationService { 15 | var authorizationStatus: BehaviorRelay { get set } 16 | var location: BehaviorRelay<[CLLocation]> { get set } 17 | func start() 18 | func stop() 19 | func requestAuthorization() 20 | func observeUpdatedAuthorization() -> Observable 21 | func observeUpdatedLocation() -> Observable<[CLLocation]> 22 | } 23 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Map/NavigationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationView.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/15. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxSwift 11 | import RxCocoa 12 | import SnapKit 13 | import Then 14 | 15 | final class NavigationView: UIView { 16 | 17 | private lazy var logoImageView = UIImageView() 18 | fileprivate lazy var menuButton = UIButton() 19 | 20 | override init(frame: CGRect) { 21 | super.init(frame: frame) 22 | 23 | setupView() 24 | setupConstraints() 25 | } 26 | 27 | @available(*, unavailable) 28 | required init?(coder: NSCoder) { 29 | super.init(coder: coder) 30 | } 31 | 32 | private func setupView() { 33 | 34 | self.backgroundColor = .white 35 | 36 | logoImageView.do { 37 | $0.contentMode = .scaleAspectFit 38 | $0.image = FogImage.logo 39 | } 40 | 41 | menuButton.do { 42 | $0.setImage(FogImage.hamburger, for: .normal) 43 | } 44 | 45 | [logoImageView, menuButton] 46 | .forEach { addSubview($0) } 47 | } 48 | 49 | private func setupConstraints() { 50 | 51 | logoImageView.snp.makeConstraints { 52 | $0.leading.equalToSuperview().inset(15) 53 | $0.bottom.equalToSuperview().inset(12) 54 | $0.width.equalTo(102) 55 | $0.height.equalTo(27) 56 | } 57 | 58 | menuButton.snp.makeConstraints { 59 | $0.trailing.equalToSuperview().inset(10) 60 | $0.centerY.equalTo(logoImageView.snp.centerY) 61 | $0.size.equalTo(30) 62 | } 63 | } 64 | } 65 | 66 | extension Reactive where Base: NavigationView { 67 | var menuButtonTapped: ControlEvent { 68 | base.menuButton.rx.tap 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Setting/Cell/SettingListTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingListTableViewCell.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/12/22. 6 | // 7 | 8 | import UIKit 9 | 10 | import SnapKit 11 | import Then 12 | 13 | final class SettingListTableViewCell: BaseTableViewCell { 14 | 15 | // MARK: Properties 16 | private let titleLabel = UILabel() 17 | 18 | // MARK: UI 19 | override func setStyle() { 20 | titleLabel.do { 21 | $0.font = .pretendardM(16) 22 | $0.textColor = .grayBlack 23 | } 24 | } 25 | 26 | override func setLayout() { 27 | contentView.addSubview(titleLabel) 28 | 29 | titleLabel.snp.makeConstraints { 30 | $0.top.leading.bottom.equalToSuperview().inset(16) 31 | } 32 | } 33 | 34 | // MARK: Custom Methods 35 | func setData(title: String) { 36 | titleLabel.text = title 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Setting/Cell/SettingNicknameTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingNicknameTableViewCell.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/12/22. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxCocoa 11 | import RxSwift 12 | import SnapKit 13 | import Then 14 | 15 | final class SettingNicknameTableViewCell: BaseTableViewCell { 16 | 17 | // MARK: Properties 18 | private let containerView = UIView() 19 | private let nicknameLabel = UILabel() 20 | private let editNicknameButton = UIButton() 21 | 22 | // MARK: UI 23 | override func setStyle() { 24 | contentView.backgroundColor = .grayGray10 25 | containerView.backgroundColor = .white 26 | 27 | nicknameLabel.do { 28 | $0.font = .pretendardB(20) 29 | $0.textColor = .grayBlack 30 | $0.text = "\(UserDefaults.nickname)님" 31 | $0.setTextStyle(targetStringList: ["님"], font: .pretendardM(20), color: .grayBlack) 32 | } 33 | 34 | editNicknameButton.do { 35 | $0.setTitle("닉네임 수정", for: .normal) 36 | $0.setTitleColor(.mainBlue4, for: .normal) 37 | $0.titleLabel?.font = .pretendardR(14) 38 | $0.setImage(FogImage.btnPen, for: .normal) 39 | } 40 | } 41 | 42 | override func setLayout() { 43 | contentView.addSubview(containerView) 44 | containerView.addSubviews([nicknameLabel, editNicknameButton]) 45 | 46 | containerView.snp.makeConstraints { 47 | $0.top.leading.trailing.equalToSuperview() 48 | $0.bottom.equalToSuperview().inset(6) 49 | } 50 | 51 | nicknameLabel.snp.makeConstraints { 52 | $0.top.equalToSuperview().inset(18) 53 | $0.leading.equalToSuperview().inset(16) 54 | $0.bottom.equalToSuperview().inset(29) 55 | } 56 | 57 | editNicknameButton.snp.makeConstraints { 58 | $0.trailing.equalToSuperview().inset(15) 59 | $0.centerY.equalTo(nicknameLabel.snp.centerY) 60 | } 61 | } 62 | } 63 | 64 | // MARK: - Custom Methods 65 | extension SettingNicknameTableViewCell { 66 | 67 | // 닉네임 수정 버튼 터치 이벤트 방출 메서드 68 | func editNicknameButtonDidTap() -> Signal { 69 | return editNicknameButton.rx.tap.asSignal() 70 | } 71 | 72 | // 닉네임 설정 메서드 73 | func setNickname(_ nickname: String) { 74 | nicknameLabel.text = nickname 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Setting/Cell/SettingTitleTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingTitleTableViewCell.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/12/22. 6 | // 7 | 8 | import UIKit 9 | 10 | import SnapKit 11 | import Then 12 | 13 | final class SettingTitleTableViewCell: BaseTableViewCell { 14 | 15 | // MARK: Properties 16 | private let titleLabel = UILabel() 17 | let lineView = UIView() 18 | 19 | // MARK: UI 20 | override func setStyle() { 21 | titleLabel.do { 22 | $0.font = .pretendardM(12) 23 | $0.textColor = .grayGray6 24 | } 25 | 26 | lineView.backgroundColor = .grayGray10 27 | } 28 | 29 | override func setLayout() { 30 | contentView.addSubviews([lineView, titleLabel]) 31 | 32 | lineView.snp.makeConstraints { 33 | $0.top.equalToSuperview().inset(10) 34 | $0.leading.trailing.equalToSuperview().inset(16) 35 | $0.height.equalTo(1) 36 | } 37 | 38 | titleLabel.snp.makeConstraints { 39 | $0.top.equalTo(lineView.snp.bottom).offset(26) 40 | $0.leading.bottom.equalToSuperview().inset(16) 41 | } 42 | } 43 | 44 | // MARK: Custom Methods 45 | func setData(title: String) { 46 | titleLabel.text = title 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Setting/Coordinator/DefaultSettingCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultSettingCoordinator.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/01/14. 6 | // 7 | 8 | import UIKit 9 | 10 | final class DefaultSettingCoordinator: SettingCoordinator { 11 | 12 | weak var finishDelegate: CoordinatorFinishDelegate? 13 | var navigationController: UINavigationController 14 | var childCoordinators = [Coordinator]() 15 | var type: CoordinatorCase { .setting } 16 | 17 | init(_ navigationController: UINavigationController) { 18 | self.navigationController = navigationController 19 | } 20 | 21 | func start() { 22 | showSettingViewController() 23 | } 24 | 25 | func finish() { 26 | finishDelegate? 27 | .didFinish(childCoordinator: self) 28 | } 29 | 30 | func showSettingViewController() { 31 | let settingViewModel = SettingViewModel(coordinator: self) 32 | let settingViewController = SettingViewController(viewModel: settingViewModel) 33 | navigationController.pushViewController(settingViewController, animated: true) 34 | } 35 | 36 | // 닉네임 수정 뷰로 이동 37 | func showEditNicknameViewController() { 38 | let editNicknameViewModel = MakeNicknameViewModel(coordinator: self) 39 | let editNicknameViewController = MakeNicknameViewController(viewModel: editNicknameViewModel) 40 | navigationController.pushViewController(editNicknameViewController, animated: true) 41 | editNicknameViewController.setNaviTitle(.edit) 42 | } 43 | } 44 | 45 | // MARK: - CoordinatorFinishDelegate 46 | extension DefaultSettingCoordinator: CoordinatorFinishDelegate { 47 | 48 | func didFinish(childCoordinator: Coordinator) { 49 | self.childCoordinators = self.childCoordinators.filter { $0.type != childCoordinator.type } 50 | childCoordinator.navigationController.popToRootViewController(animated: true) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Setting/Coordinator/Protocol/SettingCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingCoordinator.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/01/14. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol SettingCoordinator: Coordinator { 11 | func showSettingViewController() 12 | func showEditNicknameViewController() 13 | } 14 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Setting/ViewController/SettingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingViewController.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/12/22. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxCocoa 11 | import RxSwift 12 | import SnapKit 13 | import Then 14 | 15 | final class SettingViewController: BaseViewController { 16 | 17 | // MARK: Properties 18 | private let navigationView = FogNavigationView() 19 | private let settingTableView = UITableView() 20 | private let logoutButton = UIButton() 21 | 22 | private let viewModel: SettingViewModel 23 | private let disposeBag = DisposeBag() 24 | 25 | // MARK: Init 26 | init(viewModel: SettingViewModel) { 27 | self.viewModel = viewModel 28 | super.init(nibName: nil, bundle: nil) 29 | } 30 | 31 | required init?(coder: NSCoder) { 32 | fatalError("SettingViewController Error!") 33 | } 34 | 35 | // MARK: Life Cycle 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | 39 | registerCell() 40 | } 41 | 42 | // MARK: UI 43 | override func setStyle() { 44 | view.backgroundColor = .white 45 | 46 | navigationView.setTitle("설정") 47 | 48 | logoutButton.do { 49 | $0.setTitle("로그아웃", for: .normal) 50 | $0.setTitleColor(.grayGray6, for: .normal) 51 | $0.titleLabel?.font = .pretendardM(14) 52 | $0.backgroundColor = .grayGray10 53 | } 54 | 55 | settingTableView.do { 56 | $0.separatorStyle = .none 57 | $0.backgroundColor = .white 58 | $0.rowHeight = UITableView.automaticDimension 59 | $0.isScrollEnabled = false 60 | } 61 | 62 | bindSettingTableView() 63 | } 64 | 65 | override func setLayout() { 66 | view.addSubviews([navigationView, settingTableView, logoutButton]) 67 | 68 | navigationView.snp.makeConstraints { 69 | $0.top.leading.trailing.equalToSuperview() 70 | $0.height.equalTo(92) 71 | } 72 | 73 | settingTableView.snp.makeConstraints { 74 | $0.top.equalTo(navigationView.snp.bottom) 75 | $0.leading.trailing.bottom.equalToSuperview() 76 | } 77 | 78 | logoutButton.snp.makeConstraints { 79 | $0.leading.trailing.bottom.equalToSuperview() 80 | $0.height.equalTo(64) 81 | } 82 | } 83 | 84 | private func bindSettingTableView() { 85 | let list = Observable.just(["", "약관 및 정책", "서비스 이용약관", "개인정보 처리 방침", "위치기반서비스 이용약관", "기타", "회원 탈퇴"]) 86 | 87 | list.bind(to: settingTableView.rx.items) { (tableView, index, item) in 88 | let indexPath = IndexPath(row: index, section: 0) 89 | guard let editNicknameCell = tableView.dequeueReusableCell(withIdentifier: SettingNicknameTableViewCell.className, for: indexPath) as? SettingNicknameTableViewCell, 90 | let titleCell = tableView.dequeueReusableCell(withIdentifier: SettingTitleTableViewCell.className, for: indexPath) as? SettingTitleTableViewCell, 91 | let settingCell = tableView.dequeueReusableCell(withIdentifier: SettingListTableViewCell.className, for: indexPath) as? SettingListTableViewCell else { return UITableViewCell() } 92 | 93 | switch indexPath.row { 94 | case 0: 95 | let input = SettingViewModel.Input(viewWillAppear: self.rx.viewWillAppear, 96 | tapBackButton: self.navigationView.backButtonDidTap(), 97 | tapEditNicknameButton: editNicknameCell.editNicknameButtonDidTap()) 98 | let output = self.viewModel.transform(input: input) 99 | 100 | output.didBackButtonTapped 101 | .emit() 102 | .disposed(by: self.disposeBag) 103 | 104 | output.didEditNicknameButtonTapped 105 | .emit() 106 | .disposed(by: self.disposeBag) 107 | 108 | output.nickname 109 | .subscribe(onNext: { result in 110 | editNicknameCell.setNickname(result) 111 | }) 112 | .disposed(by: self.disposeBag) 113 | 114 | return editNicknameCell 115 | case 1: 116 | titleCell.lineView.isHidden = true 117 | titleCell.setData(title: item) 118 | return titleCell 119 | case 5: 120 | titleCell.setData(title: item) 121 | return titleCell 122 | default: 123 | settingCell.setData(title: item) 124 | return settingCell 125 | } 126 | } 127 | .disposed(by: disposeBag) 128 | 129 | } 130 | 131 | // MARK: Custom Methods 132 | private func registerCell() { 133 | settingTableView.register(SettingNicknameTableViewCell.self, forCellReuseIdentifier: SettingNicknameTableViewCell.className) 134 | settingTableView.register(SettingTitleTableViewCell.self, forCellReuseIdentifier: SettingTitleTableViewCell.className) 135 | settingTableView.register(SettingListTableViewCell.self, forCellReuseIdentifier: SettingListTableViewCell.className) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Setting/ViewModel/SettingViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingViewModel.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/12/22. 6 | // 7 | 8 | import Foundation 9 | 10 | import RxCocoa 11 | import RxSwift 12 | 13 | final class SettingViewModel: ViewModelType { 14 | 15 | var disposeBag = DisposeBag() 16 | private weak var coordinator: SettingCoordinator? 17 | 18 | init(coordinator: SettingCoordinator?) { 19 | self.coordinator = coordinator 20 | } 21 | 22 | struct Input { 23 | let viewWillAppear: ControlEvent 24 | let tapBackButton: Signal 25 | let tapEditNicknameButton: Signal 26 | } 27 | 28 | struct Output { 29 | let nickname: BehaviorSubject 30 | let didBackButtonTapped: Signal 31 | let didEditNicknameButtonTapped: Signal 32 | } 33 | 34 | let nickname = BehaviorSubject(value: UserDefaults.nickname) 35 | 36 | func transform(input: Input) -> Output { 37 | let didBackButtonTapped = PublishRelay() 38 | let didEditNicknameButtonTapped = PublishRelay() 39 | let output = Output(nickname: nickname, 40 | didBackButtonTapped: didBackButtonTapped.asSignal(), 41 | didEditNicknameButtonTapped: didEditNicknameButtonTapped.asSignal()) 42 | 43 | input.viewWillAppear 44 | .subscribe(with: self, onNext: { owner, _ in 45 | owner.nickname.onNext(UserDefaults.nickname) 46 | }) 47 | .disposed(by: disposeBag) 48 | 49 | input.tapBackButton 50 | .withUnretained(self) 51 | .emit { owner, _ in 52 | owner.coordinator?.finish() 53 | } 54 | .disposed(by: disposeBag) 55 | 56 | input.tapEditNicknameButton 57 | .withUnretained(self) 58 | .emit { owner, _ in 59 | owner.coordinator?.showEditNicknameViewController() 60 | } 61 | .disposed(by: disposeBag) 62 | 63 | return output 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/SmokingArea/Protocol/Contents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Contents.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/04/20. 6 | // 7 | 8 | import Foundation 9 | 10 | // Contents 인터페이스 11 | // - 바텀 뷰의 데이터와 연결되어 사용됩니다. 12 | protocol Contents {} 13 | 14 | extension SmokingAreaResponseModel: Contents {} 15 | extension MessageModel: Contents {} 16 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/SmokingArea/Protocol/Presentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Presentable.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/04/20. 6 | // 7 | 8 | import UIKit 9 | 10 | /// 해당 프로토콜은 BottomViewController에서 사용된다. 11 | /// 하단에서 올라오는 뷰에 채택한 뒤 사용한다. 12 | protocol Presentable { 13 | func viewHeight() -> CGFloat 14 | func bind(_ content: Contents) // 데이터 바인딩 15 | func show(withMovement: CGFloat, withDuration: CGFloat) // 위로 올리는 메소드 16 | } 17 | 18 | extension Presentable where Self: UIView { 19 | 20 | func show(withMovement: CGFloat, withDuration: CGFloat) { 21 | self.snp.updateConstraints { 22 | $0.bottom.equalToSuperview().inset(withMovement) 23 | } 24 | 25 | guard let superview else { fatalError("Has not superview") } 26 | UIView.animate(withDuration: withDuration, animations: superview.layoutIfNeeded) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/SmokingArea/View/SmokingAreaMessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmokingAreaMessageView.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/04/20. 6 | // 7 | 8 | import UIKit 9 | 10 | import SnapKit 11 | import Then 12 | 13 | final class SmokingAreaMessageView: BaseView, Presentable { 14 | 15 | // MARK: UI 16 | 17 | private let containerView = UIView() 18 | private let messageLabel = UILabel() 19 | private let moveLocationButton = UIButton() 20 | 21 | override func setStyle() { 22 | super.setStyle() 23 | 24 | containerView.do { 25 | $0.clipsToBounds = false 26 | $0.backgroundColor = .grayGray1 27 | $0.alpha = 0.5 28 | $0.makeRounded(cornerRadius: 10.adjustedH) 29 | } 30 | 31 | messageLabel.do { 32 | $0.textColor = .grayWhite 33 | $0.textAlignment = .center 34 | $0.font = .pretendardM(14) 35 | } 36 | 37 | moveLocationButton.do { 38 | $0.setImage(FogImage.locationInactive, for: .normal) 39 | $0.contentMode = .scaleAspectFill 40 | } 41 | } 42 | 43 | override func setLayout() { 44 | super.setLayout() 45 | 46 | addSubview(containerView) 47 | containerView.snp.makeConstraints { 48 | $0.centerX.equalToSuperview() 49 | $0.width.equalTo(254.adjusted) 50 | $0.height.equalTo(46.adjustedH) 51 | $0.bottom.equalTo(47.adjustedH) 52 | } 53 | 54 | containerView.addSubview(messageLabel) 55 | messageLabel.snp.makeConstraints { 56 | $0.directionalHorizontalEdges.equalToSuperview().inset(13.adjusted) 57 | $0.centerY.equalToSuperview() 58 | } 59 | 60 | addSubview(moveLocationButton) 61 | moveLocationButton.snp.makeConstraints { 62 | $0.size.equalTo(62.adjustedH) 63 | $0.leading.equalToSuperview().offset(-4.adjusted) 64 | $0.bottom.equalTo(containerView.snp.top).offset(-38.adjustedH) 65 | } 66 | } 67 | 68 | func bind(_ content: Contents) { 69 | guard let content = content as? MessageModel else { return } 70 | messageLabel.text = content.contents 71 | } 72 | 73 | func viewHeight() -> CGFloat { 74 | return containerView.bounds.height 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/SmokingArea/ViewModel/SmokingAreaDetailViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmokingAreaDetailViewModel.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2022/11/20. 6 | // 7 | 8 | import Foundation 9 | 10 | final class SmokingAreaDetailViewModel { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Presentation/Splash/View/SplashViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashViewController.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2022/12/03. 6 | // 7 | 8 | import UIKit 9 | 10 | final class SplashViewController: BaseViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Colors/Color.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/.gitkeep -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/FogFont.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogFont.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/15. 6 | // 7 | 8 | import Foundation 9 | 10 | enum FogFont: String, CaseIterable { 11 | 12 | case black = "Pretendard-Black" 13 | case bold = "Pretendard-Bold" 14 | case extraBold = "Pretendard-ExtraBold" 15 | case extraLight = "Pretendard-ExtraLight" 16 | case light = "Pretendard-Light" 17 | case medium = "Pretendard-Medium" 18 | case regular = "Pretendard-Regular" 19 | case semibold = "Pretendard-SemiBold" 20 | case thin = "Pretendard-Thin" 21 | } 22 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Black.otf -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Bold.otf -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-ExtraBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-ExtraBold.otf -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-ExtraLight.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-ExtraLight.otf -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Light.otf -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Medium.otf -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Regular.otf -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-SemiBold.otf -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Fonts/Pretendard-Thin.otf -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Asset.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 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Asset.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 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Asset.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/FogImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogImage.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/15. 6 | // 7 | 8 | import UIKit.UIImage 9 | 10 | public enum FogImage { 11 | 12 | static let logo = UIImage(named: "fogfog_logo")! 13 | static let hamburger = UIImage(named: "ic_ham")! 14 | static let locationInactive = UIImage(named: "loca_inactive")! 15 | static let minus = UIImage(named: "ic_minus")! 16 | static let plus = UIImage(named: "ic_plus")! 17 | static let check = UIImage(named: "btnCheck") 18 | static let mapImage = UIImage(named: "mapImage")! 19 | static let speechBubble = UIImage(named: "speechBubbleSmall")! 20 | static let appleLogo = UIImage(named: "appleLogo")! 21 | static let kakaoLogo = UIImage(named: "kakaoLogol")! 22 | static let btnMap = UIImage(named: "btnMap")! 23 | static let btnNotice = UIImage(named: "btnNotice")! 24 | static let btnSet = UIImage(named: "btnSet")! 25 | static let btnBack = UIImage(named: "btnBack")! 26 | static let placeMarker = UIImage(named: "fluentLocation16Filled")! 27 | static let wordMark = UIImage(named: "wordmark")! 28 | static let deleteTextIcon = UIImage(named: "iconDeletetext")! 29 | static let errorIcon = UIImage(named: "inputTextIocnNotice")! 30 | static let popupMap = UIImage(named: "iconPopupMap")! 31 | static let speechBubbleBig = UIImage(named: "speechBubbleBig")! 32 | static let btnX = UIImage(named: "btnX")! 33 | static let btnPen = UIImage(named: "btnPen")! 34 | static let siren = UIImage(named: "ic_siren") 35 | static let pinActive = UIImage(named: "pinActive")! 36 | } 37 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/appleLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "appleLogo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "appleLogo@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "appleLogo@3x.png", 15 | "scale" : "3x", 16 | "idiom" : "universal" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/appleLogo.imageset/appleLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/appleLogo.imageset/appleLogo.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/appleLogo.imageset/appleLogo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/appleLogo.imageset/appleLogo@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/appleLogo.imageset/appleLogo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/appleLogo.imageset/appleLogo@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnBack.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btnBack.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "btnBack@2x.png", 10 | "scale" : "2x", 11 | "idiom" : "universal" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "btnBack@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnBack.imageset/btnBack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnBack.imageset/btnBack.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnBack.imageset/btnBack@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnBack.imageset/btnBack@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnBack.imageset/btnBack@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnBack.imageset/btnBack@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnCheck.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "btnCheck.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "scale" : "2x", 10 | "idiom" : "universal", 11 | "filename" : "btnCheck@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "btnCheck@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnCheck.imageset/btnCheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnCheck.imageset/btnCheck.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnCheck.imageset/btnCheck@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnCheck.imageset/btnCheck@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnCheck.imageset/btnCheck@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnCheck.imageset/btnCheck@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnMap.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "btnMap.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "btnMap@2x.png" 12 | }, 13 | { 14 | "scale" : "3x", 15 | "filename" : "btnMap@3x.png", 16 | "idiom" : "universal" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnMap.imageset/btnMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnMap.imageset/btnMap.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnMap.imageset/btnMap@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnMap.imageset/btnMap@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnMap.imageset/btnMap@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnMap.imageset/btnMap@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnNotice.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btnNotice.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "btnNotice@2x.png" 12 | }, 13 | { 14 | "scale" : "3x", 15 | "idiom" : "universal", 16 | "filename" : "btnNotice@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnNotice.imageset/btnNotice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnNotice.imageset/btnNotice.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnNotice.imageset/btnNotice@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnNotice.imageset/btnNotice@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnNotice.imageset/btnNotice@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnNotice.imageset/btnNotice@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnPen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "btnPen.png" 7 | }, 8 | { 9 | "filename" : "btnPen@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "btnPen@3x.png", 15 | "scale" : "3x", 16 | "idiom" : "universal" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnPen.imageset/btnPen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnPen.imageset/btnPen.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnPen.imageset/btnPen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnPen.imageset/btnPen@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnPen.imageset/btnPen@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnPen.imageset/btnPen@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnSet.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "btnSet.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "btnSet@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "btnSet@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnSet.imageset/btnSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnSet.imageset/btnSet.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnSet.imageset/btnSet@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnSet.imageset/btnSet@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnSet.imageset/btnSet@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnSet.imageset/btnSet@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnX.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "scale" : "1x", 5 | "idiom" : "universal", 6 | "filename" : "btnX.png" 7 | }, 8 | { 9 | "scale" : "2x", 10 | "idiom" : "universal", 11 | "filename" : "btnX@2x.png" 12 | }, 13 | { 14 | "filename" : "btnX@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnX.imageset/btnX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnX.imageset/btnX.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnX.imageset/btnX@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnX.imageset/btnX@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnX.imageset/btnX@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/btnX.imageset/btnX@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fluentLocation16Filled.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "scale" : "1x", 5 | "idiom" : "universal", 6 | "filename" : "fluentLocation16Filled.png" 7 | }, 8 | { 9 | "filename" : "fluentLocation16Filled@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "fluentLocation16Filled@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fluentLocation16Filled.imageset/fluentLocation16Filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fluentLocation16Filled.imageset/fluentLocation16Filled.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fluentLocation16Filled.imageset/fluentLocation16Filled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fluentLocation16Filled.imageset/fluentLocation16Filled@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fluentLocation16Filled.imageset/fluentLocation16Filled@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fluentLocation16Filled.imageset/fluentLocation16Filled@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fogfog_logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "fogfog_logo.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "fogfog_logo@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "fogfog_logo@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fogfog_logo.imageset/fogfog_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fogfog_logo.imageset/fogfog_logo.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fogfog_logo.imageset/fogfog_logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fogfog_logo.imageset/fogfog_logo@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fogfog_logo.imageset/fogfog_logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/fogfog_logo.imageset/fogfog_logo@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_ham.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ic_ham.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ic_ham@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ic_ham@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_ham.imageset/ic_ham.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_ham.imageset/ic_ham.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_ham.imageset/ic_ham@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_ham.imageset/ic_ham@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_ham.imageset/ic_ham@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_ham.imageset/ic_ham@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_location.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "loca.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "loca@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "loca@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_location.imageset/loca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_location.imageset/loca.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_location.imageset/loca@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_location.imageset/loca@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_location.imageset/loca@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_location.imageset/loca@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_minus.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ic_minus.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ic_minus@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ic_minus@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_minus.imageset/ic_minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_minus.imageset/ic_minus.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_minus.imageset/ic_minus@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_minus.imageset/ic_minus@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_minus.imageset/ic_minus@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_minus.imageset/ic_minus@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_plus.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ic_plus.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ic_plus@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ic_plus@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_plus.imageset/ic_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_plus.imageset/ic_plus.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_plus.imageset/ic_plus@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_plus.imageset/ic_plus@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_plus.imageset/ic_plus@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_plus.imageset/ic_plus@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_siren.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ph_siren.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "ph_siren@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "ph_siren@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_siren.imageset/ph_siren.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_siren.imageset/ph_siren.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_siren.imageset/ph_siren@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_siren.imageset/ph_siren@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_siren.imageset/ph_siren@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/ic_siren.imageset/ph_siren@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconDeletetext.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "iconDeletetext.png", 5 | "scale" : "1x", 6 | "idiom" : "universal" 7 | }, 8 | { 9 | "filename" : "iconDeletetext@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "scale" : "3x", 15 | "idiom" : "universal", 16 | "filename" : "iconDeletetext@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconDeletetext.imageset/iconDeletetext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconDeletetext.imageset/iconDeletetext.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconDeletetext.imageset/iconDeletetext@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconDeletetext.imageset/iconDeletetext@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconDeletetext.imageset/iconDeletetext@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconDeletetext.imageset/iconDeletetext@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconPopupMap.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "map.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "map@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "map@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconPopupMap.imageset/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconPopupMap.imageset/map.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconPopupMap.imageset/map@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconPopupMap.imageset/map@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconPopupMap.imageset/map@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/iconPopupMap.imageset/map@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/inputTextIocnNotice.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "scale" : "1x", 5 | "idiom" : "universal", 6 | "filename" : "inputTextIocnNotice.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "inputTextIocnNotice@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "inputTextIocnNotice@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/inputTextIocnNotice.imageset/inputTextIocnNotice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/inputTextIocnNotice.imageset/inputTextIocnNotice.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/inputTextIocnNotice.imageset/inputTextIocnNotice@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/inputTextIocnNotice.imageset/inputTextIocnNotice@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/inputTextIocnNotice.imageset/inputTextIocnNotice@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/inputTextIocnNotice.imageset/inputTextIocnNotice@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/invalidName.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "invalidName.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "scale" : "2x", 10 | "filename" : "invalidName@2x.png", 11 | "idiom" : "universal" 12 | }, 13 | { 14 | "filename" : "invalidName@3x.png", 15 | "scale" : "3x", 16 | "idiom" : "universal" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/invalidName.imageset/invalidName.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/invalidName.imageset/invalidName.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/invalidName.imageset/invalidName@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/invalidName.imageset/invalidName@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/invalidName.imageset/invalidName@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/invalidName.imageset/invalidName@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/kakaoLogol.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "kakaoLogol.png" 7 | }, 8 | { 9 | "filename" : "kakaoLogol@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "kakaoLogol@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/kakaoLogol.imageset/kakaoLogol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/kakaoLogol.imageset/kakaoLogol.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/kakaoLogol.imageset/kakaoLogol@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/kakaoLogol.imageset/kakaoLogol@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/kakaoLogol.imageset/kakaoLogol@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/kakaoLogol.imageset/kakaoLogol@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/loca_inactive.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "loca_inactive.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "loca_inactive@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "loca_inactive@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/loca_inactive.imageset/loca_inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/loca_inactive.imageset/loca_inactive.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/loca_inactive.imageset/loca_inactive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/loca_inactive.imageset/loca_inactive@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/loca_inactive.imageset/loca_inactive@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/loca_inactive.imageset/loca_inactive@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/mapImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "scale" : "1x", 5 | "filename" : "mapImage.png", 6 | "idiom" : "universal" 7 | }, 8 | { 9 | "scale" : "2x", 10 | "filename" : "mapImage@2x.png", 11 | "idiom" : "universal" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "mapImage@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/mapImage.imageset/mapImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/mapImage.imageset/mapImage.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/mapImage.imageset/mapImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/mapImage.imageset/mapImage@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/mapImage.imageset/mapImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/mapImage.imageset/mapImage@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/pinActive.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pinActive.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "pinActive@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "pinActive@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/pinActive.imageset/pinActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/pinActive.imageset/pinActive.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/pinActive.imageset/pinActive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/pinActive.imageset/pinActive@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/pinActive.imageset/pinActive@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/pinActive.imageset/pinActive@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleBig.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "speech bubble_big.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "speech bubble_big@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "speech bubble_big@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleBig.imageset/speech bubble_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleBig.imageset/speech bubble_big.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleBig.imageset/speech bubble_big@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleBig.imageset/speech bubble_big@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleBig.imageset/speech bubble_big@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleBig.imageset/speech bubble_big@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleSmall.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "speechBubbleSmall.png" 7 | }, 8 | { 9 | "filename" : "speechBubbleSmall@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "speechBubbleSmall@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleSmall.imageset/speechBubbleSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleSmall.imageset/speechBubbleSmall.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleSmall.imageset/speechBubbleSmall@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleSmall.imageset/speechBubbleSmall@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleSmall.imageset/speechBubbleSmall@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/speechBubbleSmall.imageset/speechBubbleSmall@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/wordmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "wordmark.png" 7 | }, 8 | { 9 | "filename" : "wordmark@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "wordmark@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/wordmark.imageset/wordmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/wordmark.imageset/wordmark.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/wordmark.imageset/wordmark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/wordmark.imageset/wordmark@2x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/wordmark.imageset/wordmark@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Resources/Image/Image.xcassets/wordmark.imageset/wordmark@3x.png -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Supports/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | 포그포그 7 | UIUserInterfaceStyle 8 | Light 9 | KAKAO_NATIVE_APP_KEY 10 | ${KAKAO_NATIVE_APP_KEY} 11 | CFBundleURLTypes 12 | 13 | 14 | CFBundleTypeRole 15 | Editor 16 | CFBundleURLSchemes 17 | 18 | kakao${KAKAO_NATIVE_APP_KEY} 19 | 20 | 21 | 22 | LSApplicationQueriesSchemes 23 | 24 | kakaokompassauth 25 | kakaolink 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | UIAppFonts 33 | 34 | Pretendard-Bold.otf 35 | Pretendard-SemiBold.otf 36 | Pretendard-Regular.otf 37 | Pretendard-Light.otf 38 | Pretendard-ExtraLight.otf 39 | Pretendard-Black.otf 40 | Pretendard-ExtraBold.otf 41 | Pretendard-Medium.otf 42 | Pretendard-Thin.otf 43 | 44 | UIApplicationSceneManifest 45 | 46 | UIApplicationSupportsMultipleScenes 47 | 48 | UISceneConfigurations 49 | 50 | UIWindowSceneSessionRoleApplication 51 | 52 | 53 | UISceneConfigurationName 54 | Default Configuration 55 | UISceneDelegateClassName 56 | $(PRODUCT_MODULE_NAME).SceneDelegate 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Analytics/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Utils/Analytics/.gitkeep -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Base/BaseNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseNavigationController.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/10/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseNavigationController: UINavigationController { 11 | 12 | private(set) var isTransitioning: Bool = false 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | self.navigationBar.isHidden = true 18 | self.interactivePopGestureRecognizer?.delegate = self 19 | self.delegate = self 20 | } 21 | 22 | override func pushViewController(_ viewController: UIViewController, animated: Bool) { 23 | isTransitioning = true 24 | 25 | super.pushViewController(viewController, animated: animated) 26 | } 27 | } 28 | 29 | // MARK: - UIGestureRecognizerDelegate 30 | 31 | extension BaseNavigationController: UIGestureRecognizerDelegate { 32 | 33 | func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 34 | guard gestureRecognizer == interactivePopGestureRecognizer else { 35 | return true 36 | } 37 | 38 | // 특정 뷰 컨트롤러에 대해서 Popped 되는 것을 막고 싶다면, preventInteractivePopGesture 오버라이딩해서 false로 수정 39 | if let viewController = visibleViewController as? BaseViewController, viewController.preventInteractivePopGesture() { 40 | return false 41 | } 42 | 43 | return viewControllers.count > 1 && isTransitioning == false 44 | } 45 | } 46 | 47 | // MARK: - UINavigationControllerDelegate 48 | 49 | extension BaseNavigationController: UINavigationControllerDelegate { 50 | 51 | func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { 52 | isTransitioning = false 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Base/BaseTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableViewCell.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/12/22. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseTableViewCell: UITableViewCell { 11 | 12 | // MARK: init 13 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 14 | super.init(style: style, reuseIdentifier: reuseIdentifier) 15 | setCommonAttributes() 16 | setStyle() 17 | setLayout() 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | 24 | /// 기본 attributes 설정 메서드 25 | private func setCommonAttributes() { 26 | selectionStyle = .none 27 | } 28 | 29 | func setStyle() { } 30 | 31 | func setLayout() { } 32 | } 33 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Base/BaseView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseView.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2022/11/20. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxSwift 11 | 12 | class BaseView: UIView { 13 | 14 | var disposeBag = DisposeBag() 15 | 16 | override init(frame: CGRect) { 17 | super.init(frame: frame) 18 | 19 | setStyle() 20 | setLayout() 21 | } 22 | 23 | @available(*, unavailable) 24 | required init?(coder: NSCoder) { 25 | super.init(coder: coder) 26 | } 27 | 28 | func setStyle() {} 29 | func setLayout() {} 30 | } 31 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Base/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/14. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxSwift 11 | 12 | protocol InteractivePopGesture: AnyObject { 13 | /// Pop Gesture를 막을지 결정하는 함수, default: false 14 | /// 15 | /// 특정 뷰 컨트롤러만 제스처를 막고 싶다면 BaseViewController를 상속 받은 뷰 컨트롤러에서 16 | /// 다음 함수를 오버라이드 한 뒤, true로 리턴해주도록 하면 된다. 17 | func preventInteractivePopGesture() -> Bool 18 | } 19 | 20 | class BaseViewController: UIViewController, InteractivePopGesture { 21 | 22 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 23 | super.init(nibName: nil, bundle: nil) 24 | } 25 | 26 | @available(*, unavailable) 27 | required init?(coder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | // MARK: Life Cycle 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | setStyle() 36 | setLayout() 37 | } 38 | 39 | override func viewWillAppear(_ animated: Bool) { 40 | super.viewWillAppear(animated) 41 | 42 | navigationController?.setNavigationBarHidden(true, animated: true) 43 | } 44 | 45 | func preventInteractivePopGesture() -> Bool { 46 | return false 47 | } 48 | 49 | // MARK: UI 50 | 51 | /// Attributes (속성) 설정 메서드 52 | func setStyle() { 53 | view.backgroundColor = .white 54 | } 55 | 56 | /// Hierarchy, Constraints (계층 및 제약조건) 설정 메서드 57 | func setLayout() {} 58 | } 59 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Class/Keychain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keychain.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/07/28. 6 | // 7 | 8 | import Foundation 9 | import Security 10 | 11 | final class Keychain { 12 | 13 | // Create 14 | static func create(key: String, data: String) { 15 | let query: NSDictionary = [ 16 | kSecClass: kSecClassGenericPassword, 17 | kSecAttrAccount: key, 18 | kSecValueData: data.data(using: .utf8, allowLossyConversion: false) as Any 19 | ] 20 | SecItemDelete(query) // Keychain은 Key값에 중복이 생기면 저장할 수 없기 때문에 먼저 Delete 21 | 22 | let status = SecItemAdd(query, nil) 23 | assert(status == noErr, "Failed to save Token") 24 | } 25 | 26 | // Read 27 | static func read(key: String) -> String? { 28 | let query: NSDictionary = [ 29 | kSecClass: kSecClassGenericPassword, 30 | kSecAttrAccount: key, 31 | kSecReturnData: kCFBooleanTrue as Any, 32 | kSecMatchLimit: kSecMatchLimitOne 33 | ] 34 | 35 | var dataTypeRef: AnyObject? 36 | let status = SecItemCopyMatching(query, &dataTypeRef) 37 | 38 | if status == errSecSuccess { 39 | if let retrievedData: Data = dataTypeRef as? Data { 40 | let value = String(data: retrievedData, encoding: String.Encoding.utf8) 41 | return value 42 | } else { return nil } 43 | } else { 44 | #if DEBUG 45 | print("Failed to loading, status code = \(status)") 46 | #endif 47 | return nil 48 | } 49 | } 50 | 51 | // Delete 52 | static func delete(key: String) { 53 | let query: NSDictionary = [ 54 | kSecClass: kSecClassGenericPassword, 55 | kSecAttrAccount: key 56 | ] 57 | 58 | let status = SecItemDelete(query) 59 | if status != errSecSuccess && status != errSecItemNotFound { 60 | print("failed to delete") 61 | SecCopyErrorMessageString(status, nil) 62 | // TODO: 오류 팝업 (팝업에서 확인 버튼 클릭 시 retry 1회 하도록) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Contstant/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/05/29. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Config { 11 | 12 | static var kakaoNativeAppKey: String { 13 | guard let key = Bundle.main.infoDictionary?["KAKAO_NATIVE_APP_KEY"] as? String else { 14 | assertionFailure("KAKAO_NATIVE_APP_KEY could not found.") 15 | return "" 16 | } 17 | return key 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Contstant/CoordinatorCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoordinatorCase.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/17. 6 | // 7 | 8 | import Foundation 9 | 10 | enum CoordinatorCase { 11 | case app 12 | case login 13 | case map 14 | case setting 15 | } 16 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Contstant/NotificationCenterKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationCenterKey.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/09/29. 6 | // 7 | 8 | import Foundation 9 | 10 | enum NotificationCenterKey { 11 | static let refreshTokenHasExpired = Notification.Name("refreshTokenHasExpired") 12 | } 13 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Contstant/UserDefaultsKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultsKey.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/10/17. 6 | // 7 | 8 | import Foundation 9 | 10 | enum UserDefaultsKey: String, CaseIterable { 11 | case nickname 12 | case userId 13 | case isFirstLaunch 14 | } 15 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Utils/Extension/.gitkeep -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/Adjusted + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Adjusted + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/11/19. 6 | // 7 | 8 | import UIKit 9 | 10 | /** 11 | - Description: 12 | 스크린 너비 375를 기준으로 디자인이 나왔을 때 현재 기기의 스크린 사이즈에 비례하는 수치를 Return합니다. 13 | 14 | - Note: 15 | 기기별 대응에 사용하면 됩니다. 16 | ex) (size: 20.adjusted) 17 | */ 18 | extension CGFloat { 19 | var adjusted: CGFloat { 20 | let ratio: CGFloat = UIScreen.main.bounds.width / 375 21 | return self * ratio 22 | } 23 | 24 | var adjustedH: CGFloat { 25 | let ratio: CGFloat = UIScreen.main.bounds.height / 812 26 | return self * ratio 27 | } 28 | } 29 | 30 | extension Double { 31 | var adjusted: Double { 32 | let ratio: Double = Double(UIScreen.main.bounds.width / 375) 33 | return self * ratio 34 | } 35 | 36 | var adjustedH: Double { 37 | let ratio: Double = Double(UIScreen.main.bounds.height / 812) 38 | return self * ratio 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/Bundle + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/16. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Bundle { 11 | 12 | var apiKey: String { 13 | 14 | guard let file = self.path(forResource: "GoogleMap", ofType: "plist") else { return "" } 15 | guard let resource = NSDictionary(contentsOfFile: file) else { return "" } 16 | guard let key = resource["API_KEY"] as? String else { fatalError("API_KEY를 설정해주세요")} 17 | return key 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/Keychain + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keychain + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/07/28. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Keychain { 11 | 12 | enum Keys { 13 | static let accessToken = "accessToken" 14 | static let refreshToken = "refreshToken" 15 | static let socialType = "socialType" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/Reactive + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Reactive + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/09/08. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxCocoa 11 | import RxSwift 12 | 13 | extension Reactive where Base: UIViewController { 14 | 15 | var viewDidLoad: ControlEvent { 16 | let source = self.methodInvoked(#selector(Base.viewDidLoad)).map { _ in } 17 | return ControlEvent(events: source) 18 | } 19 | 20 | var viewWillAppear: ControlEvent { 21 | let source = self.methodInvoked(#selector(Base.viewWillAppear)).map { $0.first as? Bool ?? false } 22 | return ControlEvent(events: source) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/RxMoya + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxMoya + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/04/04. 6 | // 7 | 8 | import Foundation 9 | 10 | import Moya 11 | import RxSwift 12 | 13 | extension PrimitiveSequence where Trait == SingleTrait, Element == Moya.Response { 14 | 15 | var decoder: JSONDecoder { 16 | let decoder = JSONDecoder() 17 | return decoder 18 | } 19 | 20 | /// Wrap to common response 21 | /// - Common response로 감싼 객체로 매핑해주는 메소드 22 | func map( 23 | _ type: Response.Type 24 | ) -> PrimitiveSequence> { 25 | return map(CommonResponse.self, using: decoder) 26 | } 27 | 28 | /// Map to pure 29 | /// - Pure data로 매핑해주는 메소드(아래 설명 참조) 30 | /// - 옵셔널 타입으로 반환합니다. 31 | /* 32 | - statusCode 33 | - success 34 | - message 35 | - data? <- Pure data in our service 36 | */ 37 | func map( 38 | _ type: Response.Type 39 | ) -> PrimitiveSequence { 40 | return map(Response.self).map { $0.data } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/UIColor + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/11/18. 6 | // 7 | 8 | import UIKit.UIColor 9 | 10 | extension UIColor { 11 | 12 | @nonobjc class var fogBlue: UIColor { 13 | return UIColor(red: 91.0 / 255.0 , green: 157.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0) 14 | } 15 | 16 | @nonobjc class var shadowGray: UIColor { 17 | return UIColor(red: 28.0 / 255.0, green: 76.0 / 255.0, blue: 200.0 / 255.0, alpha: 1.0) 18 | } 19 | 20 | @nonobjc class var shadowGray2: UIColor { 21 | return UIColor(red: 204.0 / 255.0, green: 204.0 / 255.0, blue: 204.0 / 255.0, alpha: 1.0) 22 | } 23 | 24 | @nonobjc class var grayBlack: UIColor { 25 | return UIColor(red: 21.0 / 255.0, green: 22.0 / 255.0, blue: 24.0 / 255.0, alpha: 1.0) 26 | } 27 | 28 | @nonobjc class var grayGray1: UIColor { 29 | return UIColor(red: 44.0 / 255.0, green: 45.0 / 255.0, blue: 48.0 / 255.0, alpha: 1.0) 30 | } 31 | 32 | @nonobjc class var grayGray7: UIColor { 33 | return UIColor(red: 185.0 / 255.0, green: 185.0 / 255.0, blue: 186.0 / 255.0, alpha: 1.0) 34 | } 35 | 36 | @nonobjc class var grayGray3: UIColor { 37 | return UIColor(red: 91.0 / 255.0, green: 92.0 / 255.0, blue: 94.0 / 255.0, alpha: 1.0) 38 | } 39 | 40 | @nonobjc class var grayGray8: UIColor { 41 | return UIColor(red: 208.0 / 255.0, green: 208.0 / 255.0, blue: 209.0 / 255.0, alpha: 1.0) 42 | } 43 | 44 | @nonobjc class var grayWhite: UIColor { 45 | return UIColor(white: 1.0, alpha: 1.0) 46 | } 47 | 48 | @nonobjc class var grayGray4: UIColor { 49 | return UIColor(red: 115.0 / 255.0, green: 115.0 / 255.0, blue: 117.0 / 255.0, alpha: 1.0) 50 | } 51 | 52 | @nonobjc class var grayGray9: UIColor { 53 | return UIColor(white: 232.0 / 255.0, alpha: 1.0) 54 | } 55 | 56 | @nonobjc class var grayGray2: UIColor { 57 | return UIColor(red: 68.0 / 255.0, green: 69.0 / 255.0, blue: 71.0 / 255.0, alpha: 1.0) 58 | } 59 | 60 | @nonobjc class var grayGray5: UIColor { 61 | return UIColor(red: 138.0 / 255.0, green: 138.0 / 255.0, blue: 140.0 / 255.0, alpha: 1.0) 62 | } 63 | 64 | @nonobjc class var grayGray6: UIColor { 65 | return UIColor(red: 161/255, green: 162/255, blue: 163/255, alpha: 1.0) 66 | } 67 | 68 | @nonobjc class var grayGray10: UIColor { 69 | return UIColor(white: 243.0 / 255.0, alpha: 1.0) 70 | } 71 | 72 | @nonobjc class var mainBlueMain2: UIColor { 73 | return UIColor(red: 79.0 / 255.0, green: 139.0 / 255.0, blue: 1.0, alpha: 1.0) 74 | } 75 | 76 | @nonobjc class var mainBlue2: UIColor { 77 | return UIColor(red: 55.0 / 255.0, green: 94.0 / 255.0, blue: 153.0 / 255.0, alpha: 1.0) 78 | } 79 | 80 | @nonobjc class var mainBlue4: UIColor { 81 | return UIColor(red: 124.0 / 255.0, green: 177.0 / 255.0, blue: 1.0, alpha: 1.0) 82 | } 83 | 84 | @nonobjc class var mainBlue1: UIColor { 85 | return UIColor(red: 36.0 / 255.0, green: 63.0 / 255.0, blue: 102.0 / 255.0, alpha: 1.0) 86 | } 87 | 88 | @nonobjc class var mainBlue5: UIColor { 89 | return UIColor(red: 157.0 / 255.0, green: 196.0 / 255.0, blue: 1.0, alpha: 1.0) 90 | } 91 | 92 | @nonobjc class var mainBlue8: UIColor { 93 | return UIColor(red: 239/255, green: 245/255, blue: 255/255, alpha: 1.0) 94 | } 95 | 96 | @nonobjc class var mainBlue9: UIColor { 97 | return UIColor(red: 245.0 / 255.0, green: 249.0 / 255.0, blue: 1.0, alpha: 1.0) 98 | } 99 | 100 | @nonobjc class var opacityGray1: UIColor { 101 | return UIColor(red: 21.0 / 255.0, green: 22.0 / 255.0, blue: 24.0 / 255.0, alpha: 0.5) 102 | } 103 | 104 | @nonobjc class var opacityGray2: UIColor { 105 | return UIColor(red: 21.0 / 255.0, green: 22.0 / 255.0, blue: 24.0 / 255.0, alpha: 0.65) 106 | } 107 | 108 | @nonobjc class var etcRed: UIColor { 109 | return UIColor(red: 251.0 / 255.0, green: 109.0 / 255.0, blue: 104.0 / 255.0, alpha: 1.0) 110 | } 111 | 112 | @nonobjc class var etcYellow: UIColor { 113 | return UIColor(red: 1.0, green: 219.0 / 255.0, blue: 30.0 / 255.0, alpha: 1.0) 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/UIFont + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/15. 6 | // 7 | 8 | import UIKit.UIFont 9 | 10 | extension UIFont { 11 | 12 | static func pretendardEL(_ size: CGFloat) -> UIFont { 13 | return UIFont(name: FogFont.extraLight.rawValue, size: size)! 14 | } 15 | 16 | static func pretendardL(_ size: CGFloat) -> UIFont { 17 | return UIFont(name: FogFont.light.rawValue, size: size)! 18 | } 19 | 20 | static func pretendardR(_ size: CGFloat) -> UIFont { 21 | return UIFont(name: FogFont.regular.rawValue, size: size)! 22 | } 23 | 24 | static func pretendardM(_ size: CGFloat) -> UIFont { 25 | return UIFont(name: FogFont.medium.rawValue, size: size)! 26 | } 27 | 28 | static func pretendardEB(_ size: CGFloat) -> UIFont { 29 | return UIFont(name: FogFont.extraBold.rawValue, size: size)! 30 | } 31 | 32 | static func pretendardB(_ size: CGFloat) -> UIFont { 33 | return UIFont(name: FogFont.bold.rawValue, size: size)! 34 | } 35 | 36 | static func pretendardSB(_ size: CGFloat) -> UIFont { 37 | return UIFont(name: FogFont.semibold.rawValue, size: size)! 38 | } 39 | 40 | static func pretendardT(_ size: CGFloat) -> UIFont { 41 | return UIFont(name: FogFont.thin.rawValue, size: size)! 42 | } 43 | 44 | static func pretendardBL(_ size: CGFloat) -> UIFont { 45 | return UIFont(name: FogFont.black.rawValue, size: size)! 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/UIImage + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2023/11/22. 6 | // 7 | 8 | import UIKit.UIImage 9 | 10 | extension UIImage { 11 | func resizedImage(sizeImage: CGSize) -> UIImage? { 12 | let frame = CGRect(origin: CGPoint.zero, size: CGSize(width: sizeImage.width, height: sizeImage.height)) 13 | UIGraphicsBeginImageContextWithOptions(frame.size, false, 0) 14 | draw(in: frame) 15 | let resizedImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext() 16 | UIGraphicsEndImageContext() 17 | withRenderingMode(.alwaysOriginal) 18 | return resizedImage 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/UILabel + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/11/25. 6 | // 7 | 8 | import UIKit.UILabel 9 | 10 | extension UILabel { 11 | 12 | /// 특정 문자열 폰트 및 컬러 변경 메서드 13 | func setTextStyle(targetStringList: [String], font: UIFont, color: UIColor) { 14 | let fullText = text ?? "" 15 | let attributedString = NSMutableAttributedString(string: fullText) 16 | targetStringList.forEach { 17 | let range = (fullText as NSString).range(of: $0) 18 | attributedString.addAttributes([.font: font as Any, .foregroundColor: color as Any], range: range) 19 | } 20 | attributedText = attributedString 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/UITextField + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/11/25. 6 | // 7 | 8 | import UIKit.UITextField 9 | 10 | extension UITextField { 11 | 12 | /// UITextField 왼쪽에 여백 주는 메서드 13 | func addLeftPadding(_ amount: CGFloat) { 14 | let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: amount, height: self.frame.size.height)) 15 | self.leftView = paddingView 16 | self.leftViewMode = .always 17 | } 18 | 19 | /// UITextField 오른쪽에 여백 주는 메서드 20 | func addRightPadding(_ amount: CGFloat) { 21 | let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: amount, height: self.frame.size.height)) 22 | self.rightView = paddingView 23 | self.rightViewMode = .always 24 | } 25 | 26 | /// clearButton 설정 메서드 27 | func setClearButton() { 28 | let clearButton = UIButton(type: .custom) 29 | clearButton.setImage(FogImage.deleteTextIcon, for: .normal) 30 | clearButton.frame = CGRect(x: 0, y: 0, width: 18, height: 18) 31 | clearButton.contentMode = .scaleAspectFit 32 | clearButton.addTarget(self, action: #selector(UITextField.clear(sender:)), for: .touchUpInside) 33 | 34 | let rightView = UIView(frame: CGRect(x: 0, y: 0, width: 34, height: 18)) 35 | rightView.addSubview(clearButton) 36 | 37 | self.rightView = rightView 38 | self.rightViewMode = .whileEditing 39 | } 40 | 41 | @objc 42 | private func clear(sender: AnyObject) { 43 | self.text = "" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/UIView + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/11/18. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIView { 11 | 12 | static var className: String { 13 | NSStringFromClass(self.classForCoder()).components(separatedBy: ".").last! 14 | } 15 | 16 | var className: String { 17 | NSStringFromClass(self.classForCoder).components(separatedBy: ".").last! 18 | } 19 | 20 | /// 다수의 뷰를 한번에 addSubview해주는 메서드 21 | func addSubviews(_ views: [UIView]) { 22 | views.forEach { self.addSubview($0) } 23 | } 24 | 25 | /// UIView 의 모서리가 둥근 정도를 설정하는 메서드 26 | func makeRounded(cornerRadius: CGFloat?) { 27 | if let cornerRadius = cornerRadius { 28 | self.layer.cornerRadius = cornerRadius 29 | } else { 30 | // cornerRadius 가 nil 일 경우의 default 31 | self.layer.cornerRadius = self.layer.frame.height / 2 32 | } 33 | self.layer.masksToBounds = true 34 | } 35 | 36 | /// gradient 설정 메서드 37 | func setGradient(_ color1: UIColor, _ color2: UIColor) { 38 | let gradient = CAGradientLayer() 39 | 40 | gradient.colors = [color1.cgColor, color2.cgColor] 41 | gradient.locations = [0.25, 0.7] 42 | gradient.startPoint = CGPoint(x: 1.0, y: 0.0) 43 | gradient.endPoint = CGPoint(x: 1.0, y: 1.0) 44 | gradient.frame = bounds 45 | 46 | layer.addSublayer(gradient) 47 | } 48 | 49 | /// UIView의 그림자를 설정하는 메서드 50 | func addShadow(offset: CGSize, color: UIColor, opacity: Float, radius: CGFloat) { 51 | self.layer.masksToBounds = false 52 | self.layer.shadowColor = color.cgColor 53 | self.layer.shadowOffset = offset 54 | self.layer.shadowOpacity = opacity 55 | self.layer.shadowRadius = radius 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/UIViewController + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/12/22. 6 | // 7 | 8 | import UIKit.UIViewController 9 | 10 | extension UIViewController { 11 | 12 | static var className: String { 13 | NSStringFromClass(self.classForCoder()).components(separatedBy: ".").last! 14 | } 15 | 16 | var className: String { 17 | NSStringFromClass(self.classForCoder).components(separatedBy: ".").last! 18 | } 19 | } 20 | 21 | extension UIViewController { 22 | 23 | /// Bottom Presentable View 추가 24 | func addBottomView(_ subview: UIView, belowSubview: UIView? = nil) { 25 | // 기존에 깔려있는 하위 뷰 제거 26 | hideBottomView() 27 | 28 | // 특정 하위 뷰 아래에 추가 또는 가장 하위 뷰로 추가 29 | if let belowSubview = belowSubview { 30 | view.insertSubview(subview, belowSubview: belowSubview) 31 | } else { 32 | view.addSubview(subview) 33 | } 34 | 35 | // 레이아웃 초기화 36 | let defaultBottomMargin = 20 37 | let sideMargin = 13.adjusted 38 | subview.snp.makeConstraints { 39 | $0.bottom.equalToSuperview().offset(defaultBottomMargin) 40 | $0.directionalHorizontalEdges.equalToSuperview().inset(sideMargin) 41 | } 42 | } 43 | 44 | func hideBottomView() { 45 | // 기존에 깔려있는 BottomView hide animation 및 제거 46 | if let bottomView = view.subviews.first(where: { return ($0 is Presentable) }) { 47 | bottomView.snp.updateConstraints { 48 | $0.bottom.equalToSuperview().offset(20) 49 | } 50 | 51 | UIView.animate(withDuration: 0.3, animations: view.layoutIfNeeded) { _ in 52 | bottomView.removeFromSuperview() 53 | } 54 | } 55 | } 56 | 57 | // 흡연 구역 카드 뷰 보여주기 메소드 58 | func showCardView( 59 | _ content: Contents, 60 | belowSubview: UIView? = nil, 61 | withDuration: TimeInterval = 0.5 62 | ) { 63 | let cardView = SmokingAreaCardView() 64 | cardView.bind(content) 65 | addBottomView(cardView, belowSubview: belowSubview) 66 | 67 | let bottomMargin = 49.adjustedH 68 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { 69 | cardView.show(withMovement: cardView.viewHeight() + bottomMargin, withDuration: withDuration) 70 | } 71 | } 72 | 73 | // 흡연 구역 메시지 뷰(= 토스트 뷰) 보여주기 메소드 74 | func showMessageView( 75 | _ content: Contents, 76 | belowSubview: UIView? = nil, 77 | withDuration: TimeInterval = 0.5 78 | ) { 79 | let messageView = SmokingAreaMessageView() 80 | messageView.bind(content) 81 | addBottomView(messageView, belowSubview: belowSubview) 82 | 83 | let bottomMargin = 49.adjustedH 84 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { 85 | messageView.show(withMovement: messageView.viewHeight() + bottomMargin, withDuration: withDuration) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Extension/UserDefaults + Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults + Extension.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/10/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension UserDefaults { 11 | 12 | // MARK: - Properties 13 | @UserDefault(key: UserDefaultsKey.nickname.rawValue, defaultValue: "") 14 | static var nickname 15 | 16 | @UserDefault(key: UserDefaultsKey.userId.rawValue, defaultValue: -1) 17 | static var userId 18 | 19 | @UserDefault(key: UserDefaultsKey.isFirstLaunch.rawValue, defaultValue: true) 20 | static var isFirstLaunch 21 | 22 | // MARK: - Custom Methods 23 | 24 | // UserDefaults에 저장된 모든 유저 정보를 제거하는 메서드 25 | func removeAllUserDefaulsKeys() { 26 | UserDefaultsKey.allCases 27 | .forEach { UserDefaults.standard.removeObject(forKey: $0.rawValue) } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Logging/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TeamFogFog/FogFog-iOS/f3813e3751beda9a68648113287573ee512ce37b/FogFog-iOS/FogFog-iOS/Utils/Logging/.gitkeep -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Logging/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // FogFog-iOS 4 | // 5 | // Created by 김승찬 on 2022/11/14. 6 | // 7 | 8 | import Foundation 9 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/UIComponents/FogButton/FogButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogButton.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2022/11/25. 6 | // 7 | 8 | import UIKit 9 | 10 | final class FogButton: BaseView { 11 | 12 | // MARK: UI 13 | private let leftImageView = UIImageView() 14 | private let titleLabel = UILabel() 15 | lazy var button = UIButton() 16 | 17 | // MARK: Properties 18 | var style: FogButtonStyle { 19 | didSet { setAppearance(style) } 20 | } 21 | 22 | var title: String? { 23 | get { titleLabel.text } 24 | set { titleLabel.text = newValue } 25 | } 26 | 27 | var image: UIImage? { 28 | get { leftImageView.image } 29 | set { leftImageView.image = newValue } 30 | } 31 | 32 | // MARK: Init 33 | init(frame: CGRect = .zero, style: FogButtonStyle = .normal) { 34 | self.style = style 35 | super.init(frame: frame) 36 | setAppearance(style) 37 | } 38 | 39 | // MARK: Setup UI 40 | override func setStyle() { 41 | leftImageView.do { 42 | $0.contentMode = .scaleAspectFill 43 | } 44 | } 45 | 46 | override func setLayout() { 47 | addSubviews([leftImageView, titleLabel, button]) 48 | 49 | leftImageView.snp.makeConstraints { 50 | $0.leading.equalTo(20) 51 | $0.centerY.equalToSuperview() 52 | $0.size.equalTo(18) 53 | } 54 | 55 | titleLabel.snp.makeConstraints { 56 | $0.center.equalToSuperview() 57 | } 58 | 59 | button.snp.makeConstraints { 60 | $0.edges.equalToSuperview() 61 | } 62 | } 63 | } 64 | 65 | // MARK: Private Methods 66 | extension FogButton { 67 | 68 | private func setAppearance(_ style: FogButtonStyle) { 69 | layer.borderWidth = 1.5 70 | layer.borderColor = style.borderColor.cgColor 71 | backgroundColor = style.backgroundColor 72 | titleLabel.textColor = style.titleColor 73 | titleLabel.font = style.font 74 | leftImageView.image = style.leftImage 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/UIComponents/FogButton/FogButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogButtonStyle.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2022/11/25. 6 | // 7 | 8 | import UIKit 9 | 10 | struct FogButtonStyle { 11 | let borderColor: UIColor 12 | let titleColor: UIColor 13 | let backgroundColor: UIColor 14 | let font: UIFont 15 | let leftImage: UIImage? 16 | } 17 | 18 | extension FogButtonStyle { 19 | 20 | static let unselected = FogButtonStyle( 21 | borderColor: .grayGray9, 22 | titleColor: .grayGray3, 23 | backgroundColor: .grayWhite, 24 | font: .pretendardM(16), 25 | leftImage: nil 26 | ) 27 | 28 | static let selected = FogButtonStyle( 29 | borderColor: .fogBlue, 30 | titleColor: .fogBlue, 31 | backgroundColor: .mainBlue8, 32 | font: .pretendardB(16), 33 | leftImage: .checkmark 34 | ) 35 | 36 | static let normal = FogButtonStyle( 37 | borderColor: .grayBlack, 38 | titleColor: .grayWhite, 39 | backgroundColor: .grayBlack, 40 | font: .pretendardB(18), 41 | leftImage: nil 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/UIComponents/FogNavigationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogNavigationView.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/11/24. 6 | // 7 | 8 | import UIKit 9 | 10 | import RxCocoa 11 | import SnapKit 12 | import Then 13 | 14 | final class FogNavigationView: BaseView { 15 | 16 | // MARK: Properties 17 | private lazy var titleLabel = UILabel() 18 | private lazy var backButton = UIButton() 19 | 20 | // MARK: UI 21 | override func setStyle() { 22 | backgroundColor = .white 23 | 24 | titleLabel.do { 25 | $0.textColor = .grayGray2 26 | $0.font = .pretendardB(16) 27 | $0.textAlignment = .center 28 | } 29 | 30 | backButton.do { 31 | $0.setImage(FogImage.btnBack, for: .normal) 32 | } 33 | } 34 | 35 | override func setLayout() { 36 | self.addSubviews([titleLabel, backButton]) 37 | 38 | titleLabel.snp.makeConstraints { 39 | $0.centerX.equalToSuperview() 40 | $0.bottom.equalToSuperview().inset(21) 41 | } 42 | 43 | backButton.snp.makeConstraints { 44 | $0.leading.equalToSuperview().inset(16) 45 | $0.centerY.equalTo(titleLabel.snp.centerY) 46 | } 47 | } 48 | } 49 | 50 | // MARK: - Custom Methods 51 | extension FogNavigationView { 52 | 53 | /// 타이틀 설정 메서드 54 | func setTitle(_ title: String) { 55 | titleLabel.text = title 56 | } 57 | 58 | // 뒤로가기 버튼 터치 이벤트 방출하는 메서드 59 | func backButtonDidTap() -> Signal { 60 | return backButton.rx.tap.asSignal() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/UIComponents/FogTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogTextField.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2022/11/25. 6 | // 7 | 8 | import UIKit 9 | 10 | import SnapKit 11 | import Then 12 | 13 | final class FogTextField: UITextField { 14 | 15 | // MARK: Init 16 | override init(frame: CGRect) { 17 | super.init(frame: frame) 18 | 19 | setStyle() 20 | } 21 | 22 | @available(*, unavailable) 23 | required init?(coder: NSCoder) { 24 | super.init(coder: coder) 25 | } 26 | 27 | // MARK: UI 28 | private func setStyle() { 29 | setClearButton() 30 | setBoderColor(color: .grayGray9) 31 | self.makeRounded(cornerRadius: 12) 32 | self.addShadow(offset: CGSize(width: 0, height: 2), color: .shadowGray2, opacity: 0.14, radius: 17) 33 | self.addLeftPadding(19) 34 | self.font = .pretendardB(18) 35 | self.textColor = .grayGray1 36 | self.layer.borderWidth = 1 37 | self.backgroundColor = .white 38 | } 39 | } 40 | 41 | // MARK: - Custom Methods 42 | extension FogTextField { 43 | 44 | /// boderColor 설정 메서드 45 | func setBoderColor(color: UIColor) { 46 | self.layer.borderColor = color.cgColor 47 | } 48 | 49 | /// Placeholder 설정 메서드 50 | func setPlaceHolderText(placeholder: String) { 51 | self.placeholder = placeholder 52 | self.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [.foregroundColor: UIColor.grayGray7, .font: UIFont.pretendardR(18)]) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Wrapper/Published+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Published+Rx.swift 3 | // FogFog-iOS 4 | // 5 | // Created by taekki on 2023/06/07. 6 | // 7 | 8 | import RxSwift 9 | 10 | @propertyWrapper 11 | struct Published { 12 | 13 | private let subject = PublishSubject() 14 | 15 | var projectedValue: Observable { return subject } 16 | var wrappedValue: Value { didSet { valueDidChange() } } 17 | 18 | init(wrappedValue: Value) { 19 | self.wrappedValue = wrappedValue 20 | } 21 | 22 | private func valueDidChange() { 23 | subject.on(.next(wrappedValue)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOS/Utils/Wrapper/UserDefault.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefault.swift 3 | // FogFog-iOS 4 | // 5 | // Created by EUNJU on 2023/06/04. 6 | // 7 | 8 | import Foundation 9 | 10 | @propertyWrapper 11 | struct UserDefault { 12 | 13 | private let key: String 14 | private let defaultValue: T 15 | 16 | init(key: String, defaultValue: T) { 17 | self.key = key 18 | self.defaultValue = defaultValue 19 | } 20 | 21 | var wrappedValue: T { 22 | get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } 23 | set { UserDefaults.standard.set(newValue, forKey: key) } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOSTests/FogFog_iOSTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogFog_iOSTests.swift 3 | // FogFog-iOSTests 4 | // 5 | // Created by 김승찬 on 2022/11/14. 6 | // 7 | 8 | import XCTest 9 | @testable import FogFog_iOS 10 | 11 | class FogFog_iOSTests: 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 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOSUITests/FogFog_iOSUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogFog_iOSUITests.swift 3 | // FogFog-iOSUITests 4 | // 5 | // Created by 김승찬 on 2022/11/14. 6 | // 7 | 8 | import XCTest 9 | 10 | class FogFog_iOSUITests: 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 | -------------------------------------------------------------------------------- /FogFog-iOS/FogFog-iOSUITests/FogFog_iOSUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FogFog_iOSUITestsLaunchTests.swift 3 | // FogFog-iOSUITests 4 | // 5 | // Created by 김승찬 on 2022/11/14. 6 | // 7 | 8 | import XCTest 9 | 10 | class FogFog_iOSUITestsLaunchTests: 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 | -------------------------------------------------------------------------------- /FogFog-iOS/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'FogFog-iOS' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for FogFog-iOS 9 | pod 'GoogleMaps', '6.1.0' 10 | pod 'Google-Maps-iOS-Utils', '3.4.0' 11 | 12 | target 'FogFog-iOSTests' do 13 | inherit! :search_paths 14 | # Pods for testing 15 | end 16 | 17 | target 'FogFog-iOSUITests' do 18 | # Pods for testing 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /FogFog-iOS/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Google-Maps-iOS-Utils (3.4.0): 3 | - Google-Maps-iOS-Utils/Clustering (= 3.4.0) 4 | - Google-Maps-iOS-Utils/Geometry (= 3.4.0) 5 | - Google-Maps-iOS-Utils/Heatmap (= 3.4.0) 6 | - Google-Maps-iOS-Utils/QuadTree (= 3.4.0) 7 | - GoogleMaps 8 | - Google-Maps-iOS-Utils/Clustering (3.4.0): 9 | - Google-Maps-iOS-Utils/QuadTree 10 | - GoogleMaps 11 | - Google-Maps-iOS-Utils/Geometry (3.4.0): 12 | - GoogleMaps 13 | - Google-Maps-iOS-Utils/Heatmap (3.4.0): 14 | - Google-Maps-iOS-Utils/QuadTree 15 | - GoogleMaps 16 | - Google-Maps-iOS-Utils/QuadTree (3.4.0): 17 | - GoogleMaps 18 | - GoogleMaps (6.1.0): 19 | - GoogleMaps/Maps (= 6.1.0) 20 | - GoogleMaps/Base (6.1.0) 21 | - GoogleMaps/Maps (6.1.0): 22 | - GoogleMaps/Base 23 | 24 | DEPENDENCIES: 25 | - Google-Maps-iOS-Utils (= 3.4.0) 26 | - GoogleMaps (= 6.1.0) 27 | 28 | SPEC REPOS: 29 | trunk: 30 | - Google-Maps-iOS-Utils 31 | - GoogleMaps 32 | 33 | SPEC CHECKSUMS: 34 | Google-Maps-iOS-Utils: 2b4971019b7e9b3c92b4635d218552d9e093df4b 35 | GoogleMaps: cffc5d24ce920063aaeebe6ac4dbaaca1dd7f377 36 | 37 | PODFILE CHECKSUM: 2115122bcb7f794856a788aa4b639761f0803b0d 38 | 39 | COCOAPODS: 1.11.3 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![폭폭 대표사진](https://github.com/TeamFogFog/FogFog-iOS/assets/63277563/d36deffe-2dd5-415d-bc60-bba1c7da6e50) 2 | > **내 근처 가까운 흡연구역, 포그포그** 3 | > 공공데이터를 활용한 흡연구역 지도 서비스 4 | > 5 | > **31th SOPT-Term Project 인기상 🏆** 6 | > 프로젝트 기간: 2022.10 ~ 2022.11 7 | > 앱스토어 출시 준비 중 8 | 9 | ## iOS Developers 10 | | | | 11 | :---------:|:----------:|:---------:| 12 | 김승찬 | 김태현 | 최은주 13 | [@seungchan2](https://github.com/seungchan2) | [@Taehyeon-Kim](https://github.com/Taehyeon-Kim) | [@jane1choi](https://github.com/jane1choi) | 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 | | 라이브러리(Library) | 버전(Version) | 사용목적(Purpose) | 42 | |:---|:----------|:---| 43 | | SnapKit| 5.6.0 | Layout | 44 | | Then | 3.0.0 | Layout | 45 | | FlexLayout| 1.3.33 | Layout | 46 | | PinLayout| 1.10.4 | Layout | 47 | | RxSwift | 6.5.0 | 비동기 처리 | 48 | | Moya| 15.0.0 | 서버 통신 | 49 | | GoogleMaps| 6.1.0 | 구글 지도 | 50 | | KakaoOpenSDK| 2.15.0 | 카카오 소셜 로그인 | 51 |
52 | 53 | ## 프로젝트 구조 54 | ### App Architecture: MVVM-C 55 | MVVM-C 56 | 57 | ### 폴더 구조 58 | ``` 59 | FogFog-iOS 60 | ┣ 📂App 61 | ┃ ┣ AppDelegate.swift 62 | ┃ ┗ SceneDelegate.swift 63 | ┣ 📂Manager 64 | ┣ 📂Models 65 | ┣ 📂Networking 66 | ┃ ┣ 📂APIServices 67 | ┃ ┣ 📂APIs 68 | ┃ ┣ 📂Foundation 69 | ┃ ┣ 📂Models 70 | ┃ ┗ 📂Monitoring 71 | ┣ 📂OAuth 72 | ┣ 📂Presentation 73 | ┃ ┣ 📂Common 74 | ┃ ┣ 📂ExternalMap 75 | ┃ ┣ 📂Login 76 | ┃ ┣ 📂Map 77 | ┃ ┣ 📂Setting 78 | ┃ ┣ 📂SideBar 79 | ┃ ┣ 📂SmokingArea 80 | ┃ ┗ 📂Splash 81 | ┣ 📂Resources 82 | ┃ ┣ LaunchScreen.storyboard 83 | ┃ ┣ 📂Colors 84 | ┃ ┣ 📂Fonts 85 | ┃ ┗ 📂Image 86 | ┣ 📂Supports 87 | ┃ ┣ Config.xcconfig 88 | ┃ ┣ GoogleMap.plist 89 | ┃ ┗ Info.plist 90 | ┗ 📂Utils 91 | ┣ 📂Analytics 92 | ┣ 📂Base 93 | ┣ 📂Class 94 | ┣ 📂Contstant 95 | ┣ 📂Extension 96 | ┣ 📂Logging 97 | ┣ 📂UIComponents 98 | ┗ 📂Wrapper 99 | ``` 100 | --------------------------------------------------------------------------------