diff --git a/Pinit/Pinit.xcodeproj/project.pbxproj b/Pinit/Pinit.xcodeproj/project.pbxproj index 3f93ec3..d1570f9 100644 --- a/Pinit/Pinit.xcodeproj/project.pbxproj +++ b/Pinit/Pinit.xcodeproj/project.pbxproj @@ -11,9 +11,21 @@ D14369DC2D82D1970095F7E3 /* FSCalendar in Frameworks */ = {isa = PBXBuildFile; productRef = D14369DB2D82D1970095F7E3 /* FSCalendar */; }; D1436C432D8332020095F7E3 /* CombineMoya in Frameworks */ = {isa = PBXBuildFile; productRef = D1436C422D8332020095F7E3 /* CombineMoya */; }; D1436C452D8332020095F7E3 /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = D1436C442D8332020095F7E3 /* Moya */; }; + D1BCDA062D892DA4002C03BB /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = D1BCDA052D892DA4002C03BB /* Kingfisher */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 220085DD2D8945550090A4C5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6DDB45B92D7ED702006C4A4B /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6DDB45C02D7ED702006C4A4B; + remoteInfo = Pinit; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 220085D92D8945550090A4C5 /* PinitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PinitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6DDB45C12D7ED702006C4A4B /* Pinit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pinit.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -28,6 +40,11 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ + 220085DA2D8945550090A4C5 /* PinitTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = PinitTests; + sourceTree = ""; + }; D1286F2B2D815BB8006B5E52 /* Pinit */ = { isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( @@ -39,6 +56,13 @@ /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ + 220085D62D8945550090A4C5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 6DDB45BE2D7ED702006C4A4B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -47,6 +71,7 @@ D14369DC2D82D1970095F7E3 /* FSCalendar in Frameworks */, D1436C452D8332020095F7E3 /* Moya in Frameworks */, D1436C432D8332020095F7E3 /* CombineMoya in Frameworks */, + D1BCDA062D892DA4002C03BB /* Kingfisher in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -57,6 +82,7 @@ isa = PBXGroup; children = ( D1286F2B2D815BB8006B5E52 /* Pinit */, + 220085DA2D8945550090A4C5 /* PinitTests */, 6DDB45C22D7ED702006C4A4B /* Products */, ); sourceTree = ""; @@ -65,6 +91,7 @@ isa = PBXGroup; children = ( 6DDB45C12D7ED702006C4A4B /* Pinit.app */, + 220085D92D8945550090A4C5 /* PinitTests.xctest */, ); name = Products; sourceTree = ""; @@ -72,6 +99,29 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 220085D82D8945550090A4C5 /* PinitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 220085E12D8945550090A4C5 /* Build configuration list for PBXNativeTarget "PinitTests" */; + buildPhases = ( + 220085D52D8945550090A4C5 /* Sources */, + 220085D62D8945550090A4C5 /* Frameworks */, + 220085D72D8945550090A4C5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 220085DE2D8945550090A4C5 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 220085DA2D8945550090A4C5 /* PinitTests */, + ); + name = PinitTests; + packageProductDependencies = ( + ); + productName = PinitTests; + productReference = 220085D92D8945550090A4C5 /* PinitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 6DDB45C02D7ED702006C4A4B /* Pinit */ = { isa = PBXNativeTarget; buildConfigurationList = 6DDB45D72D7ED704006C4A4B /* Build configuration list for PBXNativeTarget "Pinit" */; @@ -93,6 +143,7 @@ D14369DB2D82D1970095F7E3 /* FSCalendar */, D1436C422D8332020095F7E3 /* CombineMoya */, D1436C442D8332020095F7E3 /* Moya */, + D1BCDA052D892DA4002C03BB /* Kingfisher */, ); productName = Pinit; productReference = 6DDB45C12D7ED702006C4A4B /* Pinit.app */; @@ -108,6 +159,10 @@ LastSwiftUpdateCheck = 1620; LastUpgradeCheck = 1620; TargetAttributes = { + 220085D82D8945550090A4C5 = { + CreatedOnToolsVersion = 16.2; + TestTargetID = 6DDB45C02D7ED702006C4A4B; + }; 6DDB45C02D7ED702006C4A4B = { CreatedOnToolsVersion = 16.2; }; @@ -126,6 +181,7 @@ D1286F412D815BED006B5E52 /* XCRemoteSwiftPackageReference "SnapKit" */, D14369DA2D82D1970095F7E3 /* XCRemoteSwiftPackageReference "FSCalendar" */, D1436C412D8332020095F7E3 /* XCRemoteSwiftPackageReference "Moya" */, + D1BCDA042D892DA4002C03BB /* XCRemoteSwiftPackageReference "Kingfisher" */, ); preferredProjectObjectVersion = 77; productRefGroup = 6DDB45C22D7ED702006C4A4B /* Products */; @@ -133,11 +189,19 @@ projectRoot = ""; targets = ( 6DDB45C02D7ED702006C4A4B /* Pinit */, + 220085D82D8945550090A4C5 /* PinitTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 220085D72D8945550090A4C5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 6DDB45BF2D7ED702006C4A4B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -148,6 +212,13 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 220085D52D8945550090A4C5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 6DDB45BD2D7ED702006C4A4B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -157,7 +228,51 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 220085DE2D8945550090A4C5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6DDB45C02D7ED702006C4A4B /* Pinit */; + targetProxy = 220085DD2D8945550090A4C5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ + 220085DF2D8945550090A4C5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = UUYBHY988H; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.justhm.PinitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Pinit.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Pinit"; + }; + name = Debug; + }; + 220085E02D8945550090A4C5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = UUYBHY988H; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.justhm.PinitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Pinit.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Pinit"; + }; + name = Release; + }; 6DDB45D82D7ED704006C4A4B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -336,6 +451,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 220085E12D8945550090A4C5 /* Build configuration list for PBXNativeTarget "PinitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 220085DF2D8945550090A4C5 /* Debug */, + 220085E02D8945550090A4C5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 6DDB45BC2D7ED702006C4A4B /* Build configuration list for PBXProject "Pinit" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -381,6 +505,14 @@ minimumVersion = 15.0.3; }; }; + D1BCDA042D892DA4002C03BB /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 8.3.1; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -404,6 +536,11 @@ package = D1436C412D8332020095F7E3 /* XCRemoteSwiftPackageReference "Moya" */; productName = Moya; }; + D1BCDA052D892DA4002C03BB /* Kingfisher */ = { + isa = XCSwiftPackageProductDependency; + package = D1BCDA042D892DA4002C03BB /* XCRemoteSwiftPackageReference "Kingfisher" */; + productName = Kingfisher; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 6DDB45B92D7ED702006C4A4B /* Project object */; diff --git a/Pinit/Pinit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Pinit/Pinit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 552d0d2..f02d0a9 100644 --- a/Pinit/Pinit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Pinit/Pinit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "2846ae63f5735de68c00932195b6dd3fb6654b062e71b1d4b2101dec6280e5a8", + "originHash" : "86c553cb8bafa087a7fc64a9061346e0b7f03e571b03cf0e0030f88f4fb5e75f", "pins" : [ { "identity" : "alamofire", @@ -19,6 +19,15 @@ "version" : "2.8.4" } }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "4c6b067f96953ee19526e49e4189403a2be21fb3", + "version" : "8.3.1" + } + }, { "identity" : "moya", "kind" : "remoteSourceControl", diff --git a/Pinit/Pinit/AppDelegate.swift b/Pinit/Pinit/AppDelegate.swift index 2ecd001..5faf57b 100644 --- a/Pinit/Pinit/AppDelegate.swift +++ b/Pinit/Pinit/AppDelegate.swift @@ -62,19 +62,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { }() // MARK: - Core Data Saving support - func saveContext () { - let context = persistentContainer.viewContext - if context.hasChanges { - do { - try context.save() - } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nserror = error as NSError - fatalError("Unresolved error \(nserror), \(nserror.userInfo)") - } - } - } +// func saveContext () { +// let context = persistentContainer.viewContext +// if context.hasChanges { +// do { +// try context.save() +// } catch { +// // Replace this implementation with code to handle the error appropriately. +// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. +// let nserror = error as NSError +// fatalError("Unresolved error \(nserror), \(nserror.userInfo)") +// } +// } +// } } diff --git a/Pinit/Pinit/DesignSystem/DesignSystem.swift b/Pinit/Pinit/DesignSystem/DesignSystem.swift index 6362e95..b7ff268 100644 --- a/Pinit/Pinit/DesignSystem/DesignSystem.swift +++ b/Pinit/Pinit/DesignSystem/DesignSystem.swift @@ -7,6 +7,22 @@ import UIKit +enum DesignSystemColor { + case Purple + case Lavender +} +extension DesignSystemColor { + var value: UIColor { + switch self { + case .Purple: + UIColor(hex: "#561CE2") + case .Lavender: + UIColor(hex: "#6450E2") + + } + } +} + enum DesignSystemFont { case Pretendard_Bold8 case Pretendard_Bold12 diff --git a/Pinit/Pinit/LaunchScreen.storyboard b/Pinit/Pinit/LaunchScreen.storyboard new file mode 100644 index 0000000..bcb846e --- /dev/null +++ b/Pinit/Pinit/LaunchScreen.storyboard @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pinit/Pinit/Models/WeatherModel.swift b/Pinit/Pinit/Models/WeatherModel.swift index 5ef62e7..d5d92c9 100644 --- a/Pinit/Pinit/Models/WeatherModel.swift +++ b/Pinit/Pinit/Models/WeatherModel.swift @@ -8,8 +8,8 @@ import Foundation struct WeatherResponse : Codable { - let coord: Coord? - let weather: [Weather]? + let coord: Coord + let weather: [Weather] } // MARK: - Coord diff --git a/Pinit/Pinit/Repository/CoreData/DBRepository.swift b/Pinit/Pinit/Repository/CoreData/DBRepository.swift index 605e4db..114dfa6 100644 --- a/Pinit/Pinit/Repository/CoreData/DBRepository.swift +++ b/Pinit/Pinit/Repository/CoreData/DBRepository.swift @@ -9,18 +9,18 @@ import UIKit import CoreData protocol DBRepository { - @discardableResult func addPin(pin: PinEntity) -> Bool - @discardableResult func deletePin(id: UUID) -> Bool - @discardableResult func updatePin(pin: PinEntity) -> Bool + func addPin(pin: PinEntity, filePath: String?) -> Bool + func deletePin(id: UUID) -> Bool + func updatePin(pin: PinEntity, filePath: String?) -> Bool - func fetchPinsAll() -> [PinEntity] - func fetchPinsByDate(date: Date) -> [PinEntity] + func fetchPinsAll(completion: @escaping ([PinDTO]) -> Void) + func fetchPinsByDate(date: Date, completion: @escaping ([PinDTO]) -> Void) - @discardableResult func addReview(review: ReviewEntity) -> Bool - @discardableResult func deleteReview(id: UUID) -> Bool - @discardableResult func updateReview(review: ReviewEntity) -> Bool - func fetchReviewsByPinId(pinID: UUID) -> [ReviewEntity] + func addReview(review: ReviewEntity) -> Bool + func deleteReview(id: UUID) -> Bool + func updateReview(review: ReviewEntity) -> Bool + func fetchReviewsByPinId(pinID: UUID, completion: @escaping ([ReviewDTO]) -> Void) } #warning("add 관련 기능 저장하고서 저장이 성공했는지에 따른 return 필요할듯?") final class DBRepositoryImpl: DBRepository { @@ -30,7 +30,7 @@ final class DBRepositoryImpl: DBRepository { self.context = context } - func addPin(pin: PinEntity) -> Bool { + func addPin(pin: PinEntity, filePath: String?) -> Bool { guard let entity = NSEntityDescription.entity(forEntityName: "PinDTO", in: context) else { return false } @@ -44,10 +44,8 @@ final class DBRepositoryImpl: DBRepository { pinDTO.setValue(pin.description, forKey: "content") pinDTO.setValue(pin.address, forKey: "address") pinDTO.setValue(pin.weather, forKey: "weather") - if let image = pin.mediaPath, - let filePath = saveImageToDocuments(image: image, fileName: pin.pin_id.uuidString) { - pinDTO.setValue(filePath, forKey: "mediaPath") - } + pinDTO.setValue(filePath, forKey: "mediaPath") + saveContext() return true } @@ -73,7 +71,7 @@ final class DBRepositoryImpl: DBRepository { return false } - func updatePin(pin: PinEntity) -> Bool { + func updatePin(pin: PinEntity, filePath: String?) -> Bool { let request = PinDTO.fetchRequest() request.predicate = NSPredicate(format: "pin_id == %@", pin.pin_id as CVarArg) // UUID로 해당 데이터 찾기 @@ -92,16 +90,7 @@ final class DBRepositoryImpl: DBRepository { pinDTO.setValue(pin.description, forKey: "content") pinDTO.setValue(pin.address, forKey: "address") pinDTO.setValue(pin.weather, forKey: "weather") - - // 이미지 업데이트 (기존 이미지 삭제 후 새로 저장) - if let newImage = pin.mediaPath, - let filePath = saveImageToDocuments(image: newImage, fileName: pin.pin_id.uuidString) { - - pinDTO.setValue(filePath, forKey: "mediaPath") - } - else { // 이미지가 삭제되었다면 그냥 path nil로 만들기 - pinDTO.setValue(nil, forKey: "mediaPath") - } + pinDTO.setValue(filePath, forKey: "mediaPath") // 변경사항 저장 saveContext() @@ -112,35 +101,39 @@ final class DBRepositoryImpl: DBRepository { return false } } - - func fetchPinsAll() -> [PinEntity] { - do { - let fetchResult = try context.fetch(PinDTO.fetchRequest()) - return fetchResult.compactMap { dto -> PinEntity? in - return dto.toPinEntity() +#warning("지도 위치별로 도로명주소 시? 군? 별 페치하게하면 더 효율적임 나중에 고려하기") + func fetchPinsAll(completion: @escaping ([PinDTO]) -> Void) { + let context = self.context + context.perform { + do { + let request = PinDTO.fetchRequest() + //request.fetchLimit = 100 //이건 못씀.. 현재 지도 위치별로 패치해오게 변경하면.. 그때도 쓸모는 없을듯? + let fetchResult = try context.fetch(request) + completion(fetchResult) + } catch { + print(error.localizedDescription) + completion([]) } - } catch { - print(error.localizedDescription) - return [] } } - func fetchPinsByDate(date: Date) -> [PinEntity] { - // date로 저장하면 시간도 같이 저장되기 때문에 00~24시 까지를 가져옴 - let calendar = Calendar.current - let startOfDay = calendar.startOfDay(for: date) // 당일 00:00 - let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)! // 익일 00:00 (당일 23:59까지 포함) - - let request = PinDTO.fetchRequest() - request.predicate = NSPredicate(format: "date >= %@ AND date < %@", startOfDay as NSDate, endOfDay as NSDate) - do { - let fetchResult = try context.fetch(request) - return fetchResult.compactMap { dto -> PinEntity? in - return dto.toPinEntity() + func fetchPinsByDate(date: Date, completion: @escaping ([PinDTO]) -> Void) { + let context = self.context + context.perform { + // date로 저장하면 시간도 같이 저장되기 때문에 00~24시 까지를 가져옴 + let calendar = Calendar.current + let startOfDay = calendar.startOfDay(for: date) // 당일 00:00 + let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)! // 익일 00:00 (당일 23:59까지 포함) + + let request = PinDTO.fetchRequest() + request.predicate = NSPredicate(format: "date >= %@ AND date < %@", startOfDay as NSDate, endOfDay as NSDate) + do { + let fetchResult = try context.fetch(request) + completion(fetchResult) + } catch { + print(error.localizedDescription) + completion([]) } - } catch { - print(error.localizedDescription) - return [] } } @@ -205,18 +198,19 @@ final class DBRepositoryImpl: DBRepository { } } - func fetchReviewsByPinId(pinID: UUID) -> [ReviewEntity] { - let request = ReviewDTO.fetchRequest() - request.predicate = NSPredicate(format: "pinID == %@", pinID as CVarArg) - do { - let fetchResult = try context.fetch(request) - return fetchResult.compactMap { dto -> ReviewEntity? in - return dto.toReviewEntity() + func fetchReviewsByPinId(pinID: UUID, completion: @escaping ([ReviewDTO]) -> Void) { + let context = self.context + context.perform { + let request = ReviewDTO.fetchRequest() + request.predicate = NSPredicate(format: "pinID == %@", pinID as CVarArg) + do { + let fetchResult = try context.fetch(request) + completion(fetchResult) + } + catch { + print(error.localizedDescription) + completion([]) } - } - catch { - print(error.localizedDescription) - return [] } } private func saveContext () { @@ -224,29 +218,8 @@ final class DBRepositoryImpl: DBRepository { do { try context.save() } catch { - // Replace this implementation with code to handle the error appropriately. - // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - let nserror = error as NSError - fatalError("Unresolved error \(nserror), \(nserror.userInfo)") + print("CoreData 저장 실패 \(error.localizedDescription)") } } } } - -extension DBRepositoryImpl { - // 이미지 저장 - private func saveImageToDocuments(image: UIImage, fileName: String) -> String? { - if let data = image.jpegData(compressionQuality: 1.0) { - let filePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(fileName) - do { - try data.write(to: filePath) - return filePath.path - } catch { - print("Failed to save image to documents: \(error)") - } - } - return nil - } -} - -// NSFetchResultsController ?? diff --git a/Pinit/Pinit/Repository/CoreData/PinDTO+CoreDataClass.swift b/Pinit/Pinit/Repository/CoreData/PinDTO+CoreDataClass.swift index 8967a79..0431ab2 100644 --- a/Pinit/Pinit/Repository/CoreData/PinDTO+CoreDataClass.swift +++ b/Pinit/Pinit/Repository/CoreData/PinDTO+CoreDataClass.swift @@ -11,7 +11,7 @@ import CoreData @objc(PinDTO) public class PinDTO: NSManagedObject { - func toPinEntity() -> PinEntity? { + func toPinEntity(image: UIImage?) -> PinEntity? { guard let pin_id = pin_id, let title = title, let address = address, @@ -20,11 +20,6 @@ public class PinDTO: NSManagedObject { let content = content else { return nil } - var image: UIImage? = nil // 혹시몰라서 명시 - if let mediaPath = mediaPath { - image = fetchImageFromDocuments(fileName: pin_id.uuidString) - } - return PinEntity( pin_id: pin_id, title: title, @@ -38,12 +33,5 @@ public class PinDTO: NSManagedObject { ) } - // 로컬 디렉토리에서 이미지 로드 - private func fetchImageFromDocuments(fileName: String) -> UIImage? { - let filePath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(fileName).path - if FileManager.default.fileExists(atPath: filePath) { - return UIImage(contentsOfFile: filePath) - } - return nil - } + } diff --git a/Pinit/Pinit/Repository/ImageStoreRepository.swift b/Pinit/Pinit/Repository/ImageStoreRepository.swift new file mode 100644 index 0000000..40b9ae5 --- /dev/null +++ b/Pinit/Pinit/Repository/ImageStoreRepository.swift @@ -0,0 +1,46 @@ +// +// ImageStoreRepository.swift +// Pinit +// +// Created by 안정흠 on 3/12/25. +// + +import UIKit + +protocol ImageStoreRepository { + func fetchImageFromDocuments(fileName: String) -> UIImage? + func saveImageToDocuments(image: UIImage?, fileName: String) -> String? +} + +final class ImageStoreRepositoryImpl: ImageStoreRepository { + private var fileManager: URL! + + init( + fileManagerURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + ) { + fileManager = fileManagerURL + } + + // 로컬 디렉토리에서 이미지 로드 + func fetchImageFromDocuments(fileName: String) -> UIImage? { + let filePath = fileManager.appendingPathComponent(fileName).path + if FileManager.default.fileExists(atPath: filePath) { + return UIImage(contentsOfFile: filePath) + } + return nil + } + + // 이미지 저장 + func saveImageToDocuments(image: UIImage?, fileName: String) -> String? { + if let data = image?.jpegData(compressionQuality: 1.0) { + let filePath = fileManager.appendingPathComponent(fileName) + do { + try data.write(to: filePath) + return filePath.path + } catch { + print("Failed to save image to documents: \(error)") + } + } + return nil + } +} diff --git a/Pinit/Pinit/Repository/Repository.swift b/Pinit/Pinit/Repository/Repository.swift deleted file mode 100644 index dc7d2a7..0000000 --- a/Pinit/Pinit/Repository/Repository.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// Repository.swift -// Pinit -// -// Created by 안정흠 on 3/12/25. -// - -import Foundation - diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/100.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..735634d Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/102.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/102.png new file mode 100644 index 0000000..1c171eb Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/102.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/1024.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..f877ad1 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/108.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/108.png new file mode 100644 index 0000000..5cfadb0 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/108.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/114.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..57c0aa2 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/120.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..4588286 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/128.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 0000000..0a76b28 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/144.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..235c333 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/152.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..ffb08c6 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/16.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000..e87d6e9 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/167.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..78614c7 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/172.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000..34053e3 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/172.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/180.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..8ee0f36 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/196.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/196.png new file mode 100644 index 0000000..c48fd1e Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/196.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/20.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..7228218 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/216.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/216.png new file mode 100644 index 0000000..171add4 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/216.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/234.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/234.png new file mode 100644 index 0000000..71d6ac4 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/234.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/256.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000..fe63f10 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/258.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/258.png new file mode 100644 index 0000000..7206fdf Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/258.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/29.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..d097dd2 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/32.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000..096846a Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/40.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..4f6be3b Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/48.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/48.png new file mode 100644 index 0000000..32f00ac Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/48.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/50.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..9b46bd2 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/512.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000..9ac0fbe Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/55.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000..93c653e Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/55.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/57.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..7751f73 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/58.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..aaea304 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/60.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..49afbe8 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/64.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000..9ca7b41 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/66.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/66.png new file mode 100644 index 0000000..c566da4 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/66.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/72.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..a0ce5cf Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/76.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..835d3ee Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/80.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..2539bb5 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/87.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..823368b Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/88.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000..d85be22 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/88.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/92.png b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/92.png new file mode 100644 index 0000000..e095979 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/92.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index 2305880..1319290 100644 --- a/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Pinit/Pinit/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,35 +1 @@ -{ - "images" : [ - { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"102.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"45mm","scale":"2x","size":"51x51","expected-size":"102","role":"appLauncher"},{"idiom":"watch","filename":"108.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"49mm","scale":"2x","size":"54x54","expected-size":"108","role":"appLauncher"},{"idiom":"watch","filename":"92.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"41mm","scale":"2x","size":"46x46","expected-size":"92","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"234.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"45mm","scale":"2x","size":"117x117","expected-size":"234","role":"quickLook"},{"idiom":"watch","filename":"258.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"49mm","scale":"2x","size":"129x129","expected-size":"258","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"idiom":"watch","filename":"66.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"45mm","scale":"2x","size":"33x33","expected-size":"66","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"},{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} \ No newline at end of file diff --git a/Pinit/Pinit/Resources/Assets.xcassets/Splash.imageset/AppIcon~ios-marketing.png b/Pinit/Pinit/Resources/Assets.xcassets/Splash.imageset/AppIcon~ios-marketing.png new file mode 100644 index 0000000..f4be31d Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/Splash.imageset/AppIcon~ios-marketing.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/Splash.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/Splash.imageset/Contents.json new file mode 100644 index 0000000..08ec353 --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/Splash.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "AppIcon~ios-marketing.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01d.imageset/01d.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01d.imageset/01d.png new file mode 100644 index 0000000..87d2a51 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01d.imageset/01d.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01d.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01d.imageset/Contents.json new file mode 100644 index 0000000..60e45fb --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01d.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "01d.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01n.imageset/01n.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01n.imageset/01n.png new file mode 100644 index 0000000..527137b Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01n.imageset/01n.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01n.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01n.imageset/Contents.json new file mode 100644 index 0000000..8a8cf33 --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/01n.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "01n.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02d.imageset/02d.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02d.imageset/02d.png new file mode 100644 index 0000000..01c3c9b Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02d.imageset/02d.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02d.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02d.imageset/Contents.json new file mode 100644 index 0000000..97441fe --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02d.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "02d.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02n.imageset/02n.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02n.imageset/02n.png new file mode 100644 index 0000000..b6583d2 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02n.imageset/02n.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02n.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02n.imageset/Contents.json new file mode 100644 index 0000000..8d62610 --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/02n.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "02n.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03d.imageset/03d.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03d.imageset/03d.png new file mode 100644 index 0000000..c0f7e07 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03d.imageset/03d.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03d.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03d.imageset/Contents.json new file mode 100644 index 0000000..b0df74d --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03d.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "03d.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03n.imageset/03n.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03n.imageset/03n.png new file mode 100644 index 0000000..c0f7e07 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03n.imageset/03n.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03n.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03n.imageset/Contents.json new file mode 100644 index 0000000..a6a0c76 --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/03n.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "03n.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04d.imageset/04d.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04d.imageset/04d.png new file mode 100644 index 0000000..34346dc Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04d.imageset/04d.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04d.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04d.imageset/Contents.json new file mode 100644 index 0000000..9fc19d7 --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04d.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "04d.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04n.imageset/04n.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04n.imageset/04n.png new file mode 100644 index 0000000..34346dc Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04n.imageset/04n.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04n.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04n.imageset/Contents.json new file mode 100644 index 0000000..4a444b7 --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/04n.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "04n.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09d.imageset/09d.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09d.imageset/09d.png new file mode 100644 index 0000000..022feb3 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09d.imageset/09d.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09d.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09d.imageset/Contents.json new file mode 100644 index 0000000..5f72daf --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09d.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "09d.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09n.imageset/09n.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09n.imageset/09n.png new file mode 100644 index 0000000..022feb3 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09n.imageset/09n.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09n.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09n.imageset/Contents.json new file mode 100644 index 0000000..226ddbb --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/09n.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "09n.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10d.imageset/10d.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10d.imageset/10d.png new file mode 100644 index 0000000..12d13ae Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10d.imageset/10d.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10d.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10d.imageset/Contents.json new file mode 100644 index 0000000..c75e9ea --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10d.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "10d.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10n.imageset/10n.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10n.imageset/10n.png new file mode 100644 index 0000000..1f884d1 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10n.imageset/10n.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10n.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10n.imageset/Contents.json new file mode 100644 index 0000000..d2ca6fc --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/10n.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "10n.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11d.imageset/11d.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11d.imageset/11d.png new file mode 100644 index 0000000..87aa027 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11d.imageset/11d.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11d.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11d.imageset/Contents.json new file mode 100644 index 0000000..a00034c --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11d.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "11d.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11n.imageset/11n.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11n.imageset/11n.png new file mode 100644 index 0000000..87aa027 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11n.imageset/11n.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11n.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11n.imageset/Contents.json new file mode 100644 index 0000000..5c9ffcd --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/11n.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "11n.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13d.imageset/13d.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13d.imageset/13d.png new file mode 100644 index 0000000..5bff6c5 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13d.imageset/13d.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13d.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13d.imageset/Contents.json new file mode 100644 index 0000000..08250c4 --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13d.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "13d.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13n.imageset/13n.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13n.imageset/13n.png new file mode 100644 index 0000000..5bff6c5 Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13n.imageset/13n.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13n.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13n.imageset/Contents.json new file mode 100644 index 0000000..c9e69de --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/13n.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "13n.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50d.imageset/50d.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50d.imageset/50d.png new file mode 100644 index 0000000..a1f918b Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50d.imageset/50d.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50d.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50d.imageset/Contents.json new file mode 100644 index 0000000..bdd7d65 --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50d.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "50d.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50n.imageset/50n.png b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50n.imageset/50n.png new file mode 100644 index 0000000..a1f918b Binary files /dev/null and b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50n.imageset/50n.png differ diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50n.imageset/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50n.imageset/Contents.json new file mode 100644 index 0000000..e871e5b --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/50n.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "50n.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/Contents.json b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Pinit/Pinit/Resources/Assets.xcassets/weatherIcon/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pinit/Pinit/Resources/DIContainer.swift b/Pinit/Pinit/Resources/DIContainer.swift new file mode 100644 index 0000000..0b63644 --- /dev/null +++ b/Pinit/Pinit/Resources/DIContainer.swift @@ -0,0 +1,29 @@ +// +// DIContainer.swift +// Pinit +// +// Created by 안정흠 on 3/18/25. +// + +import Foundation +import CoreData +import Moya + +final class DIContainer: NSObject { + static let usecase: UseCase = { + let container = NSPersistentContainer(name: "Pinit") + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + // context.perform을 제대로 사용하기위해 백그라운드컨텍스트로 가져옴 + // 비동기 작업에 사용됌 + let context = container.newBackgroundContext() + return UseCaseImpl( + dbRepository: DBRepositoryImpl(context: context), + imageStore: ImageStoreRepositoryImpl(), + moyaProvider: MoyaProvider() + ) + }() +} diff --git a/Pinit/Pinit/SceneDelegate.swift b/Pinit/Pinit/SceneDelegate.swift index e4976b3..1022778 100644 --- a/Pinit/Pinit/SceneDelegate.swift +++ b/Pinit/Pinit/SceneDelegate.swift @@ -47,7 +47,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // to restore the scene back to its current state. // Save changes in the application's managed object context when the application transitions to the background. - (UIApplication.shared.delegate as? AppDelegate)?.saveContext() +// (UIApplication.shared.delegate as? AppDelegate)?.saveContext() } diff --git a/Pinit/Pinit/Service/Service.swift b/Pinit/Pinit/Service/Service.swift deleted file mode 100644 index 3a4db0a..0000000 --- a/Pinit/Pinit/Service/Service.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Service.swift -// Pinit -// -// Created by 안정흠 on 3/12/25. -// - -import Foundation - -protocol Service { - -} diff --git a/Pinit/Pinit/Service/UseCase.swift b/Pinit/Pinit/Service/UseCase.swift new file mode 100644 index 0000000..40b4974 --- /dev/null +++ b/Pinit/Pinit/Service/UseCase.swift @@ -0,0 +1,122 @@ +// +// UseCase.swift +// Pinit +// +// Created by 안정흠 on 3/12/25. +// + +import UIKit +import Moya + +protocol UseCase { + + /* + Create, Update, Delete는 하나의 entity만 처리하기에 UIBlocking이 없어 그냥 처리했지만, + Read(fetch)의 경우 많은 데이터가 한번에 올 것을 고려해서 비동기로 처리함 + */ + + @discardableResult func addPin(pin: PinEntity) -> Bool + @discardableResult func updatePin(pin: PinEntity) -> Bool + @discardableResult func deletePin(pinID: UUID) -> Bool + func fetchAllPins(completion: @escaping ([PinEntity]) -> Void) + func fetchPinsByDate(date: Date, completion: @escaping ([PinEntity]) -> Void) + + func fetchCurrentWeather(latitude: Double, longitude: Double, completion: @escaping (WeatherResponse?) -> Void) + + @discardableResult func addReview(review: ReviewEntity) -> Bool + @discardableResult func updateReview(review: ReviewEntity) -> Bool + @discardableResult func deleteReview(reviewId: UUID) -> Bool + func fetchAllReviewsByPinID(pinID: UUID, completion: @escaping ([ReviewEntity]) -> Void) + +} + +final class UseCaseImpl: UseCase { + let provider: MoyaProvider + let dbRepository: DBRepository + let imageStore: ImageStoreRepository + + init(dbRepository: DBRepository, imageStore: ImageStoreRepository, moyaProvider: MoyaProvider) { + // DBRepositoryImpl(context: NSManagedObjectContext) + self.dbRepository = dbRepository + self.imageStore = imageStore + self.provider = moyaProvider + } + + func addPin(pin: PinEntity) -> Bool { + let filePath = imageStore.saveImageToDocuments(image: pin.mediaPath, fileName: pin.pin_id.uuidString) + return dbRepository.addPin(pin: pin, filePath: filePath) + } + + func updatePin(pin: PinEntity) -> Bool { + let filePath = imageStore.saveImageToDocuments(image: pin.mediaPath, fileName: pin.pin_id.uuidString) + return dbRepository.updatePin(pin: pin, filePath: filePath) + } + + func deletePin(pinID: UUID) -> Bool { + return dbRepository.deletePin(id: pinID) + } + + func fetchAllPins(completion: @escaping ([PinEntity]) -> Void) { + dbRepository.fetchPinsAll { [weak self] items in + let pinEntities = items.compactMap { item -> PinEntity? in + guard let id = item.pin_id else { return nil } + let image = self?.imageStore.fetchImageFromDocuments(fileName: id.uuidString) + return item.toPinEntity(image: image) + } + DispatchQueue.main.async { + completion(pinEntities) + } + } + } + + func fetchPinsByDate(date: Date, completion: @escaping ([PinEntity]) -> Void) { + dbRepository.fetchPinsByDate(date: date, completion: { [weak self] items in + let pinEntities = items.compactMap { item -> PinEntity? in + guard let id = item.pin_id else { return nil } + let image = self?.imageStore.fetchImageFromDocuments(fileName: id.uuidString) + return item.toPinEntity(image: image) + } + DispatchQueue.main.async { + completion(pinEntities) + } + }) + } + + //MARK: - 날씨 정보 받아오기. + func fetchCurrentWeather(latitude: Double, longitude: Double, completion: @escaping (WeatherResponse?) -> Void) { + provider.request(.getWeather(lat: latitude, lon: longitude, lang: "kr")) { result in + switch result { + case .success(let response): + do { + let weatherData = try JSONDecoder().decode(WeatherResponse.self, from: response.data) + completion(weatherData) + } catch { + print("Decoding error: \(error.localizedDescription)") + completion(nil) + } + case .failure(let error): + print("Network error: \(error.localizedDescription)") + completion(nil) + } + } + } + + func addReview(review: ReviewEntity) -> Bool { + return dbRepository.addReview(review: review) + } + + func updateReview(review: ReviewEntity) -> Bool { + return dbRepository.updateReview(review: review) + } + + func deleteReview(reviewId: UUID) -> Bool { + return dbRepository.deleteReview(id: reviewId) + } + + func fetchAllReviewsByPinID(pinID: UUID, completion: @escaping ([ReviewEntity]) -> Void) { + dbRepository.fetchReviewsByPinId(pinID: pinID) { items in + let reviewEntites = items.compactMap { $0.toReviewEntity() } + completion(reviewEntites) + } + } +} diff --git a/Pinit/Pinit/Views/Home/HomeViewController.swift b/Pinit/Pinit/Views/Home/HomeViewController.swift index 94756e7..1c3b5cd 100644 --- a/Pinit/Pinit/Views/Home/HomeViewController.swift +++ b/Pinit/Pinit/Views/Home/HomeViewController.swift @@ -15,6 +15,7 @@ class HomeViewController: UIViewController { private lazy var bottomSheetHeight: CGFloat = view.frame.height / 14 private let circleButtonSize: CGFloat = 48 private let locationmanager = CLLocationManager() + private let usecase: UseCase // MARK: UI Components private var adapter: PinCollectionViewAdapter? @@ -38,6 +39,15 @@ class HomeViewController: UIViewController { return button }() + init(usecase: UseCase) { + self.usecase = usecase + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() setupProperties() @@ -46,6 +56,11 @@ class HomeViewController: UIViewController { setupLayout() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + // loadAnnotations() + } + private func setupMapLocation() { locationmanager.delegate = self mapView.delegate = self @@ -62,14 +77,22 @@ class HomeViewController: UIViewController { ) mapView.isRotateEnabled = false mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomAnnotationView.identifier) -// mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: "ClusterView") - let annotations = PinEntity.sampleData.map { customPin -> CustomAnnotation in - let annotation = CustomAnnotation(pinData: customPin) - //annotation.clusteringIdentifier = "pinCluster" // 클러스터 그룹 지정 - return annotation - } + // mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: "ClusterView") - mapView.addAnnotations(annotations) + loadAnnotations(PinEntity.sampleData) + } + + private func loadAnnotations(_ samepleData: [PinEntity]? = nil) { + if let sampleData = samepleData { // 테스트용 + let annotations = PinEntity.sampleData.map{CustomAnnotation(pinData: $0)} + mapView.addAnnotations(annotations) + } + else { + usecase.fetchAllPins {[weak self] pins in + let annotations = pins.map { CustomAnnotation(pinData: $0) } + self?.mapView.addAnnotations(annotations) + } + } } private func setupProperties() { @@ -159,6 +182,7 @@ extension HomeViewController: MKMapViewDelegate { return clusterView! } + func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { if let annotation = view.annotation as? CustomAnnotation { print("Selected pin ID: \(annotation.pinData.pin_id)") @@ -200,11 +224,18 @@ extension HomeViewController: PinCollectionViewAdapterDelegate { func selectedItem(selected: PinEntity) { print("Selected: \(selected)") // 여기서 화면 이동 + // PinDetailViewController(usecase: usecase, pin: selected) } func deletedItem(deleted: PinEntity?) { - print("Deleted: \(deleted)") + print("Deleted: \(deleted?.title ?? "empty")") // 여기서 CoreData 업데이트 + guard let deleted = deleted else { return } + usecase.deletePin(pinID: deleted.pin_id) + let deletedAnnotation = CustomAnnotation(pinData: deleted) + mapView.removeAnnotation(deletedAnnotation) + // 삭제후 현재 위치의 어노테이션이 뭐가 있는지 다시 로드 + mapView(mapView, regionDidChangeAnimated: true) } } @@ -269,11 +300,6 @@ extension HomeViewController { } } -extension HomeViewController { - -} - - #Preview{ - HomeViewController() + HomeViewController(usecase: DIContainer.usecase) } diff --git a/Pinit/Pinit/Views/Home/LocationManager.swift b/Pinit/Pinit/Views/Home/LocationManager.swift index d5dd142..a13a8a5 100644 --- a/Pinit/Pinit/Views/Home/LocationManager.swift +++ b/Pinit/Pinit/Views/Home/LocationManager.swift @@ -11,7 +11,6 @@ import CoreLocation final class LocationManager: NSObject { let manager = CLLocationManager() - override init() { super.init() manager.delegate = self diff --git a/Pinit/Pinit/Views/MainTabBarController.swift b/Pinit/Pinit/Views/MainTabBarController.swift index 8db9382..0ac2ff8 100644 --- a/Pinit/Pinit/Views/MainTabBarController.swift +++ b/Pinit/Pinit/Views/MainTabBarController.swift @@ -6,9 +6,9 @@ // import UIKit +import CoreData final class MainTabBarController: UITabBarController { - override func viewDidLoad() { super.viewDidLoad() setupViewControllers() @@ -19,23 +19,23 @@ final class MainTabBarController: UITabBarController { let appearance = UITabBarAppearance() // set tabbar opacity appearance.configureWithOpaqueBackground() - + // remove tabbar border line appearance.shadowColor = UIColor.clear - + // set tabbar background color appearance.backgroundColor = .white - + tabBar.standardAppearance = appearance - + if #available(iOS 15.0, *) { - // set tabbar opacity - tabBar.scrollEdgeAppearance = tabBar.standardAppearance + // set tabbar opacity + tabBar.scrollEdgeAppearance = tabBar.standardAppearance } - + // set tabbar tintColor tabBar.tintColor = .black - + // set tabbar shadow tabBar.layer.masksToBounds = false tabBar.layer.shadowColor = UIColor.black.cgColor @@ -45,16 +45,15 @@ final class MainTabBarController: UITabBarController { } private func setupViewControllers() { - let home = UINavigationController(rootViewController: HomeViewController()) - + let home = UINavigationController(rootViewController: HomeViewController(usecase: DIContainer.usecase)) home.tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "house"), selectedImage: UIImage(systemName: "house.fill")) - let pastPin = UINavigationController(rootViewController: PastPinViewController()) + let pastPin = UINavigationController(rootViewController: PastPinViewController(usecase: DIContainer.usecase)) pastPin.tabBarItem = UITabBarItem(title: "PastPin", - image: UIImage(systemName: "calendar"), - tag: 1) + image: UIImage(systemName: "calendar"), + tag: 1) let setting = UINavigationController(rootViewController: SettingViewController()) setting.tabBarItem = UITabBarItem(title: "Setting", diff --git a/Pinit/Pinit/Views/PastPin/PastPinViewController.swift b/Pinit/Pinit/Views/PastPin/PastPinViewController.swift index 1dd8ae5..a460b4f 100644 --- a/Pinit/Pinit/Views/PastPin/PastPinViewController.swift +++ b/Pinit/Pinit/Views/PastPin/PastPinViewController.swift @@ -11,15 +11,13 @@ import SnapKit final class PastPinViewController: UIViewController { - //MARK: - calendar + //MARK: - 모든 PinEntity를 가져옵니다. + let pinData = PinEntity.sampleData + private let usecase: UseCase + + //MARK: - properties private let PinCalendar : FSCalendar = { let calendar = FSCalendar() - calendar.appearance.selectionColor = .systemBlue - calendar.backgroundColor = .white - calendar.layer.cornerRadius = 10 - calendar.locale = Locale(identifier: "ko_KR") - calendar.firstWeekday = 1 - calendar.appearance.headerDateFormat = "YYYY년 MM월" return calendar }() @@ -43,24 +41,31 @@ final class PastPinViewController: UIViewController { //MARK: - life cycle override func viewDidLoad() { super.viewDidLoad() + self.navigationController?.isNavigationBarHidden = true view.backgroundColor = .secondarySystemBackground SetUI() setupAdapter() - // APItest() + calendarUI() } - //MARK: - setui + //MARK: - init + init(usecase: UseCase) { + self.usecase = usecase + super.init(nibName: nil, bundle: nil) + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //MARK: - SetUI private func SetUI() { - PinCalendar.delegate = self - PinCalendar.dataSource = self - view.addSubviews(PinCalendar,PinCollectionView) PinCalendar.snp.makeConstraints{ $0.top.equalTo(view.safeAreaLayoutGuide) $0.leading.trailing.equalToSuperview().inset(16) - $0.height.equalTo(370) + $0.height.equalTo(300) } PinCollectionView.snp.makeConstraints{ $0.top.equalTo(PinCalendar.snp.bottom).offset(16) @@ -69,24 +74,33 @@ final class PastPinViewController: UIViewController { } } - //MARK: - TEST - // private let provider = MoyaProvider() - // - // func APItest() { - // provider.request(.getWeather(lat: 37.56, lon: 126.98, lang: "kr")) { result in - // switch result { - // case let .success(response): - // do { - // let data = try JSONDecoder().decode(WeatherResponse.self, from: response.data) - // print(data) - // } catch { - // print("JSON Parsing Error: \(error)") - // } - // case let .failure(error): - // print("Network Request Failed: \(error.localizedDescription)") - // } - // } - // } + //MARK: - 캘린더 세팅ㅇㅇ + + private func calendarUI(){ + PinCalendar.delegate = self + PinCalendar.dataSource = self + + PinCalendar.backgroundColor = .white + PinCalendar.layer.cornerRadius = 10 + PinCalendar.locale = Locale.init(identifier: "ko_KR") + PinCalendar.firstWeekday = 1 + PinCalendar.appearance.headerDateFormat = "YYYY년 MM월" + PinCalendar.appearance.headerMinimumDissolvedAlpha = 0.0 + PinCalendar.placeholderType = .none + + //년월 폰트 + PinCalendar.appearance.headerTitleFont = DesignSystemFont.Pretendard_Bold20.value + PinCalendar.appearance.headerTitleColor = .black + //요일 폰트 + PinCalendar.appearance.weekdayFont = DesignSystemFont.Pretendard_Bold12.value + PinCalendar.appearance.weekdayTextColor = .black + //날짜 폰트 + PinCalendar.appearance.titleFont = DesignSystemFont.Pretendard_Medium12.value + //오늘 + PinCalendar.appearance.todayColor = .systemGray3 + //오늘 아님 + PinCalendar.appearance.selectionColor = .systemBlue + } } //MARK: - FsCalendar Extension @@ -94,27 +108,33 @@ final class PastPinViewController: UIViewController { extension PastPinViewController : FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance{ func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) { - print("didSelect date: \(date)") + usecase.fetchPinsByDate(date: date) { items in + self.adapter?.data = items + self.PinCollectionView.reloadData() + } + + } + + //해당 pinEntity안에 데이터의 유무에 따라 해당 날짜에 dot이 노출댑니당>.< + func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int { + return pinData.contains { Calendar.current.isDate($0.date, inSameDayAs: date) } ? 1 : 0 } - // func calendar(_ calendar: FSCalendar, imageFor date: Date) -> UIImage? { - // return UIImage(systemName: "scribble") - // } func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, titleDefaultColorFor date: Date) -> UIColor? { let day = Calendar.current.component(.weekday, from: date) - 1 if Calendar.current.shortWeekdaySymbols[day] == "Sun" || Calendar.current.shortWeekdaySymbols[day] == "일" { - return .systemRed + return .systemRed //일요일 색 } else if Calendar.current.shortWeekdaySymbols[day] == "Sat" || Calendar.current.shortWeekdaySymbols[day] == "토" { - return .systemBlue + return .systemBlue //토요일 색 } else { - return .label + return .label //기본색 } } } //MARK: - extension -extension PastPinViewController : PinCollectionViewAdapterDelegate{ +extension PastPinViewController : PinCollectionViewAdapterDelegate { func selectedItem(selected: PinEntity) { //화면 이동 print("selectedItem") @@ -123,10 +143,11 @@ extension PastPinViewController : PinCollectionViewAdapterDelegate{ func deletedItem(deleted: PinEntity?) { //아이템 삭제 클릭시 print("deletedItem") } + } #Preview { - PastPinViewController() + PastPinViewController(usecase: DIContainer.usecase) } diff --git a/Pinit/Pinit/Views/PinEdit/PinEditViewController.swift b/Pinit/Pinit/Views/PinEdit/PinEditViewController.swift index 4f2cd05..d074a45 100644 --- a/Pinit/Pinit/Views/PinEdit/PinEditViewController.swift +++ b/Pinit/Pinit/Views/PinEdit/PinEditViewController.swift @@ -13,17 +13,15 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { private var mapView: MKMapView! //mapview 불러옴 private let datebutton = UIButton() // leftbutton을 클래스 프로퍼티로 변경 - + override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = .white mapView = MKMapView(frame: self.view.bounds) //mkmapview 초기화 및 뷰 추가함 mapView = MKMapView(frame: CGRect(x: 0, y: 60, width: self.view.bounds.width, height: self.view.bounds.height / 4)) //화면의 1/4만 나오게 함 - self.view.addSubview(mapView) //mapview 뷰에 보이게 합니다다아암;ㄹㅇ너리ㅏ머 - + self.view.addSubview(mapView) //mapview 뷰에 보이게 setUpKeyboard() let center = CLLocationCoordinate2D(latitude: 37.506446, longitude: 126.885397) //중심좌표 @@ -40,9 +38,9 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { } closeButton.tintColor = .black closeButton.alpha = 0.7 // 투명도 50% 설정 - + self.view.addSubview(closeButton) - + // Auto Layout 설정 closeButton.snp.makeConstraints { $0.top.equalToSuperview().offset(65) // 상단에서 65포인트 @@ -98,7 +96,7 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { camerabutton.imageView?.contentMode = .scaleAspectFit camerabutton.addTarget(self, action: #selector(cameraButtonTapped), for: .touchUpInside) - + self.view.addSubview(camerabutton) //사진 배경 흰색, 안에 아이콘을 검은색 @@ -148,7 +146,7 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { contentTextView.textColor = UIColor.black contentTextView.font = UIFont.systemFont(ofSize: 16) self.view.addSubview(contentTextView) - + contentTextView.snp.makeConstraints { $0.leading.equalToSuperview().offset(20) $0.top.equalTo(titlefield.snp.bottom).offset(10) @@ -244,7 +242,7 @@ final class PinEditViewController: UIViewController, UITextViewDelegate { textView.textColor = .black } } - + func textViewDidEndEditing(_ textView: UITextView) { if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { textView.text = "남기고자 하는 메모가 있다면 작성해주세요." diff --git a/Pinit/PinitTests/PinitTests.swift b/Pinit/PinitTests/PinitTests.swift new file mode 100644 index 0000000..3f92cfe --- /dev/null +++ b/Pinit/PinitTests/PinitTests.swift @@ -0,0 +1,134 @@ +// +// PinitTests.swift +// PinitTests +// +// Created by 안정흠 on 3/18/25. +// + +import XCTest +import CoreData +import Moya +import Kingfisher +@testable import Pinit + +final class PinitTests: XCTestCase { + var usecase: UseCase! + var context: NSManagedObjectContext! + var imageStore: ImageStoreRepository! + var moya: MoyaProvider! + + override func setUpWithError() throws { + let container = NSPersistentContainer(name: "Pinit") + let description = NSPersistentStoreDescription() + description.type = NSInMemoryStoreType // 임시 메모리에 저장되게함. + container.persistentStoreDescriptions = [description] + container.loadPersistentStores { storeDescription, error in + XCTAssertNil(error, "CoreData In-Memory Store 생성 실패") + } + context = container.newBackgroundContext() + + let tempDIR = URL(fileURLWithPath: NSTemporaryDirectory()) + imageStore = ImageStoreRepositoryImpl(fileManagerURL: tempDIR) // 임시 메모리에 사진 저장되게함. + + moya = MoyaProvider() + + usecase = UseCaseImpl( + dbRepository: DBRepositoryImpl(context: context), + imageStore: imageStore, + moyaProvider: moya + ) + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + context = nil + usecase = nil + } + + func test_샘플데이터로_핀추가_확인() throws { + // Given + let pins = PinEntity.sampleData + // When + for pin in pins { + // Then + XCTAssertTrue(usecase.addPin(pin: pin), "Pin 추가 실패") + } + do { + let request = PinDTO.fetchRequest() + let fetchResult = try context.fetch(request) + XCTAssertEqual(fetchResult.count, PinEntity.sampleData.count, "추가된 Pin 개수 다름") + } + catch { + XCTFail("Fetch 실패: \(error.localizedDescription)") + } + } + + func test_핀_전부가져오기() { + // Given + for sample in PinEntity.sampleData { + XCTAssertTrue(usecase.addPin(pin: sample), "Pin 추가 실패") + } + + // When + var pins: [PinEntity] = [] + usecase.fetchAllPins { items in + pins = items + + // Then + XCTAssertEqual(pins.count, PinEntity.sampleData.count, "Pin 개수 다름") + for i in 0.. Bool { + return ( + lhs.pin_id == rhs.pin_id && + lhs.latitude == rhs.latitude && + lhs.longitude == rhs.longitude && + lhs.title == rhs.title && + lhs.description == rhs.description && + lhs.mediaPath == rhs.mediaPath && + lhs.date == rhs.date && + lhs.address == rhs.address && + lhs.weather == rhs.weather + ) + } + + +}