From 04fd929e02898deb54f4a49f91fa05833f061d29 Mon Sep 17 00:00:00 2001 From: nowstring Date: Wed, 27 Feb 2019 10:33:54 +0900 Subject: [PATCH 1/3] Add features about cloudKit --- Bodabi/Bodabi.xcodeproj/project.pbxproj | 53 ++- Bodabi/Bodabi/AppDelegate.swift | 60 ++- .../Bodabi.xcdatamodel/contents | 63 ++- Bodabi/Bodabi/Models/CloudZone.swift | 16 + Bodabi/Bodabi/Models/DefaultsKey.swift | 4 +- Bodabi/Bodabi/Models/Event+Extensions.swift | 67 ++- Bodabi/Bodabi/Models/Friend+Extensions.swift | 58 +++ Bodabi/Bodabi/Models/History+Extensions.swift | 80 +++- Bodabi/Bodabi/Models/Holiday+Extensions.swift | 66 +++ .../Models/Notification+Extensions.swift | 81 +++- .../CloudService/CloudKitManager.swift | 140 ++++++ .../CloudService/CloudManagedObject.swift | 30 ++ .../Services/CloudService/CloudManager.swift | 418 ++++++++++-------- .../CloudService/CloudSyncManager.swift | 246 +++++++++++ .../Services/CloudService/RecordZone.swift | 162 +++++++ .../CoreDataService/CoreDataManager.swift | 170 ++++++- .../CoreDataService/CoreDataModel.swift | 1 + .../InputService/BaseViewController.swift | 6 +- .../Services/InputService/InputManager.swift | 5 + Bodabi/Bodabi/Supporting Files/Info.plist | 4 + 20 files changed, 1473 insertions(+), 257 deletions(-) create mode 100644 Bodabi/Bodabi/Models/CloudZone.swift create mode 100644 Bodabi/Bodabi/Models/Friend+Extensions.swift create mode 100644 Bodabi/Bodabi/Models/Holiday+Extensions.swift create mode 100644 Bodabi/Bodabi/Services/CloudService/CloudKitManager.swift create mode 100644 Bodabi/Bodabi/Services/CloudService/CloudManagedObject.swift create mode 100644 Bodabi/Bodabi/Services/CloudService/CloudSyncManager.swift create mode 100644 Bodabi/Bodabi/Services/CloudService/RecordZone.swift diff --git a/Bodabi/Bodabi.xcodeproj/project.pbxproj b/Bodabi/Bodabi.xcodeproj/project.pbxproj index b921363..d74c0c6 100644 --- a/Bodabi/Bodabi.xcodeproj/project.pbxproj +++ b/Bodabi/Bodabi.xcodeproj/project.pbxproj @@ -68,7 +68,6 @@ 49C86BF8221E483500D63455 /* Error+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C86BF7221E483500D63455 /* Error+Description.swift */; }; 49C86BFA221E48FF00D63455 /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C86BF9221E48FF00D63455 /* TabBar.swift */; }; DB147035220D4363000EA1D7 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB147034220D4363000EA1D7 /* CloudKit.framework */; }; - DB147037220D4707000EA1D7 /* CloudManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB147036220D4707000EA1D7 /* CloudManager.swift */; }; DB2725FE2209720A00F30CBC /* ThanksFriendHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2725FD2209720A00F30CBC /* ThanksFriendHeaderView.swift */; }; DB2726022209740700F30CBC /* ThanksFriendHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = DB2726012209740700F30CBC /* ThanksFriendHeaderView.xib */; }; DB27260A2209AD9700F30CBC /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2726092209AD9700F30CBC /* Item.swift */; }; @@ -128,12 +127,20 @@ F8485CF921FA176B003C935C /* FriendHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8485CF821FA176B003C935C /* FriendHistoryViewController.swift */; }; F8485CFD21FA2CB4003C935C /* FriendHistoryReceiveViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8485CFC21FA2CB4003C935C /* FriendHistoryReceiveViewCell.swift */; }; F85A8BCD22193111006FB44E /* DefaultsKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85A8BCC22193111006FB44E /* DefaultsKey.swift */; }; + F85F3300222369AD002966E4 /* CloudZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85F32FF222369AD002966E4 /* CloudZone.swift */; }; + F85F33042223A83D002966E4 /* CloudManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85F33032223A83D002966E4 /* CloudManager.swift */; }; + F85F33062223ACEC002966E4 /* CloudSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85F33052223ACEC002966E4 /* CloudSyncManager.swift */; }; F8748907221E09110087CFD1 /* RemoteType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8748906221E09110087CFD1 /* RemoteType.swift */; }; F8748909221E463C0087CFD1 /* FriendHistoryReceiveViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F8748908221E463C0087CFD1 /* FriendHistoryReceiveViewCell.xib */; }; F89ED960221294F2006588E1 /* NotificationSchedular.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89ED95F221294F2006588E1 /* NotificationSchedular.swift */; }; - F89ED9692213E5BB006588E1 /* Bodabi.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F89ED9672213E5BB006588E1 /* Bodabi.xcdatamodeld */; }; F89ED96B22151D2F006588E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F89ED96A22151D2F006588E1 /* Assets.xcassets */; }; F89ED97722163A34006588E1 /* InputFriendViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89ED97622163A34006588E1 /* InputFriendViewCell.swift */; }; + F8AFF1882221FF9D006767EA /* CloudManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF1872221FF9D006767EA /* CloudManagedObject.swift */; }; + F8AFF18A2221FFCE006767EA /* CloudKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF1892221FFCE006767EA /* CloudKitManager.swift */; }; + F8AFF18C2222063C006767EA /* Friend+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF18B2222063C006767EA /* Friend+Extensions.swift */; }; + F8AFF18E2222064C006767EA /* Holiday+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF18D2222064C006767EA /* Holiday+Extensions.swift */; }; + F8AFF191222209E4006767EA /* Bodabi.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF18F222209E4006767EA /* Bodabi.xcdatamodeld */; }; + F8AFF19322234933006767EA /* RecordZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF19222234933006767EA /* RecordZone.swift */; }; F8CB544A221D069D001693E2 /* UINavigationController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CB5449221D069D001693E2 /* UINavigationController+Extensions.swift */; }; F8E7D5B0222089EF00F1F537 /* Int+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E7D5AF222089EF00F1F537 /* Int+Extensions.swift */; }; /* End PBXBuildFile section */ @@ -203,7 +210,6 @@ 49C86BF9221E48FF00D63455 /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = ""; }; DB147032220D426A000EA1D7 /* Bodabi.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bodabi.entitlements; sourceTree = ""; }; DB147034220D4363000EA1D7 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; - DB147036220D4707000EA1D7 /* CloudManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudManager.swift; sourceTree = ""; }; DB2725FD2209720A00F30CBC /* ThanksFriendHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThanksFriendHeaderView.swift; sourceTree = ""; }; DB2726012209740700F30CBC /* ThanksFriendHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ThanksFriendHeaderView.xib; sourceTree = ""; }; DB2726092209AD9700F30CBC /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; @@ -263,12 +269,20 @@ F8485CF821FA176B003C935C /* FriendHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FriendHistoryViewController.swift; path = Bodabi/Views/FriendHistory/FriendHistoryViewController.swift; sourceTree = SOURCE_ROOT; }; F8485CFC21FA2CB4003C935C /* FriendHistoryReceiveViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FriendHistoryReceiveViewCell.swift; path = Bodabi/Views/FriendHistory/FriendHistoryReceiveViewCell.swift; sourceTree = SOURCE_ROOT; }; F85A8BCC22193111006FB44E /* DefaultsKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsKey.swift; sourceTree = ""; }; + F85F32FF222369AD002966E4 /* CloudZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudZone.swift; sourceTree = ""; }; + F85F33032223A83D002966E4 /* CloudManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudManager.swift; sourceTree = ""; }; + F85F33052223ACEC002966E4 /* CloudSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudSyncManager.swift; sourceTree = ""; }; F8748906221E09110087CFD1 /* RemoteType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteType.swift; sourceTree = ""; }; F8748908221E463C0087CFD1 /* FriendHistoryReceiveViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FriendHistoryReceiveViewCell.xib; sourceTree = ""; }; F89ED95F221294F2006588E1 /* NotificationSchedular.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSchedular.swift; sourceTree = ""; }; - F89ED9682213E5BB006588E1 /* Bodabi.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Bodabi.xcdatamodel; sourceTree = ""; }; F89ED96A22151D2F006588E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; F89ED97622163A34006588E1 /* InputFriendViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputFriendViewCell.swift; sourceTree = ""; }; + F8AFF1872221FF9D006767EA /* CloudManagedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudManagedObject.swift; sourceTree = ""; }; + F8AFF1892221FFCE006767EA /* CloudKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitManager.swift; sourceTree = ""; }; + F8AFF18B2222063C006767EA /* Friend+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Friend+Extensions.swift"; sourceTree = ""; }; + F8AFF18D2222064C006767EA /* Holiday+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Holiday+Extensions.swift"; sourceTree = ""; }; + F8AFF190222209E4006767EA /* Bodabi.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Bodabi.xcdatamodel; sourceTree = ""; }; + F8AFF19222234933006767EA /* RecordZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordZone.swift; sourceTree = ""; }; F8CB5449221D069D001693E2 /* UINavigationController+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Extensions.swift"; sourceTree = ""; }; F8E7D5AF222089EF00F1F537 /* Int+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -358,12 +372,15 @@ F82BE25C2211309B00C194F1 /* History+Extensions.swift */, F82BE25E221130B400C194F1 /* Notification+Extensions.swift */, F82BE260221130D000C194F1 /* Event+Extensions.swift */, - F89ED9672213E5BB006588E1 /* Bodabi.xcdatamodeld */, 491DD81D221505340093720A /* Tag.swift */, F85A8BCC22193111006FB44E /* DefaultsKey.swift */, 49C86BF9221E48FF00D63455 /* TabBar.swift */, F8748906221E09110087CFD1 /* RemoteType.swift */, F8197BAE221F983300984C8B /* DefaultHolidayType.swift */, + F8AFF18B2222063C006767EA /* Friend+Extensions.swift */, + F8AFF18D2222064C006767EA /* Holiday+Extensions.swift */, + F8AFF18F222209E4006767EA /* Bodabi.xcdatamodeld */, + F85F32FF222369AD002966E4 /* CloudZone.swift */, ); path = Models; sourceTree = ""; @@ -626,7 +643,11 @@ F8CB544D221DEEF6001693E2 /* CloudService */ = { isa = PBXGroup; children = ( - DB147036220D4707000EA1D7 /* CloudManager.swift */, + F8AFF1872221FF9D006767EA /* CloudManagedObject.swift */, + F8AFF1892221FFCE006767EA /* CloudKitManager.swift */, + F8AFF19222234933006767EA /* RecordZone.swift */, + F85F33032223A83D002966E4 /* CloudManager.swift */, + F85F33052223ACEC002966E4 /* CloudSyncManager.swift */, ); path = CloudService; sourceTree = ""; @@ -664,6 +685,9 @@ 4998126921F9D7B4000DB7B2 = { CreatedOnToolsVersion = 10.1; SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 1; + }; com.apple.Push = { enabled = 1; }; @@ -751,7 +775,9 @@ F8367C9621FAC77A00EBA91E /* NSObject+Identifier.swift in Sources */, 497882572217BFCB00ED64A7 /* UIImage+Resize.swift in Sources */, DBF4CEFD21FAE59B001536A8 /* UIViewController+ActionSheet.swift in Sources */, + F8AFF18A2221FFCE006767EA /* CloudKitManager.swift in Sources */, 496683C021FAC662003FBA5B /* HomeTitleViewCell.swift in Sources */, + F8AFF19322234933006767EA /* RecordZone.swift in Sources */, 491B1C4E221BF86B00ECB66F /* String+PhoneFormat.swift in Sources */, 49C86BF4221E406100D63455 /* Error+Alert.swift in Sources */, 49484D012214060E00C09384 /* TagViewController.swift in Sources */, @@ -766,6 +792,7 @@ 49C0850E21FB10020060B02C /* UIViewController+Storyboard.swift in Sources */, 49C084FD21FADDBB0060B02C /* HolidayViewCell.swift in Sources */, 496683B921FAB294003FBA5B /* UIColor+Extensions.swift in Sources */, + F85F33042223A83D002966E4 /* CloudManager.swift in Sources */, F89ED960221294F2006588E1 /* NotificationSchedular.swift in Sources */, 49C0850721FAF2810060B02C /* UpcomingEventViewCell.swift in Sources */, 4967D66022028D0300223ADF /* CalendarView.swift in Sources */, @@ -774,16 +801,18 @@ 49C084F121FAD36D0060B02C /* MyHolidaysViewCell.swift in Sources */, F82C68D42217B579000FFE7E /* SettingAlarmViewController.swift in Sources */, 4976CFC8221BA17400745422 /* SettingContactsViewController.swift in Sources */, + F85F3300222369AD002966E4 /* CloudZone.swift in Sources */, DB272661220A90AE00F30CBC /* ItemCollectionViewFlowLayout.swift in Sources */, F814529721FCCF6B004A2CF0 /* SettingViewCell.swift in Sources */, + F8AFF191222209E4006767EA /* Bodabi.xcdatamodeld in Sources */, 49C084E821FAC9B80060B02C /* HomeViewController.swift in Sources */, DB3873CD22017E5300ADF0CB /* ItemViewCell.swift in Sources */, 49484D072214F9A700C09384 /* TagHeaderViewCell.swift in Sources */, - DB147037220D4707000EA1D7 /* CloudManager.swift in Sources */, DB272663220BCA4200F30CBC /* HolidayInformationView.swift in Sources */, DB3873C922006D3900ADF0CB /* UITextField+BottomLine.swift in Sources */, F80C28AE22040FDB0032DE3E /* CoreDataManager.swift in Sources */, 4978BCCB220D5A6F0052156E /* TableFetchedResultsDelegate.swift in Sources */, + F85F33062223ACEC002966E4 /* CloudSyncManager.swift in Sources */, DBF4CF0921FB8452001536A8 /* UIView+InitFromXib.swift in Sources */, DB47A573221CF055002A2081 /* Error.swift in Sources */, F80C28A122026F0D0032DE3E /* FriendHistoryInformationViewCell.swift in Sources */, @@ -800,7 +829,9 @@ 49C0850A21FAF6AA0060B02C /* UIView+Animation.swift in Sources */, F80C28A822030D500032DE3E /* FriendHistoryHeaderView.swift in Sources */, DB47A582221E3381002A2081 /* HolidayInputModel.swift in Sources */, + F8AFF18E2222064C006767EA /* Holiday+Extensions.swift in Sources */, DBF4CF2921FC42D7001536A8 /* EntryRoute.swift in Sources */, + F8AFF18C2222063C006767EA /* Friend+Extensions.swift in Sources */, 49C0850121FADE640060B02C /* MyHolidayInputViewCell.swift in Sources */, 49484CF42212981800C09384 /* Character+Unicode.swift in Sources */, DBABB194220C4DE000B808F6 /* InputManager.swift in Sources */, @@ -810,9 +841,9 @@ F814529F21FCDDE0004A2CF0 /* NotificationViewCell.swift in Sources */, F8748907221E09110087CFD1 /* RemoteType.swift in Sources */, 49484D0522140F3800C09384 /* SelectedTagViewCell.swift in Sources */, - F89ED9692213E5BB006588E1 /* Bodabi.xcdatamodeld in Sources */, F814529421FCC909004A2CF0 /* SettingViewController.swift in Sources */, F8CB544A221D069D001693E2 /* UINavigationController+Extensions.swift in Sources */, + F8AFF1882221FF9D006767EA /* CloudManagedObject.swift in Sources */, 4969A58E221A789900E189BD /* ContactManager.swift in Sources */, F8485CFD21FA2CB4003C935C /* FriendHistoryReceiveViewCell.swift in Sources */, F8197BAF221F983300984C8B /* DefaultHolidayType.swift in Sources */, @@ -1040,12 +1071,12 @@ /* End XCConfigurationList section */ /* Begin XCVersionGroup section */ - F89ED9672213E5BB006588E1 /* Bodabi.xcdatamodeld */ = { + F8AFF18F222209E4006767EA /* Bodabi.xcdatamodeld */ = { isa = XCVersionGroup; children = ( - F89ED9682213E5BB006588E1 /* Bodabi.xcdatamodel */, + F8AFF190222209E4006767EA /* Bodabi.xcdatamodel */, ); - currentVersion = F89ED9682213E5BB006588E1 /* Bodabi.xcdatamodel */; + currentVersion = F8AFF190222209E4006767EA /* Bodabi.xcdatamodel */; path = Bodabi.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Bodabi/Bodabi/AppDelegate.swift b/Bodabi/Bodabi/AppDelegate.swift index c00db8a..f3920f2 100644 --- a/Bodabi/Bodabi/AppDelegate.swift +++ b/Bodabi/Bodabi/AppDelegate.swift @@ -9,25 +9,34 @@ import UIKit import UserNotifications import CoreData +import CloudKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - let databaseManager = CoreDataManager(modelName: "Bodabi") + let coreDataManager = CoreDataManager(modelName: "Bodabi") func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { UIApplication.shared.applicationIconBadgeNumber = 0 setUserDefaults() - databaseManager.load() + coreDataManager.load() + CloudManager.setCoreDataManager(manager: coreDataManager) + + registerForNotifications(application: application) updateDeliveredNotification() + CloudManager.createCustomZone() + CloudManager.subscribeToChanges() + + + let tabBarController = window?.rootViewController for navigationController in tabBarController?.children ?? [] { let viewController = navigationController.children.first as? CoreDataManagerClient - viewController?.setDatabaseManager(databaseManager) + viewController?.setCoreDataManager(coreDataManager) } return true } @@ -42,10 +51,47 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func applicationWillEnterForeground(_ application: UIApplication) { + CloudManager.pull { error in + if let error = error { + print(error.localizedDescription) + } + } updateDeliveredNotification() resetApplicationIconBadge() } + func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + print("Received notification") + + guard let userInfo = userInfo as? [String: NSObject] else { return } + guard let _ :CKDatabaseNotification = CKNotification(fromRemoteNotificationDictionary: userInfo) as? CKDatabaseNotification else { return +} + + CloudManager.pull { + error in + if let error = error { + print(error.localizedDescription) + } + completionHandler(.newData) + } + } + +// func requestAutorizationRemoteNotification(application: UIApplication) { +// UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { +// granted, error in +// if let error = error { +// print(error.localizedDescription) +// } else { +// if granted { +// DispatchQueue.main.async { +// application.registerForRemoteNotifications() +// application.delegate = self +// } +// } +// } +// } +// } + // MARK: - User Defaults setting private func setUserDefaults() { @@ -67,7 +113,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Method private func saveContext () { - let context = databaseManager.viewContext + let context = coreDataManager.viewContext if context.hasChanges { do { try context.save() @@ -77,7 +123,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - func registerForLocalNotifications(application: UIApplication) { + func registerForNotifications(application: UIApplication) { let center = UNUserNotificationCenter.current() center.requestAuthorization( options: [.badge, .sound, .alert]) { @@ -117,13 +163,13 @@ extension AppDelegate: UNUserNotificationCenterDelegate { let andPredicate = NSCompoundPredicate(type: .and, subpredicates: [predicate, anotherPredicate]) let sortDescriptor = NSSortDescriptor(key: "date", ascending: true) - databaseManager.fetch(type: Notification.self, predicate: andPredicate, sortDescriptor: sortDescriptor) { result in + coreDataManager.fetch(type: Notification.self, predicate: andPredicate, sortDescriptor: sortDescriptor) { result in switch result { case let .failure(error): print(error.localizedDescription) case let .success(notifications): notifications.forEach { - self.databaseManager.updateNotification(object: $0, isHandled: true) { + self.coreDataManager.updateNotification(object: $0, isHandled: true) { switch $0 { case let .failure(error): print(error.localizedDescription) diff --git a/Bodabi/Bodabi/Models/Bodabi.xcdatamodeld/Bodabi.xcdatamodel/contents b/Bodabi/Bodabi/Models/Bodabi.xcdatamodeld/Bodabi.xcdatamodel/contents index 5fca416..a1185d8 100644 --- a/Bodabi/Bodabi/Models/Bodabi.xcdatamodeld/Bodabi.xcdatamodel/contents +++ b/Bodabi/Bodabi/Models/Bodabi.xcdatamodeld/Bodabi.xcdatamodel/contents @@ -1,46 +1,65 @@ + + + + - - - + + + + + + - - + + + + + - - - - - + + + + + + + - - + + - + + + + - - - - + + + + + + + - - - - - + + + + + + \ No newline at end of file diff --git a/Bodabi/Bodabi/Models/CloudZone.swift b/Bodabi/Bodabi/Models/CloudZone.swift new file mode 100644 index 0000000..c993db6 --- /dev/null +++ b/Bodabi/Bodabi/Models/CloudZone.swift @@ -0,0 +1,16 @@ +// +// CloudZone.swift +// Bodabi +// +// Created by jaehyeon lee on 25/02/2019. +// Copyright © 2019 LeeHyeJin. All rights reserved. +// + +import CloudKit + +struct CloudZone: RecordZone { + let databaseType: DatabaseType + init(databaseType: DatabaseType) { + self.databaseType = databaseType + } +} diff --git a/Bodabi/Bodabi/Models/DefaultsKey.swift b/Bodabi/Bodabi/Models/DefaultsKey.swift index 4b04b47..3b13ef4 100644 --- a/Bodabi/Bodabi/Models/DefaultsKey.swift +++ b/Bodabi/Bodabi/Models/DefaultsKey.swift @@ -19,5 +19,7 @@ struct DefaultsKey { static let defaultAlarmDday: String = "defaultAlarmDday" static let favoriteFirstAlarmDday: String = "favoriteFirstAlarmDday" static let favoriteSecondAlarmDday: String = "favoriteSecondAlarmDday" - static let iCloudAccountAvailable: String = "iCloudAccountAvailable" + static let createdCustomZoneBefore: String = "createdCustomZoneBefore" + static let subscribedToPrivateChanges: String = "subscribedToPrivateChanges" + static let zoneChangeToken: String = "zoneChangeToken" } diff --git a/Bodabi/Bodabi/Models/Event+Extensions.swift b/Bodabi/Bodabi/Models/Event+Extensions.swift index 1e14f82..72ec612 100644 --- a/Bodabi/Bodabi/Models/Event+Extensions.swift +++ b/Bodabi/Bodabi/Models/Event+Extensions.swift @@ -6,9 +6,17 @@ // Copyright © 2019년 LeeHyeJin. All rights reserved. // +import CloudKit import Foundation -extension Event { +extension Event: CloudManagedObject { + + public override func awakeFromInsert() { + super.awakeFromInsert() + + setPrimitiveValue(Date(), forKey: "lastUpdate") + prepareForCloudTask() + } // MARK: - Helper @@ -20,5 +28,62 @@ extension Event { } return 0 } + + var recordType: String { + return RemoteType.event + } + + func prepareForCloudTask() { + recordName = recordType + "." + UUID().uuidString + let rawRecordID = CKRecord.ID(recordName: recordName!, zoneID: CloudZone.zoneID) + recordID = try? NSKeyedArchiver.archivedData(withRootObject: rawRecordID, requiringSecureCoding: false) + } + + func convertToRecord() -> CKRecord { + guard let title = title, + let date = date, + let friend = friend else { fatalError("converting to event record failed") } + + let eventRecord = cloudRecord + eventRecord[RemoteEvent.title] = title as NSString + eventRecord[RemoteEvent.date] = date as NSDate + eventRecord[RemoteEvent.favorite] = Int64(truncating: NSNumber(value: favorite)) + + let friendID = friend.cloudRecordID + eventRecord[RemoteEvent.friend] = CKRecord.Reference(recordID: friendID, action: .deleteSelf) + + return eventRecord + } + + func updateWith(record: CKRecord, coreDataManager: CoreDataManager?) { + title = record[RemoteEvent.title] as? String + date = record[RemoteEvent.date] as? Date + guard let favoriteNumber = record[RemoteEvent.favorite] as? Int else { return } + favorite = favoriteNumber == 1 ? true : false + + if let friendReference = record[RemoteEvent.friend] as? CKRecord.Reference { + let friendRecordName = friendReference.recordID.recordName + let predicate = NSPredicate(format: "recordName == %@", friendRecordName) + var friends: [Friend] = [] + coreDataManager?.fetch(type: Friend.self, + predicate: predicate, + sortDescriptor: nil, + completion: { result in + switch result { + case let .failure(error): + print(error.localizedDescription) + case let .success(values): + friends = values + } + }) + if friends.count > 0 { + friend = friends.first + } + } + + recordName = record.recordID.recordName + let archivedID = try? NSKeyedArchiver.archivedData(withRootObject: record.recordID, requiringSecureCoding: false) + recordID = archivedID + } } diff --git a/Bodabi/Bodabi/Models/Friend+Extensions.swift b/Bodabi/Bodabi/Models/Friend+Extensions.swift new file mode 100644 index 0000000..9a6cb52 --- /dev/null +++ b/Bodabi/Bodabi/Models/Friend+Extensions.swift @@ -0,0 +1,58 @@ +// +// Friend+Extensions.swift +// Bodabi +// +// Created by jaehyeon lee on 24/02/2019. +// Copyright © 2019 LeeHyeJin. All rights reserved. +// + +import CloudKit +import Foundation + +extension Friend: CloudManagedObject { + + public override func awakeFromInsert() { + super.awakeFromInsert() + + setPrimitiveValue(Date(), forKey: "lastUpdate") + prepareForCloudTask() + } + + var recordType: String { + return RemoteType.friend + } + + func prepareForCloudTask() { + recordName = recordType + "." + UUID().uuidString + let rawRecordID = CKRecord.ID(recordName: recordName!, zoneID: CloudZone.zoneID) + recordID = try? NSKeyedArchiver.archivedData(withRootObject: rawRecordID, requiringSecureCoding: false) + } + + func convertToRecord() -> CKRecord { + guard let name = name else { fatalError("converting to friend record failed") } + + let friendRecord = cloudRecord + friendRecord[RemoteFriend.name] = name as NSString + friendRecord[RemoteFriend.favorite] = Int64(truncating: NSNumber(value: favorite)) + if phoneNumber != nil, let phoneNumberValue = phoneNumber { + friendRecord[RemoteFriend.phoneNumber] = phoneNumberValue as NSString + } + if tags != nil, let tagsValue = tags { + friendRecord[RemoteFriend.tags] = tagsValue as [NSString] + } + + return friendRecord + } + + func updateWith(record: CKRecord, coreDataManager: CoreDataManager?) { + name = record[RemoteFriend.name] as? String + phoneNumber = record[RemoteFriend.phoneNumber] as? String + tags = record[RemoteFriend.tags] as? [String] + guard let favoriteNumber = record[RemoteFriend.favorite] as? Int else { return } + favorite = favoriteNumber == 1 ? true : false + + recordName = record.recordID.recordName + let archivedID = try? NSKeyedArchiver.archivedData(withRootObject: record.recordID, requiringSecureCoding: false) + recordID = archivedID + } +} diff --git a/Bodabi/Bodabi/Models/History+Extensions.swift b/Bodabi/Bodabi/Models/History+Extensions.swift index 781ecb3..d0500b2 100644 --- a/Bodabi/Bodabi/Models/History+Extensions.swift +++ b/Bodabi/Bodabi/Models/History+Extensions.swift @@ -6,9 +6,17 @@ // Copyright © 2019 LeeHyeJin. All rights reserved. // +import CloudKit import Foundation -extension History { +extension History: CloudManagedObject { + + public override func awakeFromInsert() { + super.awakeFromInsert() + + setPrimitiveValue(Date(), forKey: "lastUpdate") + prepareForCloudTask() + } // MARK: - Helper @@ -28,4 +36,74 @@ extension History { return "\(self.friend?.name ?? "")님께서 \(self.holiday?.addForSuffix() ?? "") \(self.item?.addObjectSuffix() ?? "") 전달해주셨습니다" } } + + var recordType: String { + return RemoteType.history + } + + func prepareForCloudTask() { + recordName = recordType + "." + UUID().uuidString + let rawRecordID = CKRecord.ID(recordName: recordName!, zoneID: CloudZone.zoneID) + recordID = try? NSKeyedArchiver.archivedData(withRootObject: rawRecordID, requiringSecureCoding: false) + } + + func convertToRecord() -> CKRecord { + guard let holiday = holiday, + let date = date, + let item = item, + let friend = friend else { fatalError("converting to event record failed") } + + let historyRecord = cloudRecord + historyRecord[RemoteHistory.holiday] = holiday as NSString + historyRecord[RemoteHistory.item] = item as NSString + historyRecord[RemoteHistory.date] = date as NSDate + historyRecord[RemoteHistory.isTaken] = Int64(truncating: NSNumber(value: isTaken)) + + let friendID = friend.cloudRecordID + historyRecord[RemoteHistory.friend] = CKRecord.Reference(recordID: friendID, action: .deleteSelf) + + return historyRecord + } + + func updateWith(record: CKRecord, coreDataManager: CoreDataManager?) { + holiday = record[RemoteHistory.holiday] as? String + date = record[RemoteHistory.date] as? Date + item = record[RemoteHistory.item] as? String + guard let isTakenNumer = record[RemoteHistory.isTaken] as? Int else { return } + isTaken = isTakenNumer == 1 ? true : false + + if let friendReference = record[RemoteHistory.friend] as? CKRecord.Reference { + let friendRecordName = friendReference.recordID.recordName + let predicate = NSPredicate(format: "recordName == %@", friendRecordName) + var friends: [Friend] = [] + coreDataManager?.fetch(type: Friend.self, + predicate: predicate, + sortDescriptor: nil, + completion: { result in + switch result { + case let .failure(error): + print(error.localizedDescription) + case let .success(values): + friends = values + } + }) + if friends.count > 0 { + friend = friends.first + } else { + guard let friend = self.friend else { return } + coreDataManager?.updateFriend(object: friend, recordName: friendRecordName) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case .success: + break + } + } + } + } + + recordName = record.recordID.recordName + let archivedID = try? NSKeyedArchiver.archivedData(withRootObject: record.recordID, requiringSecureCoding: false) + recordID = archivedID + } } diff --git a/Bodabi/Bodabi/Models/Holiday+Extensions.swift b/Bodabi/Bodabi/Models/Holiday+Extensions.swift new file mode 100644 index 0000000..acb10ba --- /dev/null +++ b/Bodabi/Bodabi/Models/Holiday+Extensions.swift @@ -0,0 +1,66 @@ +// +// Holiday+Extensions.swift +// Bodabi +// +// Created by jaehyeon lee on 24/02/2019. +// Copyright © 2019 LeeHyeJin. All rights reserved. +// + +import CloudKit +import Foundation + +extension Holiday: CloudManagedObject { + + public override func awakeFromInsert() { + super.awakeFromInsert() + + setPrimitiveValue(Date(), forKey: "lastUpdate") + prepareForCloudTask() + } + + var recordType: String { + return RemoteType.holiday + } + + func prepareForCloudTask() { + recordName = recordType + "." + UUID().uuidString + let rawRecordID = CKRecord.ID(recordName: recordName!, zoneID: CloudZone.zoneID) + recordID = try? NSKeyedArchiver.archivedData(withRootObject: rawRecordID, requiringSecureCoding: false) + } + + func convertToRecord() -> CKRecord { + guard let title = title, + let date = date, + let createdDate = createdDate, + let image = image else { fatalError("converting to holiday record failed") } + + let holidayRecord = cloudRecord + holidayRecord[RemoteHoliday.title] = title as NSString + holidayRecord[RemoteHoliday.date] = date as NSDate + holidayRecord[RemoteHoliday.createdDate] = createdDate as NSDate + + let imageURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString + ".dat") + do { + try image.write(to: imageURL) + } catch { + print(error.localizedDescription) + } + let imageAsset = CKAsset(fileURL: imageURL) + holidayRecord[RemoteHoliday.image] = imageAsset + + return holidayRecord + } + + func updateWith(record: CKRecord, coreDataManager: CoreDataManager? = nil) { + title = record[RemoteHoliday.title] as? String + date = record[RemoteHoliday.date] as? Date + createdDate = record[RemoteHoliday.createdDate] as? Date + let imageAsset = record[RemoteHoliday.createdDate] as? CKAsset + guard let imageURL = imageAsset?.fileURL else { return } + image = try? Data(contentsOf: imageURL) + + recordName = record.recordID.recordName + let archivedID = try? NSKeyedArchiver.archivedData(withRootObject: record.recordID, requiringSecureCoding: false) + recordID = archivedID + } +} diff --git a/Bodabi/Bodabi/Models/Notification+Extensions.swift b/Bodabi/Bodabi/Models/Notification+Extensions.swift index 50a6165..ecd2f3d 100644 --- a/Bodabi/Bodabi/Models/Notification+Extensions.swift +++ b/Bodabi/Bodabi/Models/Notification+Extensions.swift @@ -6,10 +6,19 @@ // Copyright © 2019 LeeHyeJin. All rights reserved. // +import CloudKit import Foundation -extension Notification { - +extension Notification: CloudManagedObject { + + public override func awakeFromInsert() { + super.awakeFromInsert() + + setPrimitiveValue(false, forKey: "isRead") + setPrimitiveValue(Date(), forKey: "lastUpdate") + prepareForCloudTask() + } + // MARK: - Helper var sentence: String { @@ -36,8 +45,70 @@ extension Notification { return UserDefaults.standard.integer(forKey: DefaultsKey.defaultAlarmDday) } - public override func awakeFromInsert() { - super.awakeFromInsert() - self.isRead = false + var recordType: String { + return RemoteType.notification + } + + func prepareForCloudTask() { + recordName = recordType + "." + UUID().uuidString + let rawRecordID = CKRecord.ID(recordName: recordName!, zoneID: CloudZone.zoneID) + recordID = try? NSKeyedArchiver.archivedData(withRootObject: rawRecordID, requiringSecureCoding: false) + } + + func convertToRecord() -> CKRecord { + guard let event = event, + let date = date else { fatalError("converting to notification record failed") } + + let notificationRecord = cloudRecord + notificationRecord[RemoteNotification.date] = date as NSDate + notificationRecord[RemoteNotification.isHandled] = Int64(truncating: NSNumber(value: isHandled)) + notificationRecord[RemoteNotification.isRead] = Int64(truncating: NSNumber(value: isRead)) + + let eventID = event.cloudRecordID + notificationRecord[RemoteNotification.event] = CKRecord.Reference(recordID: eventID, action: .deleteSelf) + + return notificationRecord + } + + func updateWith(record: CKRecord, coreDataManager: CoreDataManager?) { + date = record[RemoteNotification.date] as? Date + guard let isHandledNumber = record[RemoteNotification.isHandled] as? Int else { return } + isRead = isHandledNumber == 1 ? true : false + guard let isReadNumber = record[RemoteNotification.isRead] as? Int else { return } + isRead = isReadNumber == 1 ? true : false + + if let eventReference = record[RemoteNotification.event] as? CKRecord.Reference { + let eventRecordName = eventReference.recordID.recordName + let predicate = NSPredicate(format: "recordName == %@", eventRecordName) + var events: [Event] = [] + coreDataManager?.fetch(type: Event.self, + predicate: predicate, + sortDescriptor: nil, + completion: { result in + switch result { + case let .failure(error): + print(error.localizedDescription) + case let .success(values): + events = values + } + }) + if events.count > 0 { + event = events.first + } else { + guard let event = self.event else { return } + coreDataManager?.updateEvent(object: event, recordName: eventRecordName) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case .success: + break + } + } + } + } + + recordName = record.recordID.recordName + let archivedID = try? NSKeyedArchiver.archivedData(withRootObject: record.recordID, requiringSecureCoding: false) + recordID = archivedID } } diff --git a/Bodabi/Bodabi/Services/CloudService/CloudKitManager.swift b/Bodabi/Bodabi/Services/CloudService/CloudKitManager.swift new file mode 100644 index 0000000..348c96c --- /dev/null +++ b/Bodabi/Bodabi/Services/CloudService/CloudKitManager.swift @@ -0,0 +1,140 @@ +//// +//// CloudKitManager.swift +//// Bodabi +//// +//// Created by jaehyeon lee on 24/02/2019. +//// Copyright © 2019 LeeHyeJin. All rights reserved. +//// +// +//import CloudKit +// +//public protocol CloudKitManagerProtocol { +// func cloudRecordChanged(record: CKRecord) +//} +// +//final class CloudKitManager { +// +// private let container = CKContainer.default() +// private let privateDatabase = CKContainer.default().privateCloudDatabase +// private var delegate: CloudKitManagerProtocol? +// +// let subscriptionID: String = "subscriptionBodabi" +// var zoneID: CKRecordZone.ID? +// +// init() {} +// +// func checkCurrentSubscriptions(zone: RecordZone) { +// privateDatabase.fetchAllSubscriptions { subscriptions, error in +// if let error = error { +// print(error.localizedDescription) +// } +// +// if let subscriptions = subscriptions { +// if subscriptions.first(where: { $0.subscriptionID == zone.subscriptionID }) == nil { +// self.subscribeTo(zone: zone) +// } +// } +// } +// } +// +// func subscribeTo(zone: RecordZone) { +// guard let subscription = zone.subscription else { return } +// let operation = CKModifySubscriptionsOperation(subscriptionsToSave: [subscription], +// subscriptionIDsToDelete: nil) +// +// +// operation.modifySubscriptionsCompletionBlock = { subscriptions, _ , error in +// if let error = error { +// print(error.localizedDescription) +// } +// if let subscriptionID = subscriptions?.first?.subscriptionID { +// print("Saved Subscription with \(subscriptionID)") +// } +// } +// zone.database?.add(operation) +// } +// +// func setup(zone: RecordZone) { +// let zoneName = zone.description +// zone.database?.save(CKRecordZone(zoneName: zoneName)) { +// recordZone, error in +// if let error = error { +// print(error.localizedDescription) +// } +// print(recordZone!) +// } +// } +// +// func checkCloudKitSubscriptions() { +// +// checkCurrentSubscriptions(zone: CloudZone.private) +// checkCurrentSubscriptions(zone: CloudZone.shared) +// } +// +// +//// func subscribe() { +//// +//// let subscription = CKDatabaseSubscription(subscriptionID: subscriptionID) +//// privateDatabase.save(subscription) { +//// subscriptionID, error in +//// if error != nil { +//// print(error?.localizedDescription) +//// } +//// print(subscription) +//// } +//// let subscription = CKRecordZoneSubscription(zoneID: CloudKitManager.customZone.zoneID, subscriptionID: subscriptionID) +//// let notificationInfo = CKSubscription.NotificationInfo() +//// notificationInfo.shouldSendContentAvailable = true +//// subscription.notificationInfo = notificationInfo +//// +//// let subscriptionOperation = CKModifySubscriptionsOperation(subscriptionsToSave: [subscription], subscriptionIDsToDelete: []) +//// subscriptionOperation.modifySubscriptionsCompletionBlock = { +//// (_, _, error) in +//// if let error = error { +//// print(error.localizedDescription) +//// } else { +//// UserDefaults.standard.set(true, forKey: DefaultsKey.customZoneSubscription) +//// } +//// } +//// privateDatabase.add(subscriptionOperation) +// +//// func loadZone() { +//// privateDatabase.fetch(withRecordZoneID: zoneID ?? CloudKitManager.customZone.zoneID) { +//// [weak self] retrievedZone, error in +//// if error != nil { +//// print(error!.localizedDescription) +//// let ckError = error! as NSError +//// if ckError.code == CKError.zoneNotFound.rawValue { +//// self?.privateDatabase.save(CloudKitManager.customZone) { +//// newZone, error in +//// if error != nil { +//// print(error!.localizedDescription) +//// } else { +//// guard let retrievedZone = retrievedZone else { return } +//// CloudKitManager.customZone = retrievedZone +//// } +//// } +//// } else { +//// if let retrievedZone = retrievedZone { +//// CloudKitManager.customZone = retrievedZone +//// } +//// } +//// } +//// } +//// } +//// +//// func fetchCloudChanges() { +//// var changeToken: CKServerChangeToken! +//// var changeZoneToken: CKServerChangeToken! +//// +//// guard let zoneID = zoneID else { return } +//// +//// let configuration = CKFetchRecordZoneChangesOperation.ZoneConfiguration() +//// configuration.previousServerChangeToken = changeZoneToken +//// let fetchOperation = CKFetchRecordZoneChangesOperation(recordZoneIDs: [zoneID], configurationsByRecordZoneID: [zoneID: configuration]) +//// fetchOperation.recordZoneFetchCompletionBlock +//// +//// CKFetchRecordZoneChangesOperation(recordZoneIDs: [zoneID], configurationsByRecordZoneID: [CKRecordZone.ID : CKFetchRecordZoneChangesOperation.ZoneConfiguration]?) +//// } +// +//} diff --git a/Bodabi/Bodabi/Services/CloudService/CloudManagedObject.swift b/Bodabi/Bodabi/Services/CloudService/CloudManagedObject.swift new file mode 100644 index 0000000..0f92685 --- /dev/null +++ b/Bodabi/Bodabi/Services/CloudService/CloudManagedObject.swift @@ -0,0 +1,30 @@ +// +// CloudKitManagedObject.swift +// Bodabi +// +// Created by jaehyeon lee on 24/02/2019. +// Copyright © 2019 LeeHyeJin. All rights reserved. +// + +import CloudKit +import Foundation + +protocol CloudManagedObject { + var recordID: Data? { get set } + var recordName: String? { get set } + var recordType: String { get } + var lastUpdate: Date? { get set } + + func convertToRecord() -> CKRecord + func updateWith(record: CKRecord, coreDataManager: CoreDataManager?) +} + +extension CloudManagedObject { + var cloudRecord: CKRecord { + return CKRecord(recordType: recordType, recordID: cloudRecordID) + } + var cloudRecordID: CKRecord.ID { + guard let recordID = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKRecord.ID.self], from: recordID!) else { return CKRecord.ID() } + return recordID as! CKRecord.ID + } +} diff --git a/Bodabi/Bodabi/Services/CloudService/CloudManager.swift b/Bodabi/Bodabi/Services/CloudService/CloudManager.swift index 1147826..a99426c 100644 --- a/Bodabi/Bodabi/Services/CloudService/CloudManager.swift +++ b/Bodabi/Bodabi/Services/CloudService/CloudManager.swift @@ -2,209 +2,259 @@ // CloudManager.swift // Bodabi // -// Created by Kim DongHwan on 08/02/2019. +// Created by jaehyeon lee on 25/02/2019. // Copyright © 2019 LeeHyeJin. All rights reserved. // import CloudKit -import Foundation -public protocol CloudManagerProtocol { - func cloudRecordChanged(record: CKRecord) -} +private let customZoneName = "CloudZone" -struct CloudManager { +final class CloudManager { + + static var privateDatabase: CKDatabase { + return CKContainer.default().privateCloudDatabase + } + + static let zoneID = CKRecordZone.ID(zoneName: customZoneName, ownerName: CKCurrentUserDefaultName) - private let container = CKContainer.default() - private let privateDatabase = CKContainer.default().privateCloudDatabase - public var delegate: CloudManagerProtocol? - public var zoneID: CKRecordZone.ID? + static let createZoneDispatchGroup = DispatchGroup() - init() { - let zone = CKRecordZone(zoneName: "note-zone") - zoneID = zone.zoneID + init() {} + + static func iCloudIsAvailable() -> Bool { + if FileManager.default.ubiquityIdentityToken != nil { + print("cloud task is available") + return true + } else { + print("cloud task is not available") + return false + } } - var iCloudAccountAvailable: Bool { - get { - return UserDefaults.standard.bool(forKey: DefaultsKey.iCloudAccountAvailable) + static func insertRecord(_ newRecord: CKRecord) { + if !CloudManager.iCloudIsAvailable() { + return } - set { - UserDefaults.standard.set(newValue, forKey: DefaultsKey.iCloudAccountAvailable) + + self.privateDatabase.fetch(withRecordID: newRecord.recordID) { (fetchedRecord, error) in + if let error = error as? CKError { + print("Error fetching record:", error) + + if error.code == CKError.unknownItem { + print("No record found with recordID:", newRecord.recordID) + + self.privateDatabase.save(newRecord) { (_, error) in + if let error = error { + print("Error saving new record:", error) + } + } + } + } else if let fetchedRecord = fetchedRecord { + self.privateDatabase.delete(withRecordID: fetchedRecord.recordID) { (_, error) in + if let error = error { + print("Error deleting record:", error) + } else { + self.privateDatabase.save(newRecord) { + (_, error) in + if let error = error { + print("Error saving new record:", error) + } + } + } + } + } } } - func create() { - let record = CKRecord(recordType: RemoteType.friend) - record[RemoteFriend.name] = "이재현" as String - record[RemoteFriend.favorite] = 0 as NSNumber - record[RemoteFriend.tags] = ["학교", "키가 큰"] as NSArray - - privateDatabase.save(record) { record, error in + static func deleteRecord(withID recordID: CKRecord.ID) { + if !CloudManager.iCloudIsAvailable() { + return + } + + self.privateDatabase.delete(withRecordID: recordID) { (recordID, error) in if let error = error { - print(error.localizedDescription) - } else { - print("record created") + print("Error deleting record:", error) } } } - - -// private var databaseManager: DatabaseManager! -// private var friends: [Friend]? -// private var histories: [History]? -// private var holidays: [Holiday]? -// private var events: [Event]? -// private var notifications: [Notification]? + static func createCustomZone() { + if !CloudManager.iCloudIsAvailable() { + return + } + + let createdCustomZone = UserDefaults.standard.bool(forKey: DefaultsKey.createdCustomZoneBefore) + + if !createdCustomZone { + self.createZoneDispatchGroup.enter() + + let customZone = CKRecordZone(zoneID: self.zoneID) + let createZoneOperation = CKModifyRecordZonesOperation(recordZonesToSave: [customZone], recordZoneIDsToDelete: []) + + createZoneOperation.modifyRecordZonesCompletionBlock = { (saved, deleted, error) in + if error != nil { + print("Error creating custom zone: \(String(describing: error))") + } else { + UserDefaults.standard.set(true, forKey: DefaultsKey.createdCustomZoneBefore) + } + + self.createZoneDispatchGroup.leave() + } + createZoneOperation.qualityOfService = .userInitiated + + self.privateDatabase.add(createZoneOperation) + } + } -// func uploadAll() { -// -// } + static func subscribeToChanges() { + if !CloudManager.iCloudIsAvailable() { + return + } + + let subscribedToPrivateChanges = UserDefaults.standard.bool(forKey: DefaultsKey.subscribedToPrivateChanges) + + if !subscribedToPrivateChanges { + let subscription = CKDatabaseSubscription(subscriptionID: "privateChanges") + + let notificationInfo = CKSubscription.NotificationInfo() + notificationInfo.shouldSendContentAvailable = true + subscription.notificationInfo = notificationInfo + + let operation = CKModifySubscriptionsOperation(subscriptionsToSave: [subscription], subscriptionIDsToDelete: []) + operation.qualityOfService = .utility + + operation.modifySubscriptionsCompletionBlock = { (subscriptions, deletedIDs, error) in + if error != nil { + print("Error subscribing to changes: \(String(describing: error))") + } else { + UserDefaults.standard.set(true, forKey: DefaultsKey.subscribedToPrivateChanges) + } + } + + self.privateDatabase.add(operation) + } + + self.createZoneDispatchGroup.notify(queue: DispatchQueue.global()) { + let createdCustomZone = UserDefaults.standard.bool(forKey: DefaultsKey.createdCustomZoneBefore) + + if createdCustomZone { + CloudManager.pull { error in + if let error = error { + print(error.localizedDescription) + } + } + } + } + } -// func uploadFriend() { -// guard let friends: [Friend] = friends else { return } -// friends.forEach { friend in -// let record = CKRecord(recordType: "Friend") -// } -// } -// -// static func fetchAll() { -// fetchFriend() -// fetchHoliday() -// fetchHistory() -// fetchEvent() -// fetchNotification() -// } -// -// static func fetchFriend() { -// let request: NSFetchRequest = Friend.fetchRequest() -// let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) -// request.sortDescriptors = [sortDescriptor] -// if let result = try? databaseManager.viewContext.fetch(request) { -// friends = result -// } -// } -// -// static func fetchHistory() { -// let request: NSFetchRequest = History.fetchRequest() -// let sortDescriptor = NSSortDescriptor(key: "date", ascending: true) -// request.sortDescriptors = [sortDescriptor] -// if let result = try? databaseManager.viewContext.fetch(request) { -// histories = result -// } -// } -// -// static func fetchHoliday() { -// let request: NSFetchRequest = Holiday.fetchRequest() -// let sortDescriptor = NSSortDescriptor(key: "createdDate", ascending: true) -// request.sortDescriptors = [sortDescriptor] -// if let result = try? databaseManager.viewContext.fetch(request) { -// holidays = result -// } -// } -// -// static func fetchEvent() { -// let request: NSFetchRequest = Event.fetchRequest() -// let sortDescriptor = NSSortDescriptor(key: "date", ascending: true) -// request.sortDescriptors = [sortDescriptor] -// if let result = try? databaseManager.viewContext.fetch(request) { -// events = result -// } -// } -// -// static func fetchNotification() { -// let request: NSFetchRequest = Notification.fetchRequest() -// let sortDescriptor = NSSortDescriptor(key: "date", ascending: true) -// request.sortDescriptors = [sortDescriptor] -// if let result = try? databaseManager.viewContext.fetch(request) { -// notifications = result -// } -// } -//} -// -//// MARK: - DatabaseManagerClient -// -//extension CloudManager: DatabaseManagerClient { -// func setDatabaseManager(_ manager: DatabaseManager) { -// databaseManager = manager -// } -//} -// -//// MARK: - Helper Cloud Key Enum -// -//enum FriendKey: String { -// case phoneNumber -// case name -// case favorite -// case tags -// -// var string: String { -// return self.rawValue -// } -//} -// -//enum EventKey: String { -// case title -// case favorite -// case date -// case friend -// -// var string: String { -// return self.rawValue -// } -//} -// -//enum HistoryKey: String { -// case item -// case holiday -// case isTaken -// case date -// case friend -// -// var string: String { -// return self.rawValue -// } -//} -// -//enum HolidayKey: String { -// case title -// case date -// case createdDate -// case image -// -// var string: String { -// return self.rawValue -// } -//} -// -//enum NotificationKey: String { -// case isRead -// case isHandled -// case date -// case event -// -// var string: String { -// return self.rawValue -// } -//} - -// self[key.string] = newValue as? CKRecordValue -// } -// } -// func save() { -// let friendRecord = CKRecord(recordType: "Friend") -// -// friendRecord["name"] = "김동환" as NSString -// -// let container = CKContainer.default() -// let privateDatabase = container.privateCloudDatabase -// -// privateDatabase.save(friendRecord) { (record, error) in -// if let error = error { -// print(error.localizedDescription) -// } -// print(record) -// } -// } + static func pull(completion: @escaping (Error?) -> Void) { + if !CloudManager.iCloudIsAvailable() { + return + } + + var changeToken: CKServerChangeToken? = nil + let changeTokenData = UserDefaults.standard.data(forKey: DefaultsKey.zoneChangeToken) + + if changeTokenData != nil { + do { + changeToken = try NSKeyedUnarchiver.unarchivedObject(ofClass: CKServerChangeToken.self, from: changeTokenData!) + } catch { + changeToken = nil + completion(error) + } + } + + let configuration = CKFetchRecordZoneChangesOperation.ZoneConfiguration() + configuration.previousServerChangeToken = changeToken + + let operation = CKFetchRecordZoneChangesOperation(recordZoneIDs: [self.zoneID], configurationsByRecordZoneID: [self.zoneID : configuration]) + operation.fetchAllChanges = true + + operation.recordChangedBlock = { (record) in + print("recordChangedBlock") + switch record.recordType { + case RemoteType.friend: + CloudManager.writeRecordChangeToFriend(record: record) + case RemoteType.holiday: + CloudManager.writeRecordChangeToFriend(record: record) + case RemoteType.history: + CloudManager.writeRecordChangeToFriend(record: record) + case RemoteType.event: + CloudManager.writeRecordChangeToFriend(record: record) + case RemoteType.notification: + CloudManager.writeRecordChangeToFriend(record: record) + default: + break + } + } + + operation.recordWithIDWasDeletedBlock = { (recordID, recordType) in + print("recordDeletedBlock") + switch recordType { + case RemoteType.friend: + CloudManager.writeRecordDeletionToFriend(recordID: recordID) + case RemoteType.holiday: + CloudManager.writeRecordDeletionToFriend(recordID: recordID) + case RemoteType.history: + CloudManager.writeRecordDeletionToFriend(recordID: recordID) + case RemoteType.event: + CloudManager.writeRecordDeletionToFriend(recordID: recordID) + case RemoteType.notification: + CloudManager.writeRecordDeletionToFriend(recordID: recordID) + default: + break + } + } + + operation.recordZoneChangeTokensUpdatedBlock = { (zoneID, token, data) in + try? coreDataManager?.viewContext.save() + + guard let changeToken = token else { + print("Error getting new changeToken") + return + } + + do { + let changeTokenData = try NSKeyedArchiver.archivedData(withRootObject: changeToken, requiringSecureCoding: true) + UserDefaults.standard.set(changeTokenData, forKey: DefaultsKey.zoneChangeToken) + } catch { + print("Error archiving new changeToken") + completion(error) + } + } + + operation.recordZoneFetchCompletionBlock = { (zoneID, token, _, _, error) in + print("recordZoneFetchCompletionBlock") + if let error = error { + print("Error fetching zone changes:", error) + return + } + + try? coreDataManager?.viewContext.save() + + guard let changeToken = token else { + print("Error getting new changeToken") + return + } + + do { + let changeTokenData = try NSKeyedArchiver.archivedData(withRootObject: changeToken, requiringSecureCoding: true) + UserDefaults.standard.set(changeTokenData, forKey: DefaultsKey.zoneChangeToken) + } catch { + print("Error archiving new changeToken") + completion(error) + } + } + + operation.fetchRecordZoneChangesCompletionBlock = { (error) in + print("fetchRecordZoneChangesCompletionBlock") + + if let error = error { + print("Error fetching zone changes:", error) + } + completion(nil) + } + self.privateDatabase.add(operation) + } } diff --git a/Bodabi/Bodabi/Services/CloudService/CloudSyncManager.swift b/Bodabi/Bodabi/Services/CloudService/CloudSyncManager.swift new file mode 100644 index 0000000..b5d00a2 --- /dev/null +++ b/Bodabi/Bodabi/Services/CloudService/CloudSyncManager.swift @@ -0,0 +1,246 @@ +// +// CloudSyncManager.swift +// Bodabi +// +// Created by jaehyeon lee on 25/02/2019. +// Copyright © 2019 LeeHyeJin. All rights reserved. +// + +import CloudKit +import CoreData + +extension CloudManager { + + static var coreDataManager: CoreDataManager? + + static func insertToCloud(object: CoreDataObject) { + + let record = (object as! CloudManagedObject).convertToRecord() + CloudManager.insertRecord(record) + } + + static func deleteFromCloud(object: CoreDataObject) { + + let objectToDelete = object as! CloudManagedObject + guard let recordName = objectToDelete.recordName else { return } + let recordID = CKRecord.ID(recordName: recordName, zoneID: self.zoneID) + CloudManager.deleteRecord(withID: recordID) + } + + static func writeRecordChangeToFriend(record: CKRecord) { + + let predicate = NSPredicate(format: "recordName == %@", record.recordID.recordName) + + coreDataManager?.fetch(type: Friend.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(friends): + if let friend = friends.first { + friend.updateWith(record: record, coreDataManager: coreDataManager) + } else { +// coreDataManager?.createFriend(name: "", tags: [], phoneNumber: "") { +// switch $0 { +// case let .failure(error): +// print(error.localizedDescription) +// case let .success(friend): +// friend.updateWith(record: record, coreDataManager: coreDataManager) +// } +// } + } + } + } + } + + static func writeRecordChangeToHoliday(record: CKRecord) { + + let predicate = NSPredicate(format: "recordName == %@", record.recordID.recordName) + + coreDataManager?.fetch(type: Holiday.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(holidays): + if let holiday = holidays.first { + holiday.updateWith(record: record, coreDataManager: coreDataManager) + } else { + coreDataManager?.createHoliday(title: "", date: Date(), image: nil) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(holiday): + holiday.updateWith(record: record, coreDataManager: coreDataManager) + } + } + } + } + } + } + + static func writeRecordChangeToHistory(record: CKRecord) { + + let predicate = NSPredicate(format: "recordName == %@", record.recordID.recordName) + + coreDataManager?.fetch(type: History.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(histories): + if let history = histories.first { + history.updateWith(record: record, coreDataManager: coreDataManager) + } else { + let tempFriend = Friend(context: (coreDataManager?.viewContext)!) + coreDataManager?.createHistory(holiday: "", item: "", isTaken: false, date: Date(), friend: tempFriend) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(history): + history.updateWith(record: record, coreDataManager: coreDataManager) + } + } + } + } + } + } + + static func writeRecordChangeToEvent(record: CKRecord) { + + let predicate = NSPredicate(format: "recordName == %@", record.recordID.recordName) + + coreDataManager?.fetch(type: Event.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(events): + if let event = events.first { + event.updateWith(record: record, coreDataManager: coreDataManager) + } else { + let tempFriend = Friend(context: (coreDataManager?.viewContext)!) + coreDataManager?.createEvent(title: "", date: Date(), friend: tempFriend) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(event): + event.updateWith(record: record, coreDataManager: coreDataManager) + } + } + } + } + } + } + + static func writeRecordChangeToNotification(record: CKRecord) { + + let predicate = NSPredicate(format: "recordName == %@", record.recordID.recordName) + + coreDataManager?.fetch(type: Notification.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(notifications): + if let notification = notifications.first { + notification.updateWith(record: record, coreDataManager: coreDataManager) + } else { + let tempEvent = Event(context: (coreDataManager?.viewContext)!) + coreDataManager?.createNotification(event: tempEvent, date: Date()) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(notification): + notification.updateWith(record: record, coreDataManager: coreDataManager) + } + } + } + } + } + } + + static func writeRecordDeletionToFriend(recordID: CKRecord.ID) { + let predicate = NSPredicate(format: "recordName == %@", recordID.recordName) + coreDataManager?.fetch(type: Friend.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(friends): + guard let objectToDelete = friends.first else { return } + coreDataManager?.delete(object: objectToDelete) { error in + if let error = error { + print(error.localizedDescription) + } + } + } + } + } + + static func writeRecordDeletionToHoliday(recordID: CKRecord.ID) { + let predicate = NSPredicate(format: "recordName == %@", recordID.recordName) + coreDataManager?.fetch(type: Holiday.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(holidays): + guard let objectToDelete = holidays.first else { return } + coreDataManager?.delete(object: objectToDelete) { error in + if let error = error { + print(error.localizedDescription) + } + } + } + } + } + + static func writeRecordDeletionToHistory(recordID: CKRecord.ID) { + let predicate = NSPredicate(format: "recordName == %@", recordID.recordName) + coreDataManager?.fetch(type: History.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(histories): + guard let objectToDelete = histories.first else { return } + coreDataManager?.delete(object: objectToDelete) { error in + if let error = error { + print(error.localizedDescription) + } + } + } + } + } + + static func writeRecordDeletionToEvent(recordID: CKRecord.ID) { + let predicate = NSPredicate(format: "recordName == %@", recordID.recordName) + coreDataManager?.fetch(type: Event.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(events): + guard let objectToDelete = events.first else { return } + coreDataManager?.delete(object: objectToDelete) { error in + if let error = error { + print(error.localizedDescription) + } + } + } + } + } + + static func writeRecordDeletionToNotification(recordID: CKRecord.ID) { + let predicate = NSPredicate(format: "recordName == %@", recordID.recordName) + coreDataManager?.fetch(type: Notification.self, predicate: predicate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(notifications): + guard let objectToDelete = notifications.first else { return } + coreDataManager?.delete(object: objectToDelete) { error in + if let error = error { + print(error.localizedDescription) + } + } + } + } + } + + static func setCoreDataManager(manager: CoreDataManager) { + coreDataManager = manager + } +} diff --git a/Bodabi/Bodabi/Services/CloudService/RecordZone.swift b/Bodabi/Bodabi/Services/CloudService/RecordZone.swift new file mode 100644 index 0000000..f0b72e7 --- /dev/null +++ b/Bodabi/Bodabi/Services/CloudService/RecordZone.swift @@ -0,0 +1,162 @@ +// +// RecordZone.swift +// Bodabi +// +// Created by jaehyeon lee on 25/02/2019. +// Copyright © 2019 LeeHyeJin. All rights reserved. +// + +import CloudKit + +protocol RecordZone: CustomStringConvertible { + + var databaseType: DatabaseType { get } + init(databaseType: DatabaseType) +} + +extension RecordZone { + + var description: String { + return Self.description + } + + static var description: String { + return "\(Self.self)" + } + + private var databaseZoneDescription: String { + return databaseType.description + description + } + + var didSetupZone: Bool { + get { + return UserDefaults.standard.bool(forKey: "DidSetup" + databaseZoneDescription) + } + set { + UserDefaults.standard.set(newValue, forKey: "DidSetup" + databaseZoneDescription) + } + } + + static var zone: CKRecordZone { + return CKRecordZone(zoneName: Self.description) + } + + static var zoneID: CKRecordZone.ID { + return zone.zoneID + } + + var database: CKDatabase? { + switch databaseType { + case .public: + return CKContainer.default().publicCloudDatabase + case .private: + return CKContainer.default().privateCloudDatabase + case .shared: + return CKContainer.default().sharedCloudDatabase + } + } + + var subscription: CKSubscription? { + var subscription: CKSubscription? = nil + switch databaseType { + case .public: + break + case .private: + subscription = CKRecordZoneSubscription(zoneID: Self.zoneID, + subscriptionID: subscriptionID) + case .shared: + subscription = CKDatabaseSubscription(subscriptionID: subscriptionID) + } + + subscription?.notificationInfo = Self.notificationInfo + return subscription + } + + var subscriptionID: String { + return databaseZoneDescription + "Subscription" + } + + static var notificationInfo: CKSubscription.NotificationInfo { + let notification = CKSubscription.NotificationInfo() + notification.shouldSendContentAvailable = true + return notification + } + + var notificationName: NSNotification.Name { + return NSNotification.Name(rawValue: databaseZoneDescription) + } + + private var previousZoneServerChangeTokenKey: String { + return description + "ChangeTokenKey" + } + + var previousZoneServerChangeToken: CKServerChangeToken? { + get { + guard let data = UserDefaults.standard.data(forKey: previousZoneServerChangeTokenKey) else { + return nil + } + guard let token = try? NSKeyedUnarchiver.unarchivedObject(ofClass: CKServerChangeToken.self, from: data) else { return nil } + return token + } + set { + guard let newValue = newValue else { + UserDefaults.standard.set(nil, forKey: previousZoneServerChangeTokenKey) + return + } + let data = try? NSKeyedArchiver.archivedData(withRootObject: newValue, requiringSecureCoding: false) + UserDefaults.standard.set(data, forKey: previousZoneServerChangeTokenKey) + } + } + + private var previousSharedDatabaseServerChangeTokenKey: String { + return databaseZoneDescription + "ChangeTokenKey" + } + + var previousSharedDatabaseServerChangeToken: CKServerChangeToken? { + get { + guard let data = UserDefaults.standard.data(forKey: previousSharedDatabaseServerChangeTokenKey) else { + return nil + } + guard let token = try? NSKeyedUnarchiver.unarchivedObject(ofClass: CKServerChangeToken.self, from: data) else { return nil } + return token + } + set { + guard let newValue = newValue else { + UserDefaults.standard.set(nil, forKey: previousSharedDatabaseServerChangeTokenKey) + return + } + let data = try? NSKeyedArchiver.archivedData(withRootObject: newValue, requiringSecureCoding: false) + UserDefaults.standard.set(data, forKey: previousSharedDatabaseServerChangeTokenKey) + } + } + + static var `public`: Self { + return Self(databaseType: .public) + } + static var `private`: Self { + return Self(databaseType: .private) + } + static var shared: Self { + return Self(databaseType: .shared) + } +} + +enum DatabaseType: Int { + case `public` = 1 + case `private` = 2 + case shared = 3 +} + +extension DatabaseType: CustomStringConvertible { + + var description: String { + switch self { + case .public: + return "public" + case .private: + return "private" + case .shared: + return "shared" + } + } +} diff --git a/Bodabi/Bodabi/Services/CoreDataService/CoreDataManager.swift b/Bodabi/Bodabi/Services/CoreDataService/CoreDataManager.swift index 66c6159..bca688e 100644 --- a/Bodabi/Bodabi/Services/CoreDataService/CoreDataManager.swift +++ b/Bodabi/Bodabi/Services/CoreDataService/CoreDataManager.swift @@ -8,11 +8,10 @@ import Foundation import CoreData - - +import CloudKit protocol CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) + func setCoreDataManager(_ manager: CoreDataManager) } final class CoreDataManager { @@ -24,6 +23,12 @@ final class CoreDataManager { return context } + var updateContext: NSManagedObjectContext { + let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) + context.parent = self.viewContext + return context + } + init(modelName: String) { container = NSPersistentContainer(name: modelName) } @@ -61,17 +66,13 @@ final class CoreDataManager { func fetch(type: CoreDataObject.Type, predicate: NSPredicate? = nil, sortDescriptor: NSSortDescriptor? = nil, completion: @escaping (Result<[CoreDataObject]>)->()) { let backgroundContext = container.newBackgroundContext() - let request: NSFetchRequest = CoreDataObject.fetchRequest() as! NSFetchRequest - if let predicate: NSPredicate = predicate { request.predicate = predicate } - if let sortDescriptor: NSSortDescriptor = sortDescriptor { request.sortDescriptors = [sortDescriptor] } - let complete: (Result) -> Void = { result in DispatchQueue.main.async { completion(result) @@ -182,8 +183,10 @@ final class CoreDataManager { do { try backgroundContext.save() + DispatchQueue.main.async { let mainQueueEvent = self.viewContext.object(with: event.objectID) as! Event complete(.success(mainQueueEvent)) + } } catch { complete(.failure(CoreDataError.creationFailed)) } @@ -207,8 +210,10 @@ final class CoreDataManager { do { try backgroundContext.save() + DispatchQueue.main.async { let mainQueueHistory = self.viewContext.object(with: history.objectID) as! History complete(.success(mainQueueHistory)) + } } catch { complete(.failure(CoreDataError.creationFailed)) } @@ -231,8 +236,10 @@ final class CoreDataManager { do { try backgroundContext.save() + DispatchQueue.main.async { let mainQueueHoliday = self.viewContext.object(with: holiday.objectID) as! Holiday complete(.success(mainQueueHoliday)) + } } catch { complete(.failure(CoreDataError.creationFailed)) } @@ -252,7 +259,9 @@ final class CoreDataManager { notification.event = event let complete: (Result) -> Void = { result in + DispatchQueue.main.async { completion(result) + } } do { @@ -268,43 +277,56 @@ final class CoreDataManager { } } - func updateFriend(object: Friend, name: String? = nil, tags: [String]? = nil, favorite: Bool? = nil, phoneNumber: String? = nil) { + func updateFriend(object: Friend, name: String? = nil, tags: [String]? = nil, favorite: Bool? = nil, phoneNumber: String? = nil, recordName: String? = nil, completion: @escaping (Result)->()) { if name == nil, tags == nil, favorite == nil, phoneNumber == nil { return } container.performBackgroundTask { backgroundContext in - let friend = backgroundContext.object(with: object.objectID) as? Friend - if let name = name { friend?.name = name } - if let tags = tags { friend?.tags = tags } - if let favorite = favorite { friend?.favorite = favorite } - if let phoneNumber = phoneNumber { friend?.phoneNumber = phoneNumber } + guard let friend = backgroundContext.object(with: object.objectID) as? Friend else { return } + if let name = name { friend.name = name } + if let tags = tags { friend.tags = tags } + if let favorite = favorite { friend.favorite = favorite } + if let phoneNumber = phoneNumber { friend.phoneNumber = phoneNumber } + if let recordName = recordName { friend.recordName = recordName } do { try backgroundContext.save() + DispatchQueue.main.async { + guard let updatedFriend = self.viewContext.object(with: friend.objectID) as? Friend else { return } + completion(.success(updatedFriend)) + } } catch { - print("Notification creation failed: \(error.localizedDescription)") - + DispatchQueue.main.async { + completion(.failure(CoreDataError.creationFailed)) + } } } } - func updateEvent(object: Event, title: String? = nil, date: Date? = nil, favorite: Bool? = nil, friend: Friend? = nil) { + func updateEvent(object: Event, title: String? = nil, date: Date? = nil, favorite: Bool? = nil, friend: Friend? = nil, recordName: String? = nil, completion: @escaping (Result)->()) { if title == nil, date == nil, favorite == nil, friend == nil { return } container.performBackgroundTask { backgroundContext in - let event = backgroundContext.object(with: object.objectID) as? Event - if let title = title { event?.title = title } - if let date = date { event?.date = date } - if let favorite = favorite { event?.favorite = favorite } + guard let event = backgroundContext.object(with: object.objectID) as? Event else { return } + if let title = title { event.title = title } + if let date = date { event.date = date } + if let favorite = favorite { event.favorite = favorite } if let friend = friend { - event?.friend = backgroundContext.object(with: friend.objectID) as? Friend + event.friend = backgroundContext.object(with: friend.objectID) as? Friend } + if let recordName = recordName { event.recordName = recordName } do { try backgroundContext.save() + DispatchQueue.main.async { + guard let updatedEvent = self.viewContext.object(with: event.objectID) as? Event else { return } + completion(.success(updatedEvent)) + } } catch { - print("Notification creation failed: \(error.localizedDescription)") + DispatchQueue.main.async { + completion(.failure(CoreDataError.creationFailed)) + } } } } @@ -405,6 +427,110 @@ final class CoreDataManager { } } +extension CoreDataManager { + func updateLocalRecords(changedRecords: [CKRecord], deletedRecordIDs: [CKRecord.ID]) { + let deletedRecordNames = deletedRecordIDs.map { $0.recordName } + self.updateObject(for: changedRecords) + self.deleteObject(for: deletedRecordNames) + do { + try viewContext.save() + } catch { + print(error.localizedDescription) + } + } + + func retrieveObject(from recordName: String) -> NSManagedObject? { + guard let dotIndex = recordName.range(of: ".") else { return nil } + let substring = recordName[..(entityName: typeString) + request.predicate = predicate + let results = try? viewContext.fetch(request) + if let castedResults = results as? [NSManagedObject] { + objects = castedResults + } + return objects.first + } + + func updateObject(for records: [CKRecord]) { + for record in records { + let recordName = record.recordID.recordName + guard let dotIndex = recordName.range(of: ".") else { return } + let substring = recordName[.. { diff --git a/Bodabi/Bodabi/Services/InputService/BaseViewController.swift b/Bodabi/Bodabi/Services/InputService/BaseViewController.swift index 89910dd..ace7b81 100644 --- a/Bodabi/Bodabi/Services/InputService/BaseViewController.swift +++ b/Bodabi/Bodabi/Services/InputService/BaseViewController.swift @@ -14,7 +14,7 @@ import UIKit protocol Inputable { var inputManager: InputManager! { get set } - var databaseManager: CoreDataManager! { get set } + var coreDataManager: CoreDataManager! { get set } } extension Inputable where Self: UIViewController { @@ -22,8 +22,8 @@ extension Inputable where Self: UIViewController { inputManager = manager } - mutating func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + mutating func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Services/InputService/InputManager.swift b/Bodabi/Bodabi/Services/InputService/InputManager.swift index cfd93b9..02b053b 100644 --- a/Bodabi/Bodabi/Services/InputService/InputManager.swift +++ b/Bodabi/Bodabi/Services/InputService/InputManager.swift @@ -56,6 +56,7 @@ struct InputManager { return } } + CloudManager.insertToCloud(object: holiday) case .addUpcomingEventAtHome: let event: Event = Event(context: context) let friend = getFriend(context: context, data: data) @@ -67,6 +68,7 @@ struct InputManager { event.title = data.holiday event.date = data.date generateNotifications(of: event, context: context) + CloudManager.insertToCloud(object: event) case .addHistoryAtHoliday, .addHistoryAtFriendHistory: let history: History = History(context: context) @@ -76,12 +78,14 @@ struct InputManager { history.holiday = data.holiday history.date = data.date history.isTaken = entryRoute == .addHistoryAtHoliday ? true : false + CloudManager.insertToCloud(object: history) case .addFriendAtFriends: if data.isNewData { let friend: Friend = Friend(context: context) friend.name = data.name friend.tags = data.tags != nil ? data.tags : friend.tags friend.favorite = false + CloudManager.insertToCloud(object: friend) } } @@ -178,6 +182,7 @@ struct InputManager { NotificationSchedular.create(notification: notification, hour: defaultHour, minute: defaultMinutes) + CloudManager.insertToCloud(object: notification) } } diff --git a/Bodabi/Bodabi/Supporting Files/Info.plist b/Bodabi/Bodabi/Supporting Files/Info.plist index 3ded4cb..8e457de 100644 --- a/Bodabi/Bodabi/Supporting Files/Info.plist +++ b/Bodabi/Bodabi/Supporting Files/Info.plist @@ -39,6 +39,10 @@ 사용자의 연락처에 접근하여 보다비에 친구를 추가할 수 있습니다. 선물을 주고 받은 친구를 손쉽게 추가해보세요. NSPhotoLibraryUsageDescription 경조사 대표 이미지를 바꿀 수 있습니다. + UIBackgroundModes + + remote-notification + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile From 7ead045aa2b25b9bbb91341b498c29734da9ba07 Mon Sep 17 00:00:00 2001 From: nowstring Date: Wed, 27 Feb 2019 10:34:30 +0900 Subject: [PATCH 2/3] modfiy controllers to fix some bugs --- .../FriendHistoryViewController.swift | 36 +++++++++---- .../Views/Friends/FriendsViewController.swift | 23 ++++---- .../Views/Holiday/HolidayViewController.swift | 22 ++++---- .../Views/Home/HomeViewController.swift | 54 ++++++++++--------- .../Input/Date/DateInputViewController.swift | 10 ++-- .../Holiday/HolidayInputViewController.swift | 18 +++---- .../Input/Item/ItemInputViewController.swift | 10 ++-- .../Input/Name/NameInputViewController.swift | 14 ++--- .../NotificationViewController.swift | 19 +++---- .../Setting/SettingAlarmViewController.swift | 35 ++++++------ .../SettingContactsViewController.swift | 12 ++--- .../Views/Setting/SettingViewController.swift | 10 ++-- 12 files changed, 143 insertions(+), 120 deletions(-) diff --git a/Bodabi/Bodabi/Views/FriendHistory/FriendHistoryViewController.swift b/Bodabi/Bodabi/Views/FriendHistory/FriendHistoryViewController.swift index 4832085..d8d5424 100644 --- a/Bodabi/Bodabi/Views/FriendHistory/FriendHistoryViewController.swift +++ b/Bodabi/Bodabi/Views/FriendHistory/FriendHistoryViewController.swift @@ -27,7 +27,7 @@ class FriendHistoryViewController: UIViewController { } private var friend: Friend? private var histories: [History]? - private var databaseManager: CoreDataManager! + private var coreDataManager: CoreDataManager! private var isSortDescending: Bool = true private var isTableViewLoaded: Bool = false private var isInputStatus: Bool = false @@ -73,7 +73,7 @@ class FriendHistoryViewController: UIViewController { private func fetchHistory() { guard let id = friendID else { return } - friend = databaseManager.viewContext.object(with: id) as? Friend + friend = coreDataManager.viewContext.object(with: id) as? Friend guard var faultedHistories = friend?.histories?.allObjects as? [History] else { return } @@ -112,8 +112,7 @@ class FriendHistoryViewController: UIViewController { var income: Int = 0 var expenditure: Int = 0 sections = [] - sections.append(.information(items: [.information(income: String(income), expenditure: String(expenditure))])) - + guard let histories = histories else { return } @@ -133,6 +132,7 @@ class FriendHistoryViewController: UIViewController { historyItems.append(.giveHistory(history: history)) } } + sections.append(.information(items: [.information(income: String(income), expenditure: String(expenditure))])) sections.append(.history(items: historyItems)) tableView.reloadData() } @@ -182,7 +182,7 @@ class FriendHistoryViewController: UIViewController { @objc func touchUpHistoryDeleteButton(_ sender: UIButton) { let selectedRow = sender.tag guard let history = histories?[selectedRow] else { return } - databaseManager.delete(object: history) { error in + coreDataManager.delete(object: history) { error in if let error = error { print(error.localizedDescription) } else { @@ -212,17 +212,31 @@ class FriendHistoryViewController: UIViewController { viewController.entryRoute = .addHistoryAtFriendHistory // viewController.isRelationInput = false viewController.cellType = .holiday - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) present(navigationController, animated: true, completion: nil) } @IBAction func touchUpFavoriteButton(_ sender: UIBarButtonItem) { guard let friend = friend else { return } if favoriteButton.image == #imageLiteral(resourceName: "ic_emptyStar") { - databaseManager.updateFriend(object: friend, favorite: false) + coreDataManager.updateFriend(object: friend, favorite: false) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case .success: + break + } + } favoriteButton.image = #imageLiteral(resourceName: "WhiteStar") } else { - databaseManager.updateFriend(object: friend, favorite: true) + coreDataManager.updateFriend(object: friend, favorite: true) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case .success: + break + } + } favoriteButton.image = #imageLiteral(resourceName: "ic_emptyStar") } @@ -354,10 +368,10 @@ extension FriendHistorySection { } } -// MARK: - DatabaseManagerClient +// MARK: - CoreDataManagerClient extension FriendHistoryViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Friends/FriendsViewController.swift b/Bodabi/Bodabi/Views/Friends/FriendsViewController.swift index fd1e1c8..f5ebc2d 100644 --- a/Bodabi/Bodabi/Views/Friends/FriendsViewController.swift +++ b/Bodabi/Bodabi/Views/Friends/FriendsViewController.swift @@ -21,7 +21,7 @@ class FriendsViewController: UIViewController { // MARK: - Property - private var databaseManager: CoreDataManager! + private var coreDataManager: CoreDataManager! private var friends: [Friend]? private var favoriteFriends: [Friend]? private let indexs: [Character] = ["★", "•", "ㄱ", "ㄴ", "ㄷ", "ㄹ", "ㅁ", "ㅂ", @@ -99,7 +99,7 @@ class FriendsViewController: UIViewController { .instantiateViewController(ofType: NameInputViewController.self) viewController.cellType = .holiday viewController.entryRoute = .addFriendAtFriends - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) viewController.inputData = InputData() let navController = UINavigationController(rootViewController: viewController) self.present(navController, animated: true, completion: nil) @@ -108,7 +108,7 @@ class FriendsViewController: UIViewController { @IBAction func touchUpGoFetchContactsButton(_ sender: UIButton) { let viewController = storyboard(.setting) .instantiateViewController(ofType: SettingContactsViewController.self) - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) navigationController?.pushViewController(viewController, animated: true) } @@ -161,7 +161,7 @@ class FriendsViewController: UIViewController { private func fetchFriend() { let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) - databaseManager.fetch ( + coreDataManager.fetch ( type: Friend.self, sortDescriptor: sortDescriptor ) { [weak self] (result) in @@ -220,7 +220,7 @@ class FriendsViewController: UIViewController { (sender.tag < favoriteFriends?.count ?? 0) : (sender.tag < friends?.count ?? 0) else { return } (sender.isSelected ? favoriteFriends?[sender.tag] : friends?[sender.tag])? .favorite = !sender.isSelected - try? databaseManager?.viewContext.save() + try? coreDataManager?.viewContext.save() guard let friends = friends, let favoriteFriends = favoriteFriends else { return } @@ -274,7 +274,7 @@ extension FriendsViewController: UITableViewDelegate { (section == .favorite || section == .friends) else { return } let viewController = storyboard(.friendHistory) .instantiateViewController(ofType: FriendHistoryViewController.self) - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) let friend = section == .favorite ? searchFavoriteFriends?[indexPath.row] : searchFriends?[indexPath.row] @@ -289,10 +289,11 @@ extension FriendsViewController: UITableViewDelegate { if editingStyle == .delete, let friend = section == .favorite ? searchFavoriteFriends?[indexPath.row] : searchFriends?[indexPath.row] { - databaseManager?.viewContext.delete(friend) + coreDataManager?.viewContext.delete(friend) + CloudManager.deleteFromCloud(object: friend) } do { - try databaseManager?.viewContext.save() + try coreDataManager?.viewContext.save() } catch { print(error.localizedDescription) } @@ -401,11 +402,11 @@ extension FriendsViewController: UICollectionViewDelegateFlowLayout { } } -// MARK: - DatabaseManagerClient +// MARK: - CoreDataManagerClient extension FriendsViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Holiday/HolidayViewController.swift b/Bodabi/Bodabi/Views/Holiday/HolidayViewController.swift index 28372f1..db9faab 100644 --- a/Bodabi/Bodabi/Views/Holiday/HolidayViewController.swift +++ b/Bodabi/Bodabi/Views/Holiday/HolidayViewController.swift @@ -40,7 +40,7 @@ class HolidayViewController: UIViewController { } } } - private var databaseManager: CoreDataManager! + private var coreDataManager: CoreDataManager! private var isFirstScroll: Bool = true private var isHolidayEmpty: Bool = true private struct Const { @@ -91,7 +91,7 @@ class HolidayViewController: UIViewController { let secondPredicate = NSPredicate(format: "isTaken = %@", NSNumber(value: true)) let andPredicate = NSCompoundPredicate(type: .and, subpredicates: [firstPredicate, secondPredicate]) - databaseManager.fetch(type: History.self, predicate: andPredicate) { [weak self] (result) in + coreDataManager.fetch(type: History.self, predicate: andPredicate) { [weak self] (result) in switch result { case let .failure(error): print(error.localizedDescription) @@ -239,7 +239,7 @@ class HolidayViewController: UIViewController { viewController.cellType = .holiday viewController.inputData = inputData viewController.entryRoute = .addHistoryAtHoliday - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) present(navController, animated: true, completion: nil) } @@ -275,7 +275,7 @@ class HolidayViewController: UIViewController { alert.addButton(title: "확인") { [weak self] in guard let holiday = self?.holiday else { return } - self?.databaseManager.delete(object: holiday) { error in + self?.coreDataManager.delete(object: holiday) { error in if let error = error { print(error.localizedDescription) } else { @@ -286,7 +286,7 @@ class HolidayViewController: UIViewController { guard let currentTitle = holiday.title else { return } let predicate = NSPredicate(format: "holiday = %@", currentTitle) - self?.databaseManager.batchDelete(typeString: "History", predicate: predicate) { error in + self?.coreDataManager.batchDelete(typeString: "History", predicate: predicate) { error in if let error = error { print(error.localizedDescription) } @@ -373,7 +373,7 @@ extension HolidayViewController: UITableViewDelegate { case .delete: guard let removedHistory = histories?[indexPath.row] else { return } - databaseManager.delete(object: removedHistory) { [weak self] (error) in + coreDataManager.delete(object: removedHistory) { [weak self] (error) in if let error = error { print(error.localizedDescription) } else { @@ -521,7 +521,7 @@ extension HolidayViewController: UIImagePickerControllerDelegate & UINavigationC let resizingImage = holidayImage.resize(scale: 0.2), let imageData = resizingImage.jpeg(1.0) { - databaseManager.updateHoliday(object: holiday, image: imageData) { [weak self] (result) in + coreDataManager.updateHoliday(object: holiday, image: imageData) { [weak self] (result) in switch result { case let .failure(error): print(error.localizedDescription) @@ -547,7 +547,7 @@ extension HolidayViewController: UITextFieldDelegate { request.predicate = predicate // 동기적으로 값을 받아와야하기 때문에 databaseManager.fetch를 이용하지 않는다. - if let fetchResult = try? databaseManager.viewContext.fetch(request) { + if let fetchResult = try? coreDataManager.viewContext.fetch(request) { if let _ = fetchResult.first { isUnique = false } @@ -569,7 +569,7 @@ extension HolidayViewController: UITextFieldDelegate { } do { - try databaseManager.viewContext.save() + try coreDataManager.viewContext.save() } catch { print(error.localizedDescription) } @@ -600,8 +600,8 @@ extension HolidayViewController: BodabiAlertControllerDelegate { } extension HolidayViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Home/HomeViewController.swift b/Bodabi/Bodabi/Views/Home/HomeViewController.swift index d165c2f..850f857 100644 --- a/Bodabi/Bodabi/Views/Home/HomeViewController.swift +++ b/Bodabi/Bodabi/Views/Home/HomeViewController.swift @@ -17,7 +17,7 @@ class HomeViewController: UIViewController { // MARK: - Property - private var databaseManager: CoreDataManager! + private var coreDataManager: CoreDataManager! private var events: [Event]? private var holidays: [Holiday]? private var isEventEmpty: Bool = true @@ -98,7 +98,7 @@ class HomeViewController: UIViewController { private func fetchEvent() { let sortDescriptor = NSSortDescriptor(key: "date", ascending: true) let predicate: NSPredicate = NSPredicate(format: "date >= %@", NSDate()) - databaseManager.fetch( + coreDataManager.fetch( type: Event.self, predicate: predicate, sortDescriptor: sortDescriptor @@ -106,10 +106,12 @@ class HomeViewController: UIViewController { switch result { case .success(let events): self?.events = events - self?.tableView.reloadSections( - IndexSet(integer: Section.friendEvents.rawValue), - with: .none - ) + UIView.performWithoutAnimation { + self?.tableView.reloadSections( + IndexSet(integer: Section.friendEvents.rawValue), + with: .none) + } + self?.tableView.reloadData() case .failure(let err): err.loadErrorAlert(title: "이벤트 불러오기 에러") } @@ -118,17 +120,18 @@ class HomeViewController: UIViewController { private func fetchHoliday() { let sortDescriptor = NSSortDescriptor(key: "createdDate", ascending: false) - databaseManager.fetch( + coreDataManager.fetch( type: Holiday.self, sortDescriptor: sortDescriptor ) { [weak self] (result) in switch result { case .success(let holidays): self?.holidays = holidays - self?.tableView.reloadSections( - IndexSet(integer: Section.holidays.rawValue), - with: .none - ) + UIView.performWithoutAnimation { + self?.tableView.reloadSections( + IndexSet(integer: Section.holidays.rawValue), + with: .none) + } case .failure(let err): err.loadErrorAlert(title: "나의 경조사 불러오기 에러") } @@ -158,9 +161,9 @@ class HomeViewController: UIViewController { private func deleteUpcomingEvent(at indexPath: IndexPath) { guard let event = events?[indexPath.row] else { return } - databaseManager?.viewContext.delete(event) + coreDataManager?.viewContext.delete(event) do { - try databaseManager?.viewContext.save() + try coreDataManager?.viewContext.save() } catch { print(error.localizedDescription) } @@ -172,6 +175,7 @@ class HomeViewController: UIViewController { } tableView.deleteRows(at: [indexPath], with: .automatic) + CloudManager.deleteFromCloud(object: event) } // MARK: - @objcs @@ -182,7 +186,7 @@ class HomeViewController: UIViewController { let navController = UINavigationController(rootViewController: viewController) viewController.entryRoute = .addHolidayAtHome - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) viewController.inputData = InputData() present(navController, animated: true, completion: nil) } @@ -191,7 +195,7 @@ class HomeViewController: UIViewController { if UserDefaults.standard.bool(forKey: DefaultsKey.askedAuthorizeNotification) == false { UserDefaults.standard.set(true, forKey: DefaultsKey.askedAuthorizeNotification) let appDelegate = UIApplication.shared.delegate as! AppDelegate - appDelegate.registerForLocalNotifications(application: UIApplication.shared) + appDelegate.registerForNotifications(application: UIApplication.shared) } let viewController = storyboard(.input) @@ -201,7 +205,7 @@ class HomeViewController: UIViewController { // viewController.isRelationInput = false viewController.cellType = .holiday viewController.entryRoute = .addUpcomingEventAtHome - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) viewController.inputData = InputData() present(navController, animated: true, completion: nil) } @@ -222,7 +226,7 @@ class HomeViewController: UIViewController { if sender.isSelected { for dDay in [favortieFirstDday, favoriteSecondDday] { - let notification = Notification(context: databaseManager.viewContext) + let notification = Notification(context: coreDataManager.viewContext) guard let interval: TimeInterval = TimeInterval(exactly: dDay * Int.day * -1) else { return } notification.id = UUID().uuidString notification.date = event.date?.addingTimeInterval(interval) @@ -233,10 +237,10 @@ class HomeViewController: UIViewController { } } else { notifications.forEach { notificaion in - self.databaseManager.viewContext.delete(notificaion) + self.coreDataManager.viewContext.delete(notificaion) NotificationSchedular.delete(notification: notificaion) } - let notification = Notification(context: databaseManager.viewContext) + let notification = Notification(context: coreDataManager.viewContext) guard let interval: TimeInterval = TimeInterval(exactly: defaultDday * Int.day * -1) else { return } notification.id = UUID().uuidString notification.date = event.date?.addingTimeInterval(interval) @@ -245,7 +249,7 @@ class HomeViewController: UIViewController { hour: defaultHour, minute: defaultMinutes) } - try? databaseManager?.viewContext.save() + try? coreDataManager?.viewContext.save() } @objc func longPressUpcomingEvent(_ gesture: UILongPressGestureRecognizer) { @@ -287,7 +291,7 @@ extension HomeViewController: UITableViewDelegate { let viewController = storyboard(.friendHistory) .instantiateViewController(ofType: FriendHistoryViewController.self) - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) viewController.friendID = friend?.objectID navigationController?.pushViewController(viewController, animated: true) @@ -383,17 +387,17 @@ extension HomeViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let viewController = storyboard(.holiday) .instantiateViewController(ofType: HolidayViewController.self) - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) viewController.holiday = holidays?[indexPath.item] viewController.entryRoute = .addHistoryAtFriendHistory navigationController?.pushViewController(viewController, animated: true) } } -// MARK: - DatabaseManagerClient +// MARK: - CoreDataManagerClient extension HomeViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Input/Date/DateInputViewController.swift b/Bodabi/Bodabi/Views/Input/Date/DateInputViewController.swift index 060ed55..04324dc 100644 --- a/Bodabi/Bodabi/Views/Input/Date/DateInputViewController.swift +++ b/Bodabi/Bodabi/Views/Input/Date/DateInputViewController.swift @@ -36,7 +36,7 @@ class DateInputViewController: UIViewController { public var inputData: InputData? public var entryRoute: EntryRoute! - private var databaseManager: CoreDataManager! + private var coreDataManager: CoreDataManager! private var date: Date? { didSet { setNextButton() @@ -136,7 +136,7 @@ class DateInputViewController: UIViewController { guard var inputData = inputData else { return } inputData.date = date - InputManager.write(context: databaseManager.viewContext, entryRoute: entryRoute, data: inputData) + InputManager.write(context: coreDataManager.viewContext, entryRoute: entryRoute, data: inputData) dismiss(animated: true, completion: nil) } @@ -190,11 +190,11 @@ extension DateInputViewController { } } -// MARK: - DatabaseManagerClient +// MARK: - CoreDataManagerClient extension DateInputViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Input/Holiday/HolidayInputViewController.swift b/Bodabi/Bodabi/Views/Input/Holiday/HolidayInputViewController.swift index 604c574..7cc354a 100644 --- a/Bodabi/Bodabi/Views/Input/Holiday/HolidayInputViewController.swift +++ b/Bodabi/Bodabi/Views/Input/Holiday/HolidayInputViewController.swift @@ -22,7 +22,7 @@ class HolidayInputViewController: UIViewController { public var entryRoute: EntryRoute! public var cellType: CellType = .relation public var cellData: [String]? - private var databaseManager: CoreDataManager! + private var coreDataManager: CoreDataManager! private var isDeleting: Bool = false private var selectedRelation: String? private var selectedHoliday: String? @@ -54,7 +54,7 @@ class HolidayInputViewController: UIViewController { // MARK: - Initialization private func fetchHoliday() { - databaseManager.fetch(type: Holiday.self) { result in + coreDataManager.fetch(type: Holiday.self) { result in switch result { case let .failure(error): print(error.localizedDescription) @@ -180,7 +180,7 @@ class HolidayInputViewController: UIViewController { inputData?.relation = selectedRelation viewController.entryRoute = entryRoute - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) viewController.selectedRelation = selectedRelation viewController.inputData = inputData viewController.cellType = .holiday @@ -191,7 +191,7 @@ class HolidayInputViewController: UIViewController { .instantiateViewController(ofType: DateInputViewController.self) viewController.entryRoute = entryRoute - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) inputData?.holiday = selectedHoliday viewController.inputData = inputData navigationController?.pushViewController(viewController, animated: true) @@ -209,7 +209,7 @@ class HolidayInputViewController: UIViewController { viewController.entryRoute = entryRoute inputData?.holiday = selectedHoliday viewController.inputData = inputData - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) navigationController?.pushViewController(viewController, animated: true) case .addHistoryAtFriendHistory: let viewController = storyboard(.input) @@ -218,7 +218,7 @@ class HolidayInputViewController: UIViewController { viewController.entryRoute = entryRoute inputData?.holiday = selectedHoliday viewController.inputData = inputData - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) navigationController?.pushViewController(viewController, animated: true) default: break @@ -288,11 +288,11 @@ extension HolidayInputViewController: UITableViewDataSource { } } -// MARK: - DatabaseManagerClient +// MARK: - CoreDataManagerClient extension HolidayInputViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Input/Item/ItemInputViewController.swift b/Bodabi/Bodabi/Views/Input/Item/ItemInputViewController.swift index 97fbd98..1795005 100644 --- a/Bodabi/Bodabi/Views/Input/Item/ItemInputViewController.swift +++ b/Bodabi/Bodabi/Views/Input/Item/ItemInputViewController.swift @@ -26,7 +26,7 @@ class ItemInputViewController: UIViewController { public var inputData: InputData? public var entryRoute: EntryRoute! - public var databaseManager: CoreDataManager! + public var coreDataManager: CoreDataManager! private var originalBottomConstraint: CGFloat = 0.0 private var originalHeightConstraint: CGFloat = 0.0 @@ -154,14 +154,14 @@ class ItemInputViewController: UIViewController { case .addHistoryAtFriendHistory: let viewController = storyboard(.input) .instantiateViewController(ofType: DateInputViewController.self) - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) viewController.entryRoute = entryRoute inputData.item = item viewController.inputData = inputData navigationController?.pushViewController(viewController, animated: true) case .addHistoryAtHoliday: inputData.item = item - InputManager.write(context: databaseManager.viewContext, entryRoute: entryRoute, data: inputData) + InputManager.write(context: coreDataManager.viewContext, entryRoute: entryRoute, data: inputData) dismiss(animated: true, completion: nil) default: break @@ -314,7 +314,7 @@ extension ItemInputViewController: UIGestureRecognizerDelegate { } extension ItemInputViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Input/Name/NameInputViewController.swift b/Bodabi/Bodabi/Views/Input/Name/NameInputViewController.swift index bba3bab..d463c62 100644 --- a/Bodabi/Bodabi/Views/Input/Name/NameInputViewController.swift +++ b/Bodabi/Bodabi/Views/Input/Name/NameInputViewController.swift @@ -45,7 +45,7 @@ class NameInputViewController: UIViewController { public var cellType: CellType! public var isRelationInput: Bool? - private var databaseManager: CoreDataManager! + private var coreDataManager: CoreDataManager! private var friends: [Friend]? private var cellData: [String]? private var searchedFriends: [Friend]? { @@ -194,7 +194,7 @@ class NameInputViewController: UIViewController { private func fetchFriend() { let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) - databaseManager.fetch(type: Friend.self, sortDescriptor: sortDescriptor) { [weak self] (result) in + coreDataManager.fetch(type: Friend.self, sortDescriptor: sortDescriptor) { [weak self] (result) in switch result { case let .failure(error): print(error.localizedDescription) @@ -355,13 +355,13 @@ class NameInputViewController: UIViewController { viewController.entryRoute = entryRoute viewController.inputData = inputData viewController.cellType = .holiday - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) navigationController?.pushViewController(viewController, animated: true) case .addHistoryAtHoliday: let viewController = storyboard(.input) .instantiateViewController(ofType: ItemInputViewController.self) - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) viewController.entryRoute = entryRoute inputData?.isNewData = isNewData @@ -374,7 +374,7 @@ class NameInputViewController: UIViewController { inputData?.isNewData = isNewData guard let inputData = inputData else { return } - InputManager.write(context: databaseManager.viewContext, entryRoute: entryRoute, data: inputData) + InputManager.write(context: coreDataManager.viewContext, entryRoute: entryRoute, data: inputData) dismiss(animated: true, completion: nil) default: break @@ -585,8 +585,8 @@ extension NameInputViewController: UIGestureRecognizerDelegate { } extension NameInputViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Notification/NotificationViewController.swift b/Bodabi/Bodabi/Views/Notification/NotificationViewController.swift index 9c010f7..765b78f 100644 --- a/Bodabi/Bodabi/Views/Notification/NotificationViewController.swift +++ b/Bodabi/Bodabi/Views/Notification/NotificationViewController.swift @@ -18,7 +18,7 @@ class NotificationViewController: UIViewController { // MARK: - Property - private var databaseManager: CoreDataManager! + private var coreDataManager: CoreDataManager! private var fetchedResultsController: NSFetchedResultsController? override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent @@ -63,7 +63,7 @@ class NotificationViewController: UIViewController { fetchResult.sortDescriptors = [sortDescriptor] fetchResult.predicate = compoundPredicate - fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchResult, managedObjectContext: (databaseManager?.viewContext)!, sectionNameKeyPath: nil, cacheName: nil) + fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchResult, managedObjectContext: (coreDataManager?.viewContext)!, sectionNameKeyPath: nil, cacheName: nil) fetchedResultsController?.delegate = self do { try fetchedResultsController?.performFetch() @@ -76,7 +76,7 @@ class NotificationViewController: UIViewController { private func updateNotificationRead(indexPath: IndexPath) { if let notification = fetchedResultsController?.object(at: indexPath) { - databaseManager.updateNotification(object: notification, isRead: true) { + coreDataManager.updateNotification(object: notification, isRead: true) { switch $0 { case let .failure(error): print(error.localizedDescription) @@ -124,7 +124,7 @@ extension NotificationViewController: UITableViewDelegate { .instantiateViewController(ofType: FriendHistoryViewController.self) if let notification = fetchedResultsController?.object(at: indexPath) { updateNotificationRead(indexPath: indexPath) - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManager) viewController.friendID = notification.event?.friend?.objectID navigationController?.pushViewController(viewController, animated: true) } @@ -133,9 +133,10 @@ extension NotificationViewController: UITableViewDelegate { func tableView (_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { if let objectToDelete = fetchedResultsController?.object(at: indexPath) { - databaseManager?.viewContext.delete(objectToDelete) + coreDataManager?.viewContext.delete(objectToDelete) + CloudManager.deleteFromCloud(object: objectToDelete) do { - try databaseManager?.viewContext.save() + try coreDataManager?.viewContext.save() } catch { print(error.localizedDescription) } @@ -145,11 +146,11 @@ extension NotificationViewController: UITableViewDelegate { } } -// MARK: - DatabaseManagerClient +// MARK: - CoreDataManagerClient extension NotificationViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Setting/SettingAlarmViewController.swift b/Bodabi/Bodabi/Views/Setting/SettingAlarmViewController.swift index 546d3dd..0a01c93 100644 --- a/Bodabi/Bodabi/Views/Setting/SettingAlarmViewController.swift +++ b/Bodabi/Bodabi/Views/Setting/SettingAlarmViewController.swift @@ -20,7 +20,7 @@ class SettingAlarmViewController: UIViewController { // MARK: - Property - private var databaseManager: CoreDataManager! + private var coreDataManager: CoreDataManager! private let datePickerView = UIDatePicker() private let dayPickerView = UIPickerView() private let dDayData: [String: Int] = ["당일": 0, "하루 전": 1, "이틀 전": 2, "3일 전": 3, "5일 전": 5, "일주일 전": 7, "10일 전": 10, "2주 전": 14, "한 달 전": 30] @@ -128,32 +128,35 @@ class SettingAlarmViewController: UIViewController { if !checkValueChanged(defaultHour, defaultMinutes, defaultDday, favoriteFirstDday, favoriteSecondDday) { return } - databaseManager.fetch(type: Event.self) { result in + coreDataManager.fetch(type: Event.self) { result in switch result { case let .failure(error): print(error.localizedDescription) case let .success(events): for event in events { guard let notifications = event.notifications?.allObjects as? [Notification] else { return } - guard let defaultNotificationDate = event.date?.addingTimeInterval(TimeInterval(exactly: -1 * Int.day * (defaultDday + 1) + Int.hour * defaultHour + Int.minute * defaultMinutes)!) else { return } + + let notificationDates = [defaultDday, favoriteFirstDday, favoriteSecondDday] + NotificationSchedular.deleteAllNotification() - notifications.forEach { notification in - if !notification.isHandled { - self.databaseManager.updateNotification(object: notification, date: defaultNotificationDate) { - switch $0 { - case let .failure(error): - print(error.localizedDescription) - case let .success(updatedNotification): - NotificationSchedular.create(notification: updatedNotification, - hour: defaultHour, - minute: defaultMinutes) + for (index, notification) in notifications.enumerated() { + if !notification.isHandled { + guard let notificationDate = event.date?.addingTimeInterval(TimeInterval(exactly: -1 * Int.day * (notificationDates[index] + 1) + Int.hour * defaultHour + Int.minute * defaultMinutes)!) else { return } + self.coreDataManager.updateNotification(object: notification, date: notificationDate) { + switch $0 { + case let .failure(error): + print(error.localizedDescription) + case let .success(updatedNotification): + NotificationSchedular.create(notification: updatedNotification, + hour: defaultHour, + minute: defaultMinutes) + } } } } } } } - } } @objc private func touchUpPickerDoneButton() { @@ -264,7 +267,7 @@ extension SettingAlarmViewController: UIGestureRecognizerDelegate { } extension SettingAlarmViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Setting/SettingContactsViewController.swift b/Bodabi/Bodabi/Views/Setting/SettingContactsViewController.swift index a138734..300f5d5 100644 --- a/Bodabi/Bodabi/Views/Setting/SettingContactsViewController.swift +++ b/Bodabi/Bodabi/Views/Setting/SettingContactsViewController.swift @@ -18,7 +18,7 @@ class SettingContactsViewController: UIViewController { // MARK: - Property - public var databaseManager: CoreDataManager! + public var coreDataManager: CoreDataManager! private var friends: [Friend]? private var contacts: [CNContact]? { didSet { @@ -84,7 +84,7 @@ class SettingContactsViewController: UIViewController { private func fetchFriendAndFetchContact() { let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) - databaseManager.fetch( + coreDataManager.fetch( type: Friend.self, sortDescriptor: sortDescriptor ) { [weak self] (result) in @@ -140,10 +140,10 @@ class SettingContactsViewController: UIViewController { private func saveContacts(contacts: [CNContact]?) { contacts?.enumerated().forEach { [weak self] (index, contact) in - guard let databaseManager = self?.databaseManager else { return } + guard let coreDataManager = self?.coreDataManager else { return } ContactManager.shared.convertAndSaveFriend( from: contact, - database: databaseManager + database: coreDataManager ) { [weak self] (result) in switch result { case .success(let friend): @@ -191,7 +191,7 @@ extension SettingContactsViewController: UITableViewDataSource { } extension SettingContactsViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManager = manager } } diff --git a/Bodabi/Bodabi/Views/Setting/SettingViewController.swift b/Bodabi/Bodabi/Views/Setting/SettingViewController.swift index 1f4c415..d551821 100644 --- a/Bodabi/Bodabi/Views/Setting/SettingViewController.swift +++ b/Bodabi/Bodabi/Views/Setting/SettingViewController.swift @@ -16,7 +16,7 @@ class SettingViewController: UIViewController { // MARK: - Property - private var databaseManager: CoreDataManager! + private var coreDataManger: CoreDataManager! private let copyrightSegueIndentifier = "Copyright" override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent @@ -98,12 +98,12 @@ extension SettingViewController: UITableViewDelegate { case .notification: let viewController = storyboard(.setting) .instantiateViewController(ofType: SettingAlarmViewController.self) - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManger) navigationController?.pushViewController(viewController, animated: true) case .contact: let viewController = storyboard(.setting) .instantiateViewController(ofType: SettingContactsViewController.self) - viewController.setDatabaseManager(databaseManager) + viewController.setCoreDataManager(coreDataManger) navigationController?.pushViewController(viewController, animated: true) case .copyright: let viewController = storyboard(.setting) @@ -114,7 +114,7 @@ extension SettingViewController: UITableViewDelegate { } extension SettingViewController: CoreDataManagerClient { - func setDatabaseManager(_ manager: CoreDataManager) { - databaseManager = manager + func setCoreDataManager(_ manager: CoreDataManager) { + coreDataManger = manager } } From e5964d2edbc8a322680f4f1eec94560adb27b80a Mon Sep 17 00:00:00 2001 From: nowstring Date: Wed, 27 Feb 2019 16:09:42 +0900 Subject: [PATCH 3/3] Add production type container setting --- Bodabi/Bodabi/Supporting Files/Bodabi.entitlements | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Bodabi/Bodabi/Supporting Files/Bodabi.entitlements b/Bodabi/Bodabi/Supporting Files/Bodabi.entitlements index c97793b..ec259f7 100644 --- a/Bodabi/Bodabi/Supporting Files/Bodabi.entitlements +++ b/Bodabi/Bodabi/Supporting Files/Bodabi.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.icloud-container-environment + Production aps-environment development com.apple.developer.icloud-container-identifiers