diff --git a/pins.xcodeproj/project.pbxproj b/pins.xcodeproj/project.pbxproj index 70cc9fe..c021a0e 100644 --- a/pins.xcodeproj/project.pbxproj +++ b/pins.xcodeproj/project.pbxproj @@ -32,6 +32,12 @@ B72EDCA42B2B0A4000080328 /* KeyboardAnimationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B72EDCA32B2B0A4000080328 /* KeyboardAnimationManager.swift */; }; B742C5782AEA5EE900E95E96 /* CreateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B742C5772AEA5EE900E95E96 /* CreateViewModel.swift */; }; B749AED82B3831CB00633BB1 /* AnnotationIdentifying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B749AED72B3831CB00633BB1 /* AnnotationIdentifying.swift */; }; + B749AEDA2B38391E00633BB1 /* SettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B749AED92B38391E00633BB1 /* SettingViewModel.swift */; }; + B749AEDF2B383EA500633BB1 /* SettingItemHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B749AEDE2B383EA500633BB1 /* SettingItemHandling.swift */; }; + B749AEE12B383ECC00633BB1 /* LogoutItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B749AEE02B383ECC00633BB1 /* LogoutItem.swift */; }; + B749AEE32B38509A00633BB1 /* DiskCacheItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B749AEE22B38509A00633BB1 /* DiskCacheItem.swift */; }; + B749AEE52B38527300633BB1 /* ConfirmManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B749AEE42B38527300633BB1 /* ConfirmManager.swift */; }; + B749AEE72B38636000633BB1 /* DiskCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B749AEE62B38636000633BB1 /* DiskCacheManager.swift */; }; B74A4FC12AE78BAE00226EA7 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74A4FC02AE78BAE00226EA7 /* SearchViewController.swift */; }; B74A4FC32AE78BE700226EA7 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74A4FC22AE78BE700226EA7 /* SearchView.swift */; }; B74A4FC52AE7905500226EA7 /* CreateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74A4FC42AE7905500226EA7 /* CreateViewController.swift */; }; @@ -43,6 +49,9 @@ B76DD4642AFE0E4B00DFE7E1 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = B76DD4632AFE0E4B00DFE7E1 /* FirebaseStorage */; }; B76DD4662AFE238F00DFE7E1 /* LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B76DD4652AFE238F00DFE7E1 /* LoadingIndicator.swift */; }; B76EEA9E2B35F06E00D349EA /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B76EEA9D2B35F06E00D349EA /* InfoPlist.xcstrings */; }; + B7704A242B3313A900E310AC /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7704A232B3313A900E310AC /* SettingViewController.swift */; }; + B7704A262B3313EB00E310AC /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7704A252B3313EB00E310AC /* SettingView.swift */; }; + B7704A282B33193100E310AC /* SettingViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7704A272B33193100E310AC /* SettingViewCell.swift */; }; B7781B302B0F47C8006372CF /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7781B2F2B0F47C8006372CF /* Category.swift */; }; B7781B332B0F4D4B006372CF /* PinAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7781B322B0F4D4B006372CF /* PinAnnotationView.swift */; }; B7781B352B0F4F91006372CF /* PinAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7781B342B0F4F91006372CF /* PinAnnotation.swift */; }; @@ -94,6 +103,8 @@ B7E1236A2B3D7C520044E1DB /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7E123692B3D7C520044E1DB /* Data+Extensions.swift */; }; B7E8A4702AEB84420026931D /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = B7E8A46F2AEB84420026931D /* FirebaseAnalytics */; }; B7E8A4722AEB84420026931D /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = B7E8A4712AEB84420026931D /* FirebaseCrashlytics */; }; + B7EC21B12B456EC5008F80F7 /* (null) in Sources */ = {isa = PBXBuildFile; }; + B7EC21B32B458A29008F80F7 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7EC21B22B458A29008F80F7 /* UITableView+Extensions.swift */; }; B7ECD4502B3059E60085C809 /* PinClusterAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7ECD44F2B3059E60085C809 /* PinClusterAnnotationView.swift */; }; B7F071B72B14855000BB5AB9 /* DetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F071B62B14855000BB5AB9 /* DetailViewModel.swift */; }; B7F071B92B14876C00BB5AB9 /* PinResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F071B82B14876C00BB5AB9 /* PinResponse.swift */; }; @@ -140,6 +151,12 @@ B72EDCA32B2B0A4000080328 /* KeyboardAnimationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardAnimationManager.swift; sourceTree = ""; }; B742C5772AEA5EE900E95E96 /* CreateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateViewModel.swift; sourceTree = ""; }; B749AED72B3831CB00633BB1 /* AnnotationIdentifying.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnotationIdentifying.swift; sourceTree = ""; }; + B749AED92B38391E00633BB1 /* SettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewModel.swift; sourceTree = ""; }; + B749AEDE2B383EA500633BB1 /* SettingItemHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingItemHandling.swift; sourceTree = ""; }; + B749AEE02B383ECC00633BB1 /* LogoutItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutItem.swift; sourceTree = ""; }; + B749AEE22B38509A00633BB1 /* DiskCacheItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskCacheItem.swift; sourceTree = ""; }; + B749AEE42B38527300633BB1 /* ConfirmManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmManager.swift; sourceTree = ""; }; + B749AEE62B38636000633BB1 /* DiskCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskCacheManager.swift; sourceTree = ""; }; B74A4FC02AE78BAE00226EA7 /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; B74A4FC22AE78BE700226EA7 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; B74A4FC42AE7905500226EA7 /* CreateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateViewController.swift; sourceTree = ""; }; @@ -151,6 +168,9 @@ B76DD4612AFE0DB000DFE7E1 /* FirestorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirestorageService.swift; sourceTree = ""; }; B76DD4652AFE238F00DFE7E1 /* LoadingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicator.swift; sourceTree = ""; }; B76EEA9D2B35F06E00D349EA /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; + B7704A232B3313A900E310AC /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; }; + B7704A252B3313EB00E310AC /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; + B7704A272B33193100E310AC /* SettingViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewCell.swift; sourceTree = ""; }; B7781B2F2B0F47C8006372CF /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; B7781B322B0F4D4B006372CF /* PinAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinAnnotationView.swift; sourceTree = ""; }; B7781B342B0F4F91006372CF /* PinAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinAnnotation.swift; sourceTree = ""; }; @@ -202,6 +222,7 @@ B7E050F02AE4FB3A007903C3 /* MainMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMapView.swift; sourceTree = ""; }; B7E0D5902B0CB59D0090A465 /* AnimationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationManager.swift; sourceTree = ""; }; B7E123692B3D7C520044E1DB /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; + B7EC21B22B458A29008F80F7 /* UITableView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Extensions.swift"; sourceTree = ""; }; B7ECD44F2B3059E60085C809 /* PinClusterAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinClusterAnnotationView.swift; sourceTree = ""; }; B7F071B62B14855000BB5AB9 /* DetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewModel.swift; sourceTree = ""; }; B7F071B82B14876C00BB5AB9 /* PinResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinResponse.swift; sourceTree = ""; }; @@ -239,6 +260,7 @@ isa = PBXGroup; children = ( B710588D2AEA2699003D6039 /* OSLog+Extensions.swift */, + B7EC21B22B458A29008F80F7 /* UITableView+Extensions.swift */, B7035B842AF8D1FF000F74ED /* UIView+Extensions.swift */, B7B9776B2AFA20D80023718B /* Date+Extensions.swift */, B785A7832B1091BC00DC2E88 /* UILabel+Extensions.swift */, @@ -259,6 +281,7 @@ B7226B152B01F6990034B451 /* LoginViewModel.swift */, B7F071B62B14855000BB5AB9 /* DetailViewModel.swift */, B7194A472B1B596F00642FDA /* SigninViewModel.swift */, + B749AED92B38391E00633BB1 /* SettingViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -376,7 +399,10 @@ B7B20E232B1CCDB800C8304B /* KeychainManager.swift */, B711B6A92B21C08D00C661E3 /* ImagePickerManager.swift */, B72EDCA32B2B0A4000080328 /* KeyboardAnimationManager.swift */, + B749AEDE2B383EA500633BB1 /* SettingItemHandling.swift */, + B749AEE42B38527300633BB1 /* ConfirmManager.swift */, B7AE77DD2B3199FB0056D451 /* ImageCacheManager.swift */, + B749AEE62B38636000633BB1 /* DiskCacheManager.swift */, ); path = Utils; sourceTree = ""; @@ -390,6 +416,7 @@ B79A28F22AF37F93008FD3CE /* LoginViewController.swift */, B785A77D2B10743600DC2E88 /* DetailViewController.swift */, B7194A412B1B4F3300642FDA /* SigninViewController.swift */, + B7704A232B3313A900E310AC /* SettingViewController.swift */, ); path = ViewController; sourceTree = ""; @@ -425,6 +452,17 @@ path = Error; sourceTree = ""; }; + B7704A292B33194700E310AC /* SettingView */ = { + isa = PBXGroup; + children = ( + B7704A252B3313EB00E310AC /* SettingView.swift */, + B7704A272B33193100E310AC /* SettingViewCell.swift */, + B749AEE02B383ECC00633BB1 /* LogoutItem.swift */, + B749AEE22B38509A00633BB1 /* DiskCacheItem.swift */, + ); + path = SettingView; + sourceTree = ""; + }; B7781B312B0F4D3A006372CF /* MainMapView */ = { isa = PBXGroup; children = ( @@ -519,6 +557,7 @@ B7781B312B0F4D3A006372CF /* MainMapView */, B7614C102AEE085F00CAB8F0 /* CreateView */, B7D0514E2B10ACEF00D86183 /* DetailView */, + B7704A292B33194700E310AC /* SettingView */, B74A4FC22AE78BE700226EA7 /* SearchView.swift */, B7157E692AF384E2007CEF95 /* LoginView.swift */, B7194A432B1B4F4600642FDA /* SigninView.swift */, @@ -706,6 +745,7 @@ B7B9776C2AFA20D80023718B /* Date+Extensions.swift in Sources */, B7D051522B10AD2100D86183 /* DetailContentView.swift in Sources */, B7194A462B1B573800642FDA /* CALayer+Extensions.swift in Sources */, + B749AEE32B38509A00633BB1 /* DiskCacheItem.swift in Sources */, B7B977612AFA0A550023718B /* FirebaseFirestore.swift in Sources */, B74A4FC12AE78BAE00226EA7 /* SearchViewController.swift in Sources */, B711B6AA2B21C08D00C661E3 /* ImagePickerManager.swift in Sources */, @@ -719,14 +759,18 @@ B749AED82B3831CB00633BB1 /* AnnotationIdentifying.swift in Sources */, B7194A422B1B4F3300642FDA /* SigninViewController.swift in Sources */, B7DC98EB2B26179A006DD8F7 /* DetailUseCase.swift in Sources */, + B749AEE52B38527300633BB1 /* ConfirmManager.swift in Sources */, B7ECD4502B3059E60085C809 /* PinClusterAnnotationView.swift in Sources */, + B749AEE12B383ECC00633BB1 /* LogoutItem.swift in Sources */, B742C5782AEA5EE900E95E96 /* CreateViewModel.swift in Sources */, + B749AEE72B38636000633BB1 /* DiskCacheManager.swift in Sources */, B7DC98E72B260C17006DD8F7 /* CommentResponse.swift in Sources */, B7D051502B10AD1A00D86183 /* DetailNavigationView.swift in Sources */, B71C387E2B0754F90065A783 /* UserRequest.swift in Sources */, B7AE77DE2B3199FB0056D451 /* ImageCacheManager.swift in Sources */, B7B20E242B1CCDB800C8304B /* KeychainManager.swift in Sources */, B7F071B72B14855000BB5AB9 /* DetailViewModel.swift in Sources */, + B749AEDF2B383EA500633BB1 /* SettingItemHandling.swift in Sources */, B7614C0F2AEE085B00CAB8F0 /* ImageCollectionViewCell.swift in Sources */, B7AF69312AE0FEDF00385632 /* AppDelegate.swift in Sources */, B71058912AEA2974003D6039 /* MainViewModel.swift in Sources */, @@ -737,11 +781,14 @@ B7781B332B0F4D4B006372CF /* PinAnnotationView.swift in Sources */, B7BB20262B0F1696003EED3A /* MainUseCase.swift in Sources */, B7E050F12AE4FB3A007903C3 /* MainMapView.swift in Sources */, + B7704A282B33193100E310AC /* SettingViewCell.swift in Sources */, B7157E6A2AF384E2007CEF95 /* LoginView.swift in Sources */, B71058932AEA2CB3003D6039 /* CustomButton.swift in Sources */, B7D051542B10AD2B00D86183 /* DetailCommentView.swift in Sources */, B7D0514D2B10AC9C00D86183 /* UITextField+Extensions.swift in Sources */, B74A4FC32AE78BE700226EA7 /* SearchView.swift in Sources */, + B7EC21B12B456EC5008F80F7 /* (null) in Sources */, + B7EC21B32B458A29008F80F7 /* UITableView+Extensions.swift in Sources */, B79A28F32AF37F93008FD3CE /* LoginViewController.swift in Sources */, B78099432B06047400C01059 /* ImageInfo.swift in Sources */, B71C387C2B0753BB0065A783 /* LoginUseCase.swift in Sources */, @@ -752,8 +799,10 @@ B72EDCA42B2B0A4000080328 /* KeyboardAnimationManager.swift in Sources */, B785A7842B1091BC00DC2E88 /* UILabel+Extensions.swift in Sources */, B7BB20242B0E0745003EED3A /* CreateUseCase.swift in Sources */, + B7704A262B3313EB00E310AC /* SettingView.swift in Sources */, B71C38802B07554B0065A783 /* FirebaseAuthService.swift in Sources */, B7194A442B1B4F4600642FDA /* SigninView.swift in Sources */, + B749AEDA2B38391E00633BB1 /* SettingViewModel.swift in Sources */, B7AEC0CE2B034568004BA42A /* CustomTextView.swift in Sources */, B7F0B8F32B3C34A100E9BA6D /* CommentError.swift in Sources */, B70EE5BD2B15D7DD00BCCACF /* String+Extensions.swift in Sources */, @@ -764,6 +813,7 @@ B7B977642AFA0D270023718B /* PinRequest.swift in Sources */, B7AF69332AE0FEDF00385632 /* SceneDelegate.swift in Sources */, B71C38832B075CC70065A783 /* CryptoUtils.swift in Sources */, + B7704A242B3313A900E310AC /* SettingViewController.swift in Sources */, B7B20E1D2B1CBAC700C8304B /* UserRepository.swift in Sources */, B7DC98E92B260FCF006DD8F7 /* CommentView.swift in Sources */, B7DC98E52B260BD9006DD8F7 /* CommentRequest.swift in Sources */, @@ -908,6 +958,7 @@ INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "핀을 생성할 때 사진을 첨부하려면 사진 접근 권한이 필요합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIStatusBarStyle = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( @@ -1037,6 +1088,7 @@ INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "핀을 생성할 때 사진을 첨부하려면 사진 접근 권한이 필요합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIStatusBarStyle = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( @@ -1160,6 +1212,7 @@ INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "핀을 생성할 때 사진을 첨부하려면 사진 접근 권한이 필요합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIStatusBarStyle = ""; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/pins/Data/Service/FirestorageService.swift b/pins/Data/Service/FirestorageService.swift index da6b20e..0088ff5 100644 --- a/pins/Data/Service/FirestorageService.swift +++ b/pins/Data/Service/FirestorageService.swift @@ -34,10 +34,9 @@ final class FirestorageService: FirestorageServiceProtocol { } func downloadImage(urlString: String) async -> UIImage? { - if let cachedImage = ImageCacheManager.shared.getImage(forKey: urlString) { + if let cachedImage = DiskCacheManager.retrieveCachedImage(withFilename: urlString) { return cachedImage } - let storageReference = Storage.storage().reference(forURL: urlString) let megaByte = Int64(2 * 1024 * 1024) @@ -47,7 +46,7 @@ final class FirestorageService: FirestorageServiceProtocol { os_log("Error downloading image: \(error)") continuation.resume(returning: nil) } else if let data = data, let image = UIImage(data: data) { - ImageCacheManager.shared.setImage(image, forKey: urlString) + DiskCacheManager.cacheImage(image, withFilename: urlString) continuation.resume(returning: image) } else { continuation.resume(returning: nil) diff --git a/pins/Extension/UITableView+Extensions.swift b/pins/Extension/UITableView+Extensions.swift new file mode 100644 index 0000000..ab20ad9 --- /dev/null +++ b/pins/Extension/UITableView+Extensions.swift @@ -0,0 +1,17 @@ +// +// UITableView+Extensions.swift +// pins +// +// Created by 주동석 on 1/3/24. +// + +import UIKit + +extension UITableView { + func dequeueReusableCell(forIndexPath indexPath: IndexPath) -> T where T: ReuseIdentifying { + guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else { + fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)") + } + return cell + } +} diff --git a/pins/Presentation/View/MainMapView/MainMapView.swift b/pins/Presentation/View/MainMapView/MainMapView.swift index 3d656dc..f2ef365 100644 --- a/pins/Presentation/View/MainMapView/MainMapView.swift +++ b/pins/Presentation/View/MainMapView/MainMapView.swift @@ -72,11 +72,11 @@ final class MainMapView: UIView { button.accessibilityLabel = "내 위치로" return button }() - private let logoutButton: CustomButton = { + private let settingButton: CustomButton = { let button = CustomButton() button.setShadow() - button.setImage(systemName: "door.right.hand.open") - button.accessibilityLabel = "로그아웃" + button.setImage(systemName: "gearshape") + button.accessibilityLabel = "설정" return button }() @@ -92,7 +92,7 @@ final class MainMapView: UIView { // MARK: - Layouts private func setLayout() { - [mapView, centerPinImage, createButton, cancelButton, searchButton, createModeButton, refeshButton, myLocationButton, logoutButton].forEach { addSubview($0) } + [mapView, centerPinImage, createButton, cancelButton, searchButton, createModeButton, refeshButton, myLocationButton, settingButton].forEach { addSubview($0) } mapView .leadingLayout(equalTo: leadingAnchor) @@ -142,7 +142,7 @@ final class MainMapView: UIView { .widthLayout(45) .heightLayout(45) - logoutButton + settingButton .leadingLayout(equalTo: leadingAnchor, constant: 16) .topLayout(equalTo: safeAreaLayoutGuide.topAnchor, constant: 16) .widthLayout(45) @@ -221,7 +221,7 @@ final class MainMapView: UIView { createButton.addAction(action, for: .touchUpInside) } - func setLogoutButtonAction(_ action: UIAction) { - logoutButton.addAction(action, for: .touchUpInside) + func setSettingButtonAction(_ action: UIAction) { + settingButton.addAction(action, for: .touchUpInside) } } diff --git a/pins/Presentation/View/SettingView/DiskCacheItem.swift b/pins/Presentation/View/SettingView/DiskCacheItem.swift new file mode 100644 index 0000000..b265a9b --- /dev/null +++ b/pins/Presentation/View/SettingView/DiskCacheItem.swift @@ -0,0 +1,30 @@ +// +// DiskCacheItem.swift +// pins +// +// Created by 주동석 on 12/24/23. +// + +import OSLog +import UIKit + +final class DiskCacheItem: SettingItemHandling { + var title: String = "메모리" + var onPresentAlert: ((UIAlertController) -> Void)? + + init(title: String) { + self.title = title + } + + func performAction() { + let alert = ConfirmManager.makeAlert(title: "캐시 지우기", message: "모든 캐시를 지우시겠습니까?") { [weak self] in + self?.removeDiskCache() + self?.title = "메모리 Zero KB 사용중" + } + onPresentAlert?(alert) + } + + private func removeDiskCache() { + DiskCacheManager.clearCache() + } +} diff --git a/pins/Presentation/View/SettingView/LogoutItem.swift b/pins/Presentation/View/SettingView/LogoutItem.swift new file mode 100644 index 0000000..fdd9ee5 --- /dev/null +++ b/pins/Presentation/View/SettingView/LogoutItem.swift @@ -0,0 +1,36 @@ +// +// LogoutItem.swift +// pins +// +// Created by 주동석 on 12/24/23. +// + +import OSLog +import UIKit +import FirebaseAuth + +final class LogoutItem: SettingItemHandling { + var title: String = "로그아웃" + var navigationController: UINavigationController? + var onPresentAlert: ((UIAlertController) -> Void)? + + init(navigationController: UINavigationController?) { + self.navigationController = navigationController + } + + func performAction() { + let alert = ConfirmManager.makeAlert(title: "로그아웃", message: "정말 로그아웃 하시겠습니까?") { [weak self] in + self?.logout() + } + onPresentAlert?(alert) + } + + private func logout() { + do { + try Auth.auth().signOut() + navigationController?.viewControllers = [LoginViewController()] + } catch { + os_log("logout \(error)") + } + } +} diff --git a/pins/Presentation/View/SettingView/SettingView.swift b/pins/Presentation/View/SettingView/SettingView.swift new file mode 100644 index 0000000..17f301a --- /dev/null +++ b/pins/Presentation/View/SettingView/SettingView.swift @@ -0,0 +1,98 @@ +// +// SettingView.swift +// pins +// +// Created by 주동석 on 12/20/23. +// + +import UIKit + +final class SettingView: UIView { + enum SettingSection: Int { + case mainSection + } + typealias SettingDataSource = UITableViewDiffableDataSource + typealias SettingSnapshot = NSDiffableDataSourceSnapshot + // MARK: - 프로퍼티 + private let titleLabel: UILabel = { + let label: UILabel = UILabel() + label.text = "설정" + label.textColor = .defaultText + label.font = UIFont.systemFont(ofSize: 18) + return label + }() + private let backButton: CustomButton = { + let button = CustomButton(backgroundColor: .defaultBackground) + button.setImage(systemName: "chevron.backward") + return button + }() + let tableView: UITableView = { + let tableView: UITableView = UITableView(frame: .zero, style: .plain) + tableView.register(SettingViewCell.self, forCellReuseIdentifier: "SettingViewCell") + tableView.backgroundColor = .defaultBackground + tableView.separatorInset.left = 0 + return tableView + }() + private var dataSource: SettingDataSource! + private var viewModel: SettingViewModel! + // MARK: - 초기화 + init(viewModel: SettingViewModel) { + super.init(frame: .zero) + backgroundColor = .defaultBackground + self.viewModel = viewModel + + setBackButtonLayout() + setTitleLabelLayout() + setTableViewLayout() + configureDiffableDataSource() + performInitialDataPopulation() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + // MARK: - 타이틀 + private func setTitleLabelLayout() { + addSubview(titleLabel) + titleLabel + .topLayout(equalTo: safeAreaLayoutGuide.topAnchor) + .centerXLayout(equalTo: centerXAnchor) + + } + // MARK: - 뒤로가기 + private func setBackButtonLayout() { + addSubview(backButton) + backButton + .leadingLayout(equalTo: leadingAnchor, constant: 16) + .topLayout(equalTo: safeAreaLayoutGuide.topAnchor) + } + + func setBackButtonAction(_ action: UIAction) { + backButton.addAction(action, for: .touchUpInside) + } + // MARK: - 테이블 뷰 + private func setTableViewLayout() { + addSubview(tableView) + tableView + .topLayout(equalTo: titleLabel.bottomAnchor, constant: 16) + .bottomLayout(equalTo: bottomAnchor) + .leadingLayout(equalTo: leadingAnchor) + .trailingLayout(equalTo: trailingAnchor) + } + + private func configureDiffableDataSource() { + dataSource = SettingDataSource(tableView: tableView, cellProvider: { [weak self] tableView, indexPath, _ in + guard let self else { fatalError("self is nil") } + let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as SettingViewCell + cell.setLabelText(self.viewModel.getTableStringData()[indexPath.row]) + return cell + }) + } + + private func performInitialDataPopulation() { + var snapshot = SettingSnapshot() + snapshot.appendSections([.mainSection]) + snapshot.appendItems(viewModel.getTableStringData()) + dataSource.apply(snapshot, animatingDifferences: true) + } +} diff --git a/pins/Presentation/View/SettingView/SettingViewCell.swift b/pins/Presentation/View/SettingView/SettingViewCell.swift new file mode 100644 index 0000000..51bde6c --- /dev/null +++ b/pins/Presentation/View/SettingView/SettingViewCell.swift @@ -0,0 +1,37 @@ +// +// SettingTableCell.swift +// pins +// +// Created by 주동석 on 12/20/23. +// + +import UIKit + +final class SettingViewCell: UITableViewCell, ReuseIdentifying { + private let label: UILabel = { + let label: UILabel = UILabel() + label.textColor = .defaultText + label.font = UIFont.systemFont(ofSize: 18) + return label + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + backgroundColor = .defaultBackground + configureLabel() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configureLabel() { + addSubview(label) + label.leadingLayout(equalTo: leadingAnchor, constant: 16) + .centerYLayout(equalTo: centerYAnchor) + } + + func setLabelText(_ text: String) { + label.text = text + } +} diff --git a/pins/Presentation/ViewController/MainViewController.swift b/pins/Presentation/ViewController/MainViewController.swift index 1a8f787..f9742de 100644 --- a/pins/Presentation/ViewController/MainViewController.swift +++ b/pins/Presentation/ViewController/MainViewController.swift @@ -120,13 +120,9 @@ final class MainViewController: UIViewController { self?.viewModel.setCreateViewIsPresented(isPresented: false) })) - mainMapView.setLogoutButtonAction(UIAction(handler: { [weak self] _ in - do { - try Auth.auth().signOut() - self?.navigationController?.viewControllers = [LoginViewController()] - } catch let signOutError as NSError { - os_log("Error signing out: %@", log: .ui, type: .error, signOutError) - } + mainMapView.setSettingButtonAction(UIAction(handler: { [weak self] _ in + let settingViewController: SettingViewController = SettingViewController() + self?.navigationController?.pushViewController(settingViewController, animated: true) })) } } diff --git a/pins/Presentation/ViewController/SettingViewController.swift b/pins/Presentation/ViewController/SettingViewController.swift new file mode 100644 index 0000000..37a3cce --- /dev/null +++ b/pins/Presentation/ViewController/SettingViewController.swift @@ -0,0 +1,57 @@ +// +// SettingViewController.swift +// pins +// +// Created by 주동석 on 12/20/23. +// + +import UIKit + +final class SettingViewController: UIViewController { + private lazy var viewModel: SettingViewModel = { + let logoutItem = LogoutItem(navigationController: navigationController) + logoutItem.onPresentAlert = { [weak self] alert in + self?.present(alert, animated: true) + } + let cacheSize = DiskCacheManager.calculateCacheSize() + let formattedSize = DiskCacheManager.formatBytes(cacheSize) + let diskCacheItem = DiskCacheItem(title: "메모리 \(formattedSize) 사용중") + diskCacheItem.onPresentAlert = { [weak self] alert in + self?.present(alert, animated: true) + } + return SettingViewModel(tableData: [ + logoutItem, + diskCacheItem + ]) + }() + private var settingView: SettingView { + view as! SettingView + } + + override func viewDidLoad() { + super.viewDidLoad() + setAction() + setTableDelegate() + } + + override func loadView() { + view = SettingView(viewModel: viewModel) + } + + private func setAction() { + settingView.setBackButtonAction(UIAction(handler: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + })) + } + + private func setTableDelegate() { + settingView.tableView.delegate = self + } +} + +extension SettingViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let selectedItem = viewModel.getTableData()[indexPath.row] + selectedItem.performAction() + } +} diff --git a/pins/Presentation/ViewModel/SettingViewModel.swift b/pins/Presentation/ViewModel/SettingViewModel.swift new file mode 100644 index 0000000..321cb26 --- /dev/null +++ b/pins/Presentation/ViewModel/SettingViewModel.swift @@ -0,0 +1,24 @@ +// +// SettingViewModel.swift +// pins +// +// Created by 주동석 on 12/24/23. +// + +import Foundation + +final class SettingViewModel { + private var tableData: [SettingItemHandling] = [] + + init(tableData: [SettingItemHandling]) { + self.tableData = tableData + } + + func getTableData() -> [SettingItemHandling] { + return tableData + } + + func getTableStringData() -> [String] { + return tableData.map { $0.title } + } +} diff --git a/pins/Utils/ConfirmManager.swift b/pins/Utils/ConfirmManager.swift new file mode 100644 index 0000000..0dc2f87 --- /dev/null +++ b/pins/Utils/ConfirmManager.swift @@ -0,0 +1,22 @@ +// +// ConfirmManager.swift +// pins +// +// Created by 주동석 on 12/24/23. +// + +import UIKit +enum ConfirmManager { + static func makeAlert(title: String, message: String, action: @escaping () -> Void) -> UIAlertController { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + let confirmAction = UIAlertAction(title: "확인", style: .default) { _ in + action() + } + alert.addAction(confirmAction) + + let cancelAction = UIAlertAction(title: "취소", style: .cancel) + alert.addAction(cancelAction) + + return alert + } +} diff --git a/pins/Utils/DiskCacheManager.swift b/pins/Utils/DiskCacheManager.swift new file mode 100644 index 0000000..3cfcfe1 --- /dev/null +++ b/pins/Utils/DiskCacheManager.swift @@ -0,0 +1,72 @@ +// +// DiskCacheManager.swift +// pins +// +// Created by 주동석 on 12/24/23. +// + +import UIKit +import OSLog +import CryptoKit + +enum DiskCacheManager { + static func cacheImage(_ image: UIImage, withFilename filename: String) { + let hash = CryptoUtils.sha256(filename) + if let data = image.jpegData(compressionQuality: 0.7) ?? image.pngData() { + let fileManager = FileManager.default + if let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first { + let fileURL = cacheDirectory.appendingPathComponent(hash) + try? data.write(to: fileURL) + } + } + } + static func retrieveCachedImage(withFilename filename: String) -> UIImage? { + let hash = CryptoUtils.sha256(filename) + let fileManager = FileManager.default + if let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first { + let fileURL = cacheDirectory.appendingPathComponent(hash) + if let imageData = try? Data(contentsOf: fileURL) { + return UIImage(data: imageData) + } + } + return nil + } + static func clearCache() { + let fileManager = FileManager.default + if let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first { + do { + let fileURLs = try fileManager.contentsOfDirectory(at: cacheDirectory, includingPropertiesForKeys: nil) + for fileURL in fileURLs { + try fileManager.removeItem(at: fileURL) + } + } catch { + print("Error clearing cache: \(error)") + } + } + } + static func calculateCacheSize() -> Int { + let fileManager = FileManager.default + var totalSize: Int = 0 + + if let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first { + do { + let fileURLs = try fileManager.contentsOfDirectory(at: cacheDirectory, includingPropertiesForKeys: nil) + for fileURL in fileURLs { + let fileAttributes = try fileManager.attributesOfItem(atPath: fileURL.path) + if let fileSize = fileAttributes[.size] as? Int { + totalSize += fileSize + } + } + } catch { + os_log("Error calculating cache size: \(error)") + } + } + return totalSize + } + static func formatBytes(_ bytes: Int) -> String { + let formatter = ByteCountFormatter() + formatter.allowedUnits = [.useMB, .useGB] + formatter.countStyle = .file + return formatter.string(fromByteCount: Int64(bytes)) + } +} diff --git a/pins/Utils/SettingItemHandling.swift b/pins/Utils/SettingItemHandling.swift new file mode 100644 index 0000000..e1d2f5f --- /dev/null +++ b/pins/Utils/SettingItemHandling.swift @@ -0,0 +1,13 @@ +// +// SettingItemHandling.swift +// pins +// +// Created by 주동석 on 12/24/23. +// + +import Foundation + +protocol SettingItemHandling { + var title: String { get set } + func performAction() +}