├── .github └── FUNDING.yml ├── .gitignore ├── README.md ├── Simple CloudKit App ├── Simple CloudKit Messenger Sample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── Source │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json │ ├── Info.plist │ ├── Michael.jpg │ ├── SPRFriendsTableViewController.h │ ├── SPRFriendsTableViewController.m │ ├── SPRInboxTableViewController.h │ ├── SPRInboxTableViewController.m │ ├── SPRMessageViewController.h │ ├── SPRMessageViewController.m │ ├── SPRTabViewController.h │ ├── SPRTabViewController.m │ ├── Simple CloudKit Messenger Sample.entitlements │ └── main.m ├── SimpleCloudKit.xcworkspace └── contents.xcworkspacedata └── SimpleCloudKitManager ├── SimpleCloudKitManager.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SimpleCloudKitManager ├── Info.plist └── SimpleCloudKitManager-Info.plist └── Source ├── CKDiscoveredUserInfo+Extras.h ├── CKDiscoveredUserInfo+Extras.m ├── SPRConstants.h ├── SPRMessage+Protected.h ├── SPRMessage.h ├── SPRMessage.m ├── SPRSimpleCloudKitManager.h └── SPRSimpleCloudKitManager.m /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: adamwulf 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | *xcshareddata 20 | 21 | # CocoaPods 22 | # 23 | # We recommend against adding the Pods directory to your .gitignore. However 24 | # you should judge for yourself, the pros and cons are mentioned at: 25 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 26 | # 27 | # Pods/ 28 | 29 | .DS_Store 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloud Kit Messenger 2 | 3 | CloudKitMessenger is a part of [Loose Leaf](https://getlooseleaf.com), and allows for sending text and binary messages between users through CloudKit on iOS. 4 | 5 | A simple, functioning example is included showing how to build messaging for your app on top of CloudKit. It allows a user to send and receive messages to and from anyone in their address book who is also using your app (and has allowed themselves to be discoverable.) 6 | 7 | This messenger allows your users to send text and an image to their friends who are also using your app. It only has a few methods, and drastically shrinks the amount of error cases you will have to deal with. 8 | 9 | ## Example App 10 | 11 | Check out [BarkLoud](https://github.com/FergalMohan/BarkLoud), a more fully featured chat application based on cloudkit-manager. 12 | 13 | ## Building 14 | 15 | This project builds a static iOS framework bundle. 16 | 17 | ## Installation 18 | 19 | Download or clone the repository, and copy these files to your project: 20 | 21 | ``` 22 | SPRMessage.h 23 | SPRMessage.m 24 | SPRSimpleCloudKitMessenger.h 25 | SPRSimpleCloudKitMessenger.m 26 | ``` 27 | 28 | ## Setup 29 | 30 | You'll need to configure your iCloud/CloudKit capabilities. If you are running the sample app, you'll need to use a custom container name. 31 | 32 | ![iCloud Configuration](http://content.screencast.com/users/sprynmr/folders/Snagit/media/d2ad40e9-0ad4-4fd3-9cd1-931064a2d17a/cloudkit.png) 33 | 34 | ## Basic Usage 35 | 36 | ### Login 37 | 38 | CloudKit doesn't offer an exact equivalent to logging in. If the user is logged into iCloud on their device, they are logged into iCloud as far as your app is concerned. 39 | 40 | `verifyAndFetchActiveiCloudUserWithCompletionHandler` is your main entry point. Although the user is already technically logged in, this would be a good method to call from a "Login with iCloud" button. You could store the success of this method in a user default, to know whether you should be able to successfully call other methods. 41 | 42 | ```Objective-C 43 | - (void) loginAction { 44 | [[SPRSimpleCloudKitMessenger sharedMessenger] verifyAndFetchActiveiCloudUserWithCompletionHandler:^(CKDiscoveredUserInfo *userInfo, NSError *error) { 45 | if (error) { 46 | [[[UIAlertView alloc] initWithTitle:@"Error" message:error.localizedDescription delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; 47 | } else { 48 | [[NSUserDefaults standardUserDefaults] setBool: YES forKey:@"loggedIn"]; 49 | } 50 | }]; 51 | } 52 | ``` 53 | 54 | This method does the majority of the heavy lifting for setting up for the active iCloud user. It checks if they have a valid iCloud account and prompts for them to be discoverable. It will return an error if they don't have a valid iCloud account, or if their discovery permissions are disabled. 55 | 56 | Once "logged in", you should call this method every time your app becomes active so it can perform it's checks. 57 | 58 | ```Objective-C 59 | - (void)applicationDidBecomeActive:(UIApplication *)application { 60 | BOOL loggedIn = [[NSUserDefaults standardUserDefaults] boolForKey:@"loggedIn"]; 61 | if (loggedIn) { 62 | [[SPRSimpleCloudKitMessenger sharedMessenger] verifyAndFetchActiveiCloudUserWithCompletionHandler:^(CKDiscoveredUserInfo *userInfo, NSError *error) { 63 | if (error) { 64 | if(error.code == RSimpleCloudMessengerErroriCloudAcountChanged) { 65 | // user has changed from previous user 66 | // do logout logic and let the new user "login" 67 | } else { 68 | // some other error, decide whether to show error 69 | } 70 | } 71 | }]; 72 | } 73 | } 74 | ``` 75 | 76 | This method will also return an error if the user changed iCloud accounts since the last time they used your app. You should check for error code == `RSimpleCloudMessengerErroriCloudAcountChanged` and clean up any private user data. Once you have cleaned up old user data, call this method again to prepare for the new iCloud user (or when they tap a "login" button). 77 | 78 | Any errors returned from this method, or any other method on this class, will have a friendly error message in NSLocalizedDescription. 79 | 80 | All serious errors will carry the code `SPRSimpleCloudMessengerErrorUnexpected`. 81 | 82 | ### Friends 83 | 84 | To grab all the available friends from the user's address book that are using the app and discoverable, you'll use `discoverAllFriendsWithCompletionHandler`. It provides an array of `CKDiscoveredUserInfo` objects. 85 | 86 | ### Sending message 87 | 88 | To send a message, you'll use the `sendMessage:withImageURL:toUserRecordID:withCompletionHandler`. The `CKRecordID` can be pulled off of a `CKDiscoveredUserInfo` object from the previous method. CloudKit makes it very easy to upload blob objects like images. You just need to provide the location to the image on disk. 89 | 90 | ```Objective-C 91 | - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 92 | CKDiscoveredUserInfo *userInfo = self.friends[indexPath.row]; 93 | NSURL *imageURL = [self getImageURL]; 94 | [[SPRSimpleCloudKitMessenger sharedMessenger] sendMessage:textfield.text withImageURL:imageURL toUserRecordID:userInfo.userRecordID withCompletionHandler:^(NSError *error) { 95 | if (error) { 96 | [[[UIAlertView alloc] initWithTitle:@"Error" message:error.localizedDescription delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; 97 | } else { 98 | [[[UIAlertView alloc] initWithTitle:@"Success!" message:@"Message sent" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; 99 | } 100 | }]; 101 | [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES]; 102 | } 103 | ``` 104 | 105 | ### Fetching messages 106 | 107 | Fetching messages is quite easy. Calling `fetchNewMessagesWithCompletionHandler` will give you an array of `SPRMessage` objects. Just be sure to store messages somewhere if you need to maintain them across app launches, as there is no way currently to retrieve old messages. 108 | 109 | The `SPRMessage` object is just a very simple data object, but it adheres to the NSCoding protocol, so it can be stored/cached easily. 110 | 111 | ```Objective-C 112 | - (void)viewDidLoad 113 | { 114 | [super viewDidLoad]; 115 | [[SPRSimpleCloudKitMessenger sharedMessenger] fetchNewMessagesWithCompletionHandler:^(NSArray *messages, NSError *error) { 116 | self.messages = [self.messages arrayByAddingObjectsFromArray:messages]; 117 | [self.tableView reloadData]; 118 | }]; 119 | } 120 | 121 | - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 122 | UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"SPRMessageCell"]; 123 | SPRMessage *message = self.messages[indexPath.row]; 124 | cell.textLabel.text = message.messageText; 125 | return cell; 126 | } 127 | ``` 128 | 129 | The method above for fetching messages doesn't automatically pull down the image data for a message. When displaying the message detail, you can call `fetchDetailsForMessage:withCompletionHandler:` to get the image data. You pass a `SPRMessage` object in, and that same object is updated. You can check if the image exists before requesting the details. 130 | 131 | ```Objective-C 132 | - (void)viewDidLoad 133 | { 134 | [super viewDidLoad]; 135 | self.messageLabel.text = self.message.messageText; 136 | if (self.message.messageImage) { 137 | self.imageView.image = self.message.messageImage; 138 | } else { 139 | [[SPRSimpleCloudKitMessenger sharedMessenger] fetchDetailsForMessage:self.message withCompletionHandler:^(SPRMessage *message, NSError *error) { 140 | self.messageLabel.text = message.messageText; 141 | self.imageView.image = message.messageImage; 142 | }]; 143 | } 144 | } 145 | ``` 146 | 147 | The only other method to be aware of is `messageForQueryNotification:withCompletionHandler:` which lets you turn a new message notification the user may have swiped/tapped into a message object that you can display. 148 | 149 | ```Objective-C 150 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)info { 151 | 152 | // Do something if the app was in background. Could handle foreground notifications differently 153 | if (application.applicationState != UIApplicationStateActive) { 154 | [self checkForNotificationToHandleWithUserInfo:info]; 155 | } 156 | } 157 | 158 | - (void) checkForNotificationToHandleWithUserInfo:(NSDictionary *)userInfo { 159 | NSString *notificationKey = [userInfo valueForKeyPath:@"ck.qry.sid"]; 160 | if ([notificationKey isEqualToString:SPRSubscriptionIDIncomingMessages]) { 161 | CKQueryNotification *notification = [CKQueryNotification notificationFromRemoteNotificationDictionary:userInfo]; 162 | [[SPRSimpleCloudKitMessenger sharedMessenger] messageForQueryNotification:notification withCompletionHandler:^(SPRMessage *message, NSError *error) { 163 | // Do something with the message, like pushing it onto the stack 164 | NSLog(@"%@", message); 165 | }]; 166 | } 167 | } 168 | ``` 169 | -------------------------------------------------------------------------------- /Simple CloudKit App/Simple CloudKit Messenger Sample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 660549E619A29F8200F5B17F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 660549E519A29F8200F5B17F /* Foundation.framework */; }; 11 | 6623F6AB19A2AFB500E21399 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6623F6AA19A2AFB500E21399 /* CloudKit.framework */; }; 12 | 6682FEF91A8DB50200187325 /* SimpleCloudKitManager.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6682FEF81A8DB50200187325 /* SimpleCloudKitManager.framework */; }; 13 | 66A7AF1519A5252C00E0AA29 /* SPRTabViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A7AF1419A5252C00E0AA29 /* SPRTabViewController.m */; }; 14 | 711DE13A1952CD4000A8839D /* Michael.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 711DE1391952CD4000A8839D /* Michael.jpg */; }; 15 | 716E523B194ACC8E00E1477E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 716E5232194ACC8E00E1477E /* AppDelegate.m */; }; 16 | 716E523D194ACC8E00E1477E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 716E5234194ACC8E00E1477E /* Images.xcassets */; }; 17 | 716E523F194ACC8E00E1477E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 716E5236194ACC8E00E1477E /* main.m */; }; 18 | 716E5249194ACE8200E1477E /* SPRFriendsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 716E5248194ACE8200E1477E /* SPRFriendsTableViewController.m */; }; 19 | 71F9209A1954343500FC7FA5 /* SPRInboxTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 71F920991954343500FC7FA5 /* SPRInboxTableViewController.m */; }; 20 | FA36F238195D292800E22F2D /* SPRMessageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FA36F237195D292800E22F2D /* SPRMessageViewController.m */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 660549E519A29F8200F5B17F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 25 | 6623F6AA19A2AFB500E21399 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 26 | 6682FEF81A8DB50200187325 /* SimpleCloudKitManager.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SimpleCloudKitManager.framework; path = "../../../../../../../Library/Developer/Xcode/DerivedData/SimpleCloudKit-deowvdkbqsbkvkenrwkpmzgumemk/Build/Products/Debug-iphoneos/SimpleCloudKitManager.framework"; sourceTree = ""; }; 27 | 66A02F9D19A290790013EFAE /* Simple CloudKit Messenger Sample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Simple CloudKit Messenger Sample.entitlements"; sourceTree = ""; }; 28 | 66A7AF1319A5252C00E0AA29 /* SPRTabViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRTabViewController.h; sourceTree = ""; }; 29 | 66A7AF1419A5252C00E0AA29 /* SPRTabViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRTabViewController.m; sourceTree = ""; }; 30 | 711DE1391952CD4000A8839D /* Michael.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = Michael.jpg; sourceTree = ""; }; 31 | 71430781194951F400375FFD /* Simple CloudKit Messenger Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Simple CloudKit Messenger Sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 716E5231194ACC8E00E1477E /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 33 | 716E5232194ACC8E00E1477E /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 34 | 716E5234194ACC8E00E1477E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 35 | 716E5235194ACC8E00E1477E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 716E5236194ACC8E00E1477E /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 37 | 716E5247194ACE8200E1477E /* SPRFriendsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRFriendsTableViewController.h; sourceTree = ""; }; 38 | 716E5248194ACE8200E1477E /* SPRFriendsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRFriendsTableViewController.m; sourceTree = ""; }; 39 | 71F920981954343500FC7FA5 /* SPRInboxTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRInboxTableViewController.h; sourceTree = ""; }; 40 | 71F920991954343500FC7FA5 /* SPRInboxTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRInboxTableViewController.m; sourceTree = ""; }; 41 | FA36F236195D292800E22F2D /* SPRMessageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRMessageViewController.h; sourceTree = ""; }; 42 | FA36F237195D292800E22F2D /* SPRMessageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRMessageViewController.m; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | 7143077E194951F400375FFD /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | 6623F6AB19A2AFB500E21399 /* CloudKit.framework in Frameworks */, 51 | 6682FEF91A8DB50200187325 /* SimpleCloudKitManager.framework in Frameworks */, 52 | 660549E619A29F8200F5B17F /* Foundation.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | 6605490219A2976D00F5B17F /* Classes */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 716E5247194ACE8200E1477E /* SPRFriendsTableViewController.h */, 63 | 716E5248194ACE8200E1477E /* SPRFriendsTableViewController.m */, 64 | 71F920981954343500FC7FA5 /* SPRInboxTableViewController.h */, 65 | 71F920991954343500FC7FA5 /* SPRInboxTableViewController.m */, 66 | FA36F236195D292800E22F2D /* SPRMessageViewController.h */, 67 | FA36F237195D292800E22F2D /* SPRMessageViewController.m */, 68 | ); 69 | name = Classes; 70 | sourceTree = ""; 71 | }; 72 | 6605490319A2977D00F5B17F /* App Delegate */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 716E5231194ACC8E00E1477E /* AppDelegate.h */, 76 | 716E5232194ACC8E00E1477E /* AppDelegate.m */, 77 | 66A7AF1319A5252C00E0AA29 /* SPRTabViewController.h */, 78 | 66A7AF1419A5252C00E0AA29 /* SPRTabViewController.m */, 79 | ); 80 | name = "App Delegate"; 81 | sourceTree = ""; 82 | }; 83 | 6605490419A2978600F5B17F /* Supporting Files */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 66A02F9D19A290790013EFAE /* Simple CloudKit Messenger Sample.entitlements */, 87 | 711DE1391952CD4000A8839D /* Michael.jpg */, 88 | 716E5234194ACC8E00E1477E /* Images.xcassets */, 89 | 716E5235194ACC8E00E1477E /* Info.plist */, 90 | 716E5236194ACC8E00E1477E /* main.m */, 91 | ); 92 | name = "Supporting Files"; 93 | sourceTree = ""; 94 | }; 95 | 6605490519A297B600F5B17F /* Frameworks */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 6682FEF81A8DB50200187325 /* SimpleCloudKitManager.framework */, 99 | 6623F6AA19A2AFB500E21399 /* CloudKit.framework */, 100 | 660549E519A29F8200F5B17F /* Foundation.framework */, 101 | ); 102 | name = Frameworks; 103 | sourceTree = ""; 104 | }; 105 | 71430778194951F400375FFD = { 106 | isa = PBXGroup; 107 | children = ( 108 | 716E5230194ACC8E00E1477E /* Source */, 109 | 6605490519A297B600F5B17F /* Frameworks */, 110 | 71430782194951F400375FFD /* Products */, 111 | ); 112 | sourceTree = ""; 113 | }; 114 | 71430782194951F400375FFD /* Products */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 71430781194951F400375FFD /* Simple CloudKit Messenger Sample.app */, 118 | ); 119 | name = Products; 120 | sourceTree = ""; 121 | }; 122 | 716E5230194ACC8E00E1477E /* Source */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 6605490319A2977D00F5B17F /* App Delegate */, 126 | 6605490219A2976D00F5B17F /* Classes */, 127 | 6605490419A2978600F5B17F /* Supporting Files */, 128 | ); 129 | path = Source; 130 | sourceTree = ""; 131 | }; 132 | /* End PBXGroup section */ 133 | 134 | /* Begin PBXNativeTarget section */ 135 | 71430780194951F400375FFD /* Simple CloudKit Messenger Sample */ = { 136 | isa = PBXNativeTarget; 137 | buildConfigurationList = 7143079B194951F400375FFD /* Build configuration list for PBXNativeTarget "Simple CloudKit Messenger Sample" */; 138 | buildPhases = ( 139 | 7143077D194951F400375FFD /* Sources */, 140 | 7143077E194951F400375FFD /* Frameworks */, 141 | 7143077F194951F400375FFD /* Resources */, 142 | ); 143 | buildRules = ( 144 | ); 145 | dependencies = ( 146 | ); 147 | name = "Simple CloudKit Messenger Sample"; 148 | productName = "CloudKit Manager"; 149 | productReference = 71430781194951F400375FFD /* Simple CloudKit Messenger Sample.app */; 150 | productType = "com.apple.product-type.application"; 151 | }; 152 | /* End PBXNativeTarget section */ 153 | 154 | /* Begin PBXProject section */ 155 | 71430779194951F400375FFD /* Project object */ = { 156 | isa = PBXProject; 157 | attributes = { 158 | CLASSPREFIX = SPR; 159 | LastUpgradeCheck = 0600; 160 | ORGANIZATIONNAME = Sprynthesis; 161 | TargetAttributes = { 162 | 71430780194951F400375FFD = { 163 | CreatedOnToolsVersion = 6.0; 164 | DevelopmentTeam = MU27A4SCKC; 165 | SystemCapabilities = { 166 | com.apple.BackgroundModes = { 167 | enabled = 1; 168 | }; 169 | com.apple.iCloud = { 170 | enabled = 1; 171 | }; 172 | }; 173 | }; 174 | }; 175 | }; 176 | buildConfigurationList = 7143077C194951F400375FFD /* Build configuration list for PBXProject "Simple CloudKit Messenger Sample" */; 177 | compatibilityVersion = "Xcode 3.2"; 178 | developmentRegion = English; 179 | hasScannedForEncodings = 0; 180 | knownRegions = ( 181 | en, 182 | ); 183 | mainGroup = 71430778194951F400375FFD; 184 | productRefGroup = 71430782194951F400375FFD /* Products */; 185 | projectDirPath = ""; 186 | projectRoot = ""; 187 | targets = ( 188 | 71430780194951F400375FFD /* Simple CloudKit Messenger Sample */, 189 | ); 190 | }; 191 | /* End PBXProject section */ 192 | 193 | /* Begin PBXResourcesBuildPhase section */ 194 | 7143077F194951F400375FFD /* Resources */ = { 195 | isa = PBXResourcesBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | 711DE13A1952CD4000A8839D /* Michael.jpg in Resources */, 199 | 716E523D194ACC8E00E1477E /* Images.xcassets in Resources */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXResourcesBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | 7143077D194951F400375FFD /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | FA36F238195D292800E22F2D /* SPRMessageViewController.m in Sources */, 211 | 716E5249194ACE8200E1477E /* SPRFriendsTableViewController.m in Sources */, 212 | 66A7AF1519A5252C00E0AA29 /* SPRTabViewController.m in Sources */, 213 | 716E523F194ACC8E00E1477E /* main.m in Sources */, 214 | 71F9209A1954343500FC7FA5 /* SPRInboxTableViewController.m in Sources */, 215 | 716E523B194ACC8E00E1477E /* AppDelegate.m in Sources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXSourcesBuildPhase section */ 220 | 221 | /* Begin XCBuildConfiguration section */ 222 | 71430799194951F400375FFD /* Debug */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | ALWAYS_SEARCH_USER_PATHS = NO; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_WARN_BOOL_CONVERSION = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INT_CONVERSION = YES; 236 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 237 | CLANG_WARN_UNREACHABLE_CODE = YES; 238 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 239 | COPY_PHASE_STRIP = NO; 240 | ENABLE_STRICT_OBJC_MSGSEND = YES; 241 | GCC_C_LANGUAGE_STANDARD = gnu99; 242 | GCC_DYNAMIC_NO_PIC = NO; 243 | GCC_OPTIMIZATION_LEVEL = 0; 244 | GCC_PREPROCESSOR_DEFINITIONS = ( 245 | "DEBUG=1", 246 | "$(inherited)", 247 | ); 248 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 249 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 250 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 251 | GCC_WARN_UNDECLARED_SELECTOR = YES; 252 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 253 | GCC_WARN_UNUSED_FUNCTION = YES; 254 | GCC_WARN_UNUSED_VARIABLE = YES; 255 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 256 | METAL_ENABLE_DEBUG_INFO = YES; 257 | ONLY_ACTIVE_ARCH = YES; 258 | SDKROOT = iphoneos; 259 | }; 260 | name = Debug; 261 | }; 262 | 7143079A194951F400375FFD /* Release */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 267 | CLANG_CXX_LIBRARY = "libc++"; 268 | CLANG_ENABLE_MODULES = YES; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_WARN_BOOL_CONVERSION = YES; 271 | CLANG_WARN_CONSTANT_CONVERSION = YES; 272 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 277 | CLANG_WARN_UNREACHABLE_CODE = YES; 278 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 279 | COPY_PHASE_STRIP = YES; 280 | ENABLE_NS_ASSERTIONS = NO; 281 | ENABLE_STRICT_OBJC_MSGSEND = YES; 282 | GCC_C_LANGUAGE_STANDARD = gnu99; 283 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 284 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 285 | GCC_WARN_UNDECLARED_SELECTOR = YES; 286 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 287 | GCC_WARN_UNUSED_FUNCTION = YES; 288 | GCC_WARN_UNUSED_VARIABLE = YES; 289 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 290 | METAL_ENABLE_DEBUG_INFO = NO; 291 | SDKROOT = iphoneos; 292 | VALIDATE_PRODUCT = YES; 293 | }; 294 | name = Release; 295 | }; 296 | 7143079C194951F400375FFD /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 300 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 301 | CODE_SIGN_ENTITLEMENTS = "Source/Simple CloudKit Messenger Sample.entitlements"; 302 | CODE_SIGN_IDENTITY = "iPhone Developer"; 303 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 304 | INFOPLIST_FILE = Source/Info.plist; 305 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 306 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 307 | PRODUCT_NAME = "Simple CloudKit Messenger Sample"; 308 | PROVISIONING_PROFILE = ""; 309 | TARGETED_DEVICE_FAMILY = "1,2"; 310 | }; 311 | name = Debug; 312 | }; 313 | 7143079D194951F400375FFD /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 317 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 318 | CODE_SIGN_ENTITLEMENTS = "Source/Simple CloudKit Messenger Sample.entitlements"; 319 | CODE_SIGN_IDENTITY = "iPhone Developer"; 320 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 321 | INFOPLIST_FILE = Source/Info.plist; 322 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 323 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 324 | PRODUCT_NAME = "Simple CloudKit Messenger Sample"; 325 | PROVISIONING_PROFILE = ""; 326 | TARGETED_DEVICE_FAMILY = "1,2"; 327 | }; 328 | name = Release; 329 | }; 330 | /* End XCBuildConfiguration section */ 331 | 332 | /* Begin XCConfigurationList section */ 333 | 7143077C194951F400375FFD /* Build configuration list for PBXProject "Simple CloudKit Messenger Sample" */ = { 334 | isa = XCConfigurationList; 335 | buildConfigurations = ( 336 | 71430799194951F400375FFD /* Debug */, 337 | 7143079A194951F400375FFD /* Release */, 338 | ); 339 | defaultConfigurationIsVisible = 0; 340 | defaultConfigurationName = Release; 341 | }; 342 | 7143079B194951F400375FFD /* Build configuration list for PBXNativeTarget "Simple CloudKit Messenger Sample" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | 7143079C194951F400375FFD /* Debug */, 346 | 7143079D194951F400375FFD /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | /* End XCConfigurationList section */ 352 | }; 353 | rootObject = 71430779194951F400375FFD /* Project object */; 354 | } 355 | -------------------------------------------------------------------------------- /Simple CloudKit App/Simple CloudKit Messenger Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CloudKit Manager 4 | // 5 | // Created by Bob Spryn on 6/11/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CloudKit Manager 4 | // 5 | // Created by Bob Spryn on 6/11/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import 11 | #import "SPRTabViewController.h" 12 | 13 | @implementation AppDelegate 14 | 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 17 | NSLog(@"didFinishLaunchingWithOptions"); 18 | 19 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 20 | // Override point for customization after application launch. 21 | SPRTabViewController* viewController = [[SPRTabViewController alloc] init]; 22 | self.window.rootViewController = viewController; 23 | [self.window makeKeyAndVisible]; 24 | 25 | if (launchOptions != nil) 26 | { 27 | NSDictionary *dictionary = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; 28 | if (dictionary != nil) 29 | { 30 | [self checkForNotificationToHandleWithUserInfo:dictionary]; 31 | } 32 | } 33 | 34 | [self verifyAndLogIntoCloudKit]; 35 | 36 | return YES; 37 | } 38 | 39 | - (void)applicationDidBecomeActive:(UIApplication *)application { 40 | NSLog(@"applicationDidBecomeActive"); 41 | } 42 | 43 | -(void) applicationWillEnterForeground:(UIApplication *)application{ 44 | NSLog(@"applicationWillEnterForeground"); 45 | [self verifyAndLogIntoCloudKit]; 46 | } 47 | 48 | -(void) applicationDidEnterBackground:(UIApplication *)application{ 49 | NSLog(@"applicationDidEnterBackground"); 50 | } 51 | 52 | -(void) applicationWillResignActive:(UIApplication *)application{ 53 | NSLog(@"applicationWillResignActive"); 54 | } 55 | 56 | - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { 57 | NSLog(@"%@", deviceToken); 58 | } 59 | 60 | - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { 61 | NSLog(@"%@", error); 62 | } 63 | 64 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)info { 65 | 66 | // Do something if the app was in background. Could handle foreground notifications differently 67 | if (application.applicationState != UIApplicationStateActive) { 68 | [self checkForNotificationToHandleWithUserInfo:info]; 69 | }else{ 70 | [self checkForNotificationToHandleWithUserInfo:info]; 71 | } 72 | } 73 | 74 | - (void) checkForNotificationToHandleWithUserInfo:(NSDictionary *)userInfo { 75 | NSString *notificationKey = [userInfo valueForKeyPath:@"ck.qry.sid"]; 76 | if ([notificationKey isEqualToString:SPRSubscriptionIDIncomingMessages]) { 77 | CKQueryNotification *notification = [CKQueryNotification notificationFromRemoteNotificationDictionary:userInfo]; 78 | [[SPRSimpleCloudKitManager sharedManager] messageForQueryNotification:notification withCompletionHandler:^(SPRMessage *message, NSError *error) { 79 | // Do something with the message, like pushing it onto the stack 80 | NSLog(@"%@", message); 81 | if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { 82 | UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"Message!" message:[message.messageData path] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 83 | [alertView show]; 84 | } 85 | 86 | }]; 87 | } 88 | 89 | } 90 | 91 | 92 | -(void) verifyAndLogIntoCloudKit{ 93 | [[SPRSimpleCloudKitManager sharedManager] promptAndFetchUserInfoOnComplete:^(SCKMApplicationPermissionStatus permissionStatus, CKRecordID *recordID, CKDiscoveredUserInfo * userInfo, NSError *error) { 94 | if (error) { 95 | [[[UIAlertView alloc] initWithTitle:@"Error" message:error.localizedDescription delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; 96 | }else{ 97 | [[SPRSimpleCloudKitManager sharedManager] promptForRemoteNotificationsIfNecessary]; 98 | } 99 | }]; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Simple CloudKit App/Source/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Simple CloudKit App/Source/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.milestonemade.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIBackgroundModes 26 | 27 | remote-notification 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/Michael.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamwulf/cloudkit-manager/e31ab173875716a2b6a5466636d02550cf07bcc9/Simple CloudKit App/Source/Michael.jpg -------------------------------------------------------------------------------- /Simple CloudKit App/Source/SPRFriendsTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPRFriendsTableViewController.h 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Bob Spryn on 6/13/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SPRFriendsTableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/SPRFriendsTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPRFriendsTableViewController.m 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Bob Spryn on 6/13/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | @import CloudKit; 10 | 11 | #import "SPRFriendsTableViewController.h" 12 | #import 13 | 14 | @interface SPRFriendsTableViewController () 15 | @property (nonatomic, strong) NSArray *friends; 16 | @end 17 | 18 | @implementation SPRFriendsTableViewController 19 | 20 | - (instancetype)initWithStyle:(UITableViewStyle)style 21 | { 22 | self = [super initWithStyle:style]; 23 | if (self) { 24 | self.friends = @[]; 25 | } 26 | return self; 27 | } 28 | 29 | -(NSArray*) filteredArrayOfFriendRecords:(NSArray*)friendRecords{ 30 | return [friendRecords filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { 31 | return [evaluatedObject firstName] != nil; 32 | }]]; 33 | } 34 | 35 | - (void)viewDidLoad 36 | { 37 | [super viewDidLoad]; 38 | 39 | UITableView* tv = (UITableView*) self.view; 40 | tv.dataSource = self; 41 | tv.delegate = self; 42 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"]; 43 | 44 | 45 | [[SPRSimpleCloudKitManager sharedManager] silentlyVerifyiCloudAccountStatusOnComplete:^(SCKMAccountStatus accountStatus, SCKMApplicationPermissionStatus permissionStatus, NSError *error) { 46 | if(accountStatus == SCKMAccountStatusAvailable){ 47 | NSLog(@"logged in as a real user"); 48 | [[SPRSimpleCloudKitManager sharedManager] discoverAllFriendsWithCompletionHandler:^(NSArray *friendRecords, NSError *error) { 49 | if (error) { 50 | [[[UIAlertView alloc] initWithTitle:@"Error" message:error.localizedDescription delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; 51 | } else { 52 | self.friends = [self filteredArrayOfFriendRecords:friendRecords]; 53 | [self.tableView reloadData]; 54 | } 55 | }]; 56 | }else{ 57 | NSLog(@"not logged in at all"); 58 | } 59 | }]; 60 | } 61 | 62 | - (void)didReceiveMemoryWarning 63 | { 64 | [super didReceiveMemoryWarning]; 65 | // Dispose of any resources that can be recreated. 66 | } 67 | 68 | #pragma mark - Table view data source 69 | 70 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 71 | { 72 | return self.friends.count; 73 | } 74 | 75 | - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 76 | UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"]; 77 | CKDiscoveredUserInfo *userInfo = self.friends[indexPath.row]; 78 | cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", userInfo.firstName, userInfo.lastName]; 79 | return cell; 80 | } 81 | 82 | - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 83 | CKDiscoveredUserInfo *userInfo = self.friends[indexPath.row]; 84 | NSString * bundleImagePath = [[NSBundle mainBundle] pathForResource:@"Michael" ofType:@"jpg"]; 85 | NSURL *imageURL = [NSURL fileURLWithPath:bundleImagePath]; 86 | [[SPRSimpleCloudKitManager sharedManager] sendFile:imageURL withAttributes:nil toUserRecordID:userInfo.userRecordID withProgressHandler:nil withCompletionHandler:^(NSError *error) { 87 | if (error) { 88 | [[[UIAlertView alloc] initWithTitle:@"Error" message:error.localizedDescription delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; 89 | } else { 90 | [[[UIAlertView alloc] initWithTitle:@"Success!" message:@"Message sent" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; 91 | } 92 | }]; 93 | [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES]; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/SPRInboxTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPRInboxTableViewController.h 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Bob Spryn on 6/20/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SPRInboxTableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/SPRInboxTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPRInboxTableViewController.m 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Bob Spryn on 6/20/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import "SPRInboxTableViewController.h" 10 | #import 11 | #import "SPRMessageViewController.h" 12 | 13 | @interface SPRInboxTableViewController () 14 | @property (nonatomic, strong) NSArray *messages; 15 | @end 16 | 17 | @implementation SPRInboxTableViewController 18 | 19 | -(instancetype) initWithStyle:(UITableViewStyle)style{ 20 | self = [super initWithStyle:style]; 21 | if (!self) return nil; 22 | self.messages = @[]; 23 | return self; 24 | } 25 | 26 | - (void)viewDidLoad 27 | { 28 | [super viewDidLoad]; 29 | 30 | UITableView* tv = (UITableView*) self.view; 31 | tv.dataSource = self; 32 | tv.delegate = self; 33 | 34 | [tv registerClass:[UITableViewCell class] forCellReuseIdentifier:@"SPRMessageCell"]; 35 | 36 | 37 | [[SPRSimpleCloudKitManager sharedManager] fetchNewMessagesAndMarkAsReadWithCompletionHandler:^(NSArray *messages, NSError *error) { 38 | self.messages = [self.messages arrayByAddingObjectsFromArray:messages]; 39 | [self.tableView reloadData]; 40 | }]; 41 | 42 | self.refreshControl = [[UIRefreshControl alloc]init]; 43 | [tv addSubview:self.refreshControl]; 44 | [self.refreshControl addTarget:self action:@selector(refreshView:) forControlEvents:UIControlEventValueChanged]; 45 | } 46 | 47 | - (void)refreshView:(UIRefreshControl *)sender { 48 | // Do something... 49 | [[SPRSimpleCloudKitManager sharedManager] fetchNewMessagesAndMarkAsReadWithCompletionHandler:^(NSArray *messages, NSError *error) { 50 | NSLog(@"finished fetching messages, in inbox controller"); 51 | self.messages = [self.messages arrayByAddingObjectsFromArray:messages]; 52 | [self.tableView reloadData]; 53 | [self.refreshControl endRefreshing]; 54 | }]; 55 | } 56 | 57 | - (void)didReceiveMemoryWarning 58 | { 59 | [super didReceiveMemoryWarning]; 60 | // Dispose of any resources that can be recreated. 61 | } 62 | 63 | #pragma mark - Table view data source 64 | 65 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 66 | { 67 | return 1; 68 | } 69 | 70 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 71 | { 72 | return self.messages.count; 73 | } 74 | 75 | - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 76 | UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"SPRMessageCell"]; 77 | SPRMessage *message = self.messages[indexPath.row]; 78 | cell.textLabel.text = [message.messageData path]; 79 | return cell; 80 | } 81 | 82 | - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 83 | NSUInteger index = [self.tableView indexPathForCell:(UITableViewCell *) sender].row; 84 | ((SPRMessageViewController *)segue.destinationViewController).message = self.messages[index]; 85 | } 86 | 87 | -(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 88 | SPRMessageViewController* messageVC =[[SPRMessageViewController alloc] init]; 89 | messageVC.message = self.messages[indexPath.row]; 90 | [self.navigationController pushViewController:messageVC animated:YES]; 91 | } 92 | 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/SPRMessageViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPRMessageViewController.h 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Jen Dron on 6/27/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SPRMessageViewController : UIViewController 13 | @property (nonatomic, strong) SPRMessage *message; 14 | @end 15 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/SPRMessageViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPRMessageViewController.m 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Jen Dron on 6/27/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import "SPRMessageViewController.h" 10 | #import 11 | 12 | @interface SPRMessageViewController () 13 | @property (strong, nonatomic) UIImageView *imageView; 14 | @property (strong, nonatomic) UILabel *messageLabel; 15 | 16 | @end 17 | 18 | @implementation SPRMessageViewController 19 | 20 | - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 21 | { 22 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 23 | if (self) { 24 | // Custom initialization 25 | } 26 | return self; 27 | } 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | 33 | self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; 34 | [self.view addSubview:self.imageView]; 35 | 36 | CGRect labelRect = self.view.bounds; 37 | labelRect.origin.y = 60; 38 | labelRect.size.height = 40; 39 | self.messageLabel = [[UILabel alloc] initWithFrame:labelRect]; 40 | [self.view addSubview:self.messageLabel]; 41 | 42 | self.messageLabel.text = [self.message.messageData path]; 43 | if ([self.message.messageData path]) { 44 | self.imageView.image = [UIImage imageWithContentsOfFile:[self.message.messageData path]]; 45 | } else { 46 | [[SPRSimpleCloudKitManager sharedManager] fetchDetailsForMessage:self.message withCompletionHandler:^(SPRMessage *message, NSError *error) { 47 | self.messageLabel.text = [message.messageData path]; 48 | self.imageView.image = [UIImage imageWithContentsOfFile:[self.message.messageData path]]; 49 | }]; 50 | } 51 | } 52 | 53 | - (void)didReceiveMemoryWarning 54 | { 55 | [super didReceiveMemoryWarning]; 56 | // Dispose of any resources that can be recreated. 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/SPRTabViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPRTabViewController.h 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Adam Wulf on 8/20/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SPRTabViewController : UITabBarController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/SPRTabViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPRTabViewController.m 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Adam Wulf on 8/20/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import "SPRTabViewController.h" 10 | #import "SPRFriendsTableViewController.h" 11 | #import "SPRInboxTableViewController.h" 12 | 13 | @interface SPRTabViewController () 14 | 15 | @end 16 | 17 | @implementation SPRTabViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view. 22 | 23 | // setup all tabs 24 | 25 | SPRFriendsTableViewController* friendList = [[SPRFriendsTableViewController alloc] initWithStyle:UITableViewStylePlain]; 26 | UINavigationController* friendNav = [[UINavigationController alloc] initWithRootViewController:friendList]; 27 | friendNav.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemContacts tag:0]; 28 | 29 | SPRInboxTableViewController* inboxList = [[SPRInboxTableViewController alloc] initWithStyle:UITableViewStylePlain]; 30 | UINavigationController* inboxNav = [[UINavigationController alloc] initWithRootViewController:inboxList]; 31 | inboxNav.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Inbox" image:nil tag:1]; 32 | 33 | self.viewControllers = @[friendNav, inboxNav]; 34 | 35 | } 36 | 37 | - (void)didReceiveMemoryWarning { 38 | [super didReceiveMemoryWarning]; 39 | // Dispose of any resources that can be recreated. 40 | } 41 | 42 | /* 43 | #pragma mark - Navigation 44 | 45 | // In a storyboard-based application, you will often want to do a little preparation before navigation 46 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 47 | // Get the new view controller using [segue destinationViewController]. 48 | // Pass the selected object to the new view controller. 49 | } 50 | */ 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/Simple CloudKit Messenger Sample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.icloud-container-identifiers 6 | 7 | iCloud.$(CFBundleIdentifier) 8 | 9 | com.apple.developer.icloud-services 10 | 11 | CloudKit 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Simple CloudKit App/Source/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CloudKit Manager 4 | // 5 | // Created by Bob Spryn on 6/11/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SimpleCloudKit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/SimpleCloudKitManager.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 66AFAD2E1A8DE02F00FD0263 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 660549E319A29F6D00F5B17F /* Foundation.framework */; }; 11 | 66AFAD351A8DE02F00FD0263 /* SimpleCloudKitManager-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 66AFAD341A8DE02F00FD0263 /* SimpleCloudKitManager-Info.plist */; }; 12 | 66AFAD401A8DE0AC00FD0263 /* CKDiscoveredUserInfo+Extras.m in Sources */ = {isa = PBXBuildFile; fileRef = 66BEBFEF19BBB92D0032D30C /* CKDiscoveredUserInfo+Extras.m */; }; 13 | 66AFAD411A8DE0AE00FD0263 /* SPRMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 6623F6A119A2A44C00E21399 /* SPRMessage.m */; }; 14 | 66AFAD421A8DE0B000FD0263 /* SPRSimpleCloudKitManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6623F6A319A2A44C00E21399 /* SPRSimpleCloudKitManager.m */; }; 15 | 66AFAD431A8DE0B300FD0263 /* SPRSimpleCloudKitManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6623F6A219A2A44C00E21399 /* SPRSimpleCloudKitManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | 66AFAD441A8DE0B900FD0263 /* SPRMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 6623F6A019A2A44C00E21399 /* SPRMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | 66AFAD451A8DE0BA00FD0263 /* SPRMessage+Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 66F968A919C10BA700C1F696 /* SPRMessage+Protected.h */; }; 18 | 66AFAD461A8DE0BD00FD0263 /* SPRConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 663523E819A7E8FB00A26B88 /* SPRConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | 66AFAD471A8DE0BE00FD0263 /* CKDiscoveredUserInfo+Extras.h in Headers */ = {isa = PBXBuildFile; fileRef = 66BEBFEE19BBB92D0032D30C /* CKDiscoveredUserInfo+Extras.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 660549DD19A29E0F00F5B17F /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 24 | 660549E319A29F6D00F5B17F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 25 | 6623F6A019A2A44C00E21399 /* SPRMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRMessage.h; sourceTree = ""; }; 26 | 6623F6A119A2A44C00E21399 /* SPRMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRMessage.m; sourceTree = ""; }; 27 | 6623F6A219A2A44C00E21399 /* SPRSimpleCloudKitManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPRSimpleCloudKitManager.h; sourceTree = ""; }; 28 | 6623F6A319A2A44C00E21399 /* SPRSimpleCloudKitManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPRSimpleCloudKitManager.m; sourceTree = ""; }; 29 | 663523E819A7E8FB00A26B88 /* SPRConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SPRConstants.h; sourceTree = ""; }; 30 | 66AFAD2D1A8DE02F00FD0263 /* SimpleCloudKitManager.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework.static; includeInIndex = 0; path = SimpleCloudKitManager.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 66AFAD311A8DE02F00FD0263 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | 66AFAD341A8DE02F00FD0263 /* SimpleCloudKitManager-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SimpleCloudKitManager-Info.plist"; sourceTree = ""; }; 33 | 66AFAD531A8DE2E600FD0263 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 34 | 66BEBFEE19BBB92D0032D30C /* CKDiscoveredUserInfo+Extras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CKDiscoveredUserInfo+Extras.h"; sourceTree = ""; }; 35 | 66BEBFEF19BBB92D0032D30C /* CKDiscoveredUserInfo+Extras.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CKDiscoveredUserInfo+Extras.m"; sourceTree = ""; }; 36 | 66F968A919C10BA700C1F696 /* SPRMessage+Protected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SPRMessage+Protected.h"; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 66AFAD281A8DE02F00FD0263 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | 66AFAD2E1A8DE02F00FD0263 /* Foundation.framework in Frameworks */, 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | 6605498E19A29BF300F5B17F = { 52 | isa = PBXGroup; 53 | children = ( 54 | 66AFAD531A8DE2E600FD0263 /* README.md */, 55 | 6623F66B19A2A1E100E21399 /* Source */, 56 | 66AFAD301A8DE02F00FD0263 /* Supporting Files */, 57 | 6605499B19A29BF300F5B17F /* Frameworks */, 58 | 6605499A19A29BF300F5B17F /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | 6605499A19A29BF300F5B17F /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 66AFAD2D1A8DE02F00FD0263 /* SimpleCloudKitManager.framework */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | 6605499B19A29BF300F5B17F /* Frameworks */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 660549E319A29F6D00F5B17F /* Foundation.framework */, 74 | 660549DD19A29E0F00F5B17F /* CloudKit.framework */, 75 | ); 76 | name = Frameworks; 77 | sourceTree = ""; 78 | }; 79 | 6623F66B19A2A1E100E21399 /* Source */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 66BEBFEE19BBB92D0032D30C /* CKDiscoveredUserInfo+Extras.h */, 83 | 66BEBFEF19BBB92D0032D30C /* CKDiscoveredUserInfo+Extras.m */, 84 | 663523E819A7E8FB00A26B88 /* SPRConstants.h */, 85 | 66F968A919C10BA700C1F696 /* SPRMessage+Protected.h */, 86 | 6623F6A019A2A44C00E21399 /* SPRMessage.h */, 87 | 6623F6A119A2A44C00E21399 /* SPRMessage.m */, 88 | 6623F6A219A2A44C00E21399 /* SPRSimpleCloudKitManager.h */, 89 | 6623F6A319A2A44C00E21399 /* SPRSimpleCloudKitManager.m */, 90 | ); 91 | path = Source; 92 | sourceTree = ""; 93 | }; 94 | 66AFAD301A8DE02F00FD0263 /* Supporting Files */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 66AFAD341A8DE02F00FD0263 /* SimpleCloudKitManager-Info.plist */, 98 | 66AFAD311A8DE02F00FD0263 /* Info.plist */, 99 | ); 100 | name = "Supporting Files"; 101 | path = SimpleCloudKitManager; 102 | sourceTree = ""; 103 | }; 104 | /* End PBXGroup section */ 105 | 106 | /* Begin PBXHeadersBuildPhase section */ 107 | 66AFAD291A8DE02F00FD0263 /* Headers */ = { 108 | isa = PBXHeadersBuildPhase; 109 | buildActionMask = 2147483647; 110 | files = ( 111 | 66AFAD431A8DE0B300FD0263 /* SPRSimpleCloudKitManager.h in Headers */, 112 | 66AFAD471A8DE0BE00FD0263 /* CKDiscoveredUserInfo+Extras.h in Headers */, 113 | 66AFAD441A8DE0B900FD0263 /* SPRMessage.h in Headers */, 114 | 66AFAD461A8DE0BD00FD0263 /* SPRConstants.h in Headers */, 115 | 66AFAD451A8DE0BA00FD0263 /* SPRMessage+Protected.h in Headers */, 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | /* End PBXHeadersBuildPhase section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | 66AFAD2C1A8DE02F00FD0263 /* SimpleCloudKitManager */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = 66AFAD3A1A8DE02F00FD0263 /* Build configuration list for PBXNativeTarget "SimpleCloudKitManager" */; 125 | buildPhases = ( 126 | 66AFAD3D1A8DE06400FD0263 /* Prepare Build Script */, 127 | 66AFAD271A8DE02F00FD0263 /* Sources */, 128 | 66AFAD281A8DE02F00FD0263 /* Frameworks */, 129 | 66AFAD291A8DE02F00FD0263 /* Headers */, 130 | 66AFAD2A1A8DE02F00FD0263 /* Resources */, 131 | 66AFAD3E1A8DE07600FD0263 /* Process Headers Script */, 132 | 66AFAD3F1A8DE08600FD0263 /* Build Framework Script */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = SimpleCloudKitManager; 139 | productName = SimpleCloudKitManager; 140 | productReference = 66AFAD2D1A8DE02F00FD0263 /* SimpleCloudKitManager.framework */; 141 | productType = "com.apple.product-type.framework.static"; 142 | }; 143 | /* End PBXNativeTarget section */ 144 | 145 | /* Begin PBXProject section */ 146 | 6605498F19A29BF300F5B17F /* Project object */ = { 147 | isa = PBXProject; 148 | attributes = { 149 | LastUpgradeCheck = 0640; 150 | ORGANIZATIONNAME = "Adam Wulf"; 151 | TargetAttributes = { 152 | 66AFAD2C1A8DE02F00FD0263 = { 153 | CreatedOnToolsVersion = 6.1.1; 154 | }; 155 | }; 156 | }; 157 | buildConfigurationList = 6605499219A29BF300F5B17F /* Build configuration list for PBXProject "SimpleCloudKitManager" */; 158 | compatibilityVersion = "Xcode 3.2"; 159 | developmentRegion = English; 160 | hasScannedForEncodings = 0; 161 | knownRegions = ( 162 | en, 163 | ); 164 | mainGroup = 6605498E19A29BF300F5B17F; 165 | productRefGroup = 6605499A19A29BF300F5B17F /* Products */; 166 | projectDirPath = ""; 167 | projectRoot = ""; 168 | targets = ( 169 | 66AFAD2C1A8DE02F00FD0263 /* SimpleCloudKitManager */, 170 | ); 171 | }; 172 | /* End PBXProject section */ 173 | 174 | /* Begin PBXResourcesBuildPhase section */ 175 | 66AFAD2A1A8DE02F00FD0263 /* Resources */ = { 176 | isa = PBXResourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 66AFAD351A8DE02F00FD0263 /* SimpleCloudKitManager-Info.plist in Resources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXResourcesBuildPhase section */ 184 | 185 | /* Begin PBXShellScriptBuildPhase section */ 186 | 66AFAD3D1A8DE06400FD0263 /* Prepare Build Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Prepare Build Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "set -e\n\nset +u\nif [[ $UFW_MASTER_SCRIPT_RUNNING ]]\nthen\n# Nothing for the slave script to do\nexit 0\nfi\nset -u\n\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]\nthen\nUFW_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]\nthen\nUFW_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$UFW_SDK_PLATFORM\" = \"iphoneos\" ]]\nthen\nUFW_OTHER_PLATFORM=iphonesimulator\nelse\nUFW_OTHER_PLATFORM=iphoneos\nfi\n\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$UFW_SDK_PLATFORM$ ]]\nthen\nUFW_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${UFW_OTHER_PLATFORM}\"\nelse\necho \"Could not find $UFW_SDK_PLATFORM in $BUILT_PRODUCTS_DIR\"\nexit 1\nfi\n\nONLY_ACTIVE_PLATFORM=${ONLY_ACTIVE_PLATFORM:-$ONLY_ACTIVE_ARCH}\n\n# Short-circuit if all binaries are up to date\n\nif [[ -f \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]] && \\\n[[ -f \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]] && \\\n[[ ! \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -nt \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]] && \\\n([[ \"${ONLY_ACTIVE_PLATFORM}\" == \"YES\" ]] || \\\n([[ -f \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]] && \\\n[[ -f \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]] && \\\n[[ ! \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -nt \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${EXECUTABLE_PATH}\" ]]\n)\n)\nthen\nexit 0\nfi\n\n\n# Clean other platform if needed\n\nif [[ ! -f \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]] && [[ \"${ONLY_ACTIVE_PLATFORM}\" != \"YES\" ]]\nthen\necho \"Platform \\\"$UFW_SDK_PLATFORM\\\" was cleaned recently. Cleaning \\\"$UFW_OTHER_PLATFORM\\\" as well\"\necho xcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" clean\nxcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" clean\nfi\n\n\n# Make sure we are building from fresh binaries\n\nrm -rf \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\nrm -rf \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\n\nif [[ \"${ONLY_ACTIVE_PLATFORM}\" != \"YES\" ]]\nthen\nrm -rf \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\nrm -rf \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\nfi\n"; 199 | }; 200 | 66AFAD3E1A8DE07600FD0263 /* Process Headers Script */ = { 201 | isa = PBXShellScriptBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | ); 205 | inputPaths = ( 206 | ); 207 | name = "Process Headers Script"; 208 | outputPaths = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | shellPath = /bin/sh; 212 | shellScript = "\nHEADERS_ROOT=$SRCROOT/$PRODUCT_NAME\nFRAMEWORK_HEADERS_DIR=\"$BUILT_PRODUCTS_DIR/$WRAPPER_NAME/Versions/$FRAMEWORK_VERSION/Headers\"\n\n## only header files expected at this point\nPUBLIC_HEADERS=$(find $FRAMEWORK_HEADERS_DIR/. -not -type d 2> /dev/null | sed -e \"s@.*/@@g\")\n\nFIND_OPTS=\"\"\nfor PUBLIC_HEADER in $PUBLIC_HEADERS; do\nif [ -n \"$FIND_OPTS\" ]; then\nFIND_OPTS=\"$FIND_OPTS -o\"\nfi\nFIND_OPTS=\"$FIND_OPTS -name '$PUBLIC_HEADER'\"\ndone\n\nif [ -n \"$FIND_OPTS\" ]; then\nfor ORIG_HEADER in $(eval \"find $HEADERS_ROOT/. $FIND_OPTS\" 2> /dev/null | sed -e \"s@^$HEADERS_ROOT/./@@g\"); do\nPUBLIC_HEADER=$(basename $ORIG_HEADER)\nRELATIVE_PATH=$(dirname $ORIG_HEADER)\nif [ -e $FRAMEWORK_HEADERS_DIR/$PUBLIC_HEADER ]; then\nmkdir -p \"$FRAMEWORK_HEADERS_DIR/$RELATIVE_PATH\"\nmv \"$FRAMEWORK_HEADERS_DIR/$PUBLIC_HEADER\" \"$FRAMEWORK_HEADERS_DIR/$RELATIVE_PATH/$PUBLIC_HEADER\"\nfi\ndone\nfi\n"; 213 | }; 214 | 66AFAD3F1A8DE08600FD0263 /* Build Framework Script */ = { 215 | isa = PBXShellScriptBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | inputPaths = ( 220 | ); 221 | name = "Build Framework Script"; 222 | outputPaths = ( 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | shellPath = /bin/sh; 226 | shellScript = "\nset -e\n\nset +u\nif [[ $UFW_MASTER_SCRIPT_RUNNING ]]\nthen\n# Nothing for the slave script to do\nexit 0\nfi\nset -u\nexport UFW_MASTER_SCRIPT_RUNNING=1\n\n\n# Functions\n\n## List files in the specified directory, storing to the specified array.\n#\n# @param $1 The path to list\n# @param $2 The name of the array to fill\n#\n##\nlist_files ()\n{\n filelist=$(ls \"$1\")\n while read line\n do\n eval \"$2[\\${#$2[*]}]=\\\"\\$line\\\"\"\n done <<< \"$filelist\"\n}\n\n\n# Sanity check\n\nif [[ ! -f \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" ]]\nthen\necho \"Framework target \\\"${TARGET_NAME}\\\" had no source files to build from. Make sure your source files have the correct target membership\"\nexit 1\nfi\n\necho \"Target Executable: ${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\n\n\n# Gather information\n\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]\nthen\nUFW_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]\nthen\nUFW_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\nif [[ \"$UFW_SDK_PLATFORM\" = \"iphoneos\" ]]\nthen\nUFW_OTHER_PLATFORM=iphonesimulator\nelse\nUFW_OTHER_PLATFORM=iphoneos\nfi\n\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$UFW_SDK_PLATFORM$ ]]\nthen\nUFW_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${UFW_OTHER_PLATFORM}\"\nelse\necho \"Could not find $UFW_SDK_PLATFORM in $BUILT_PRODUCTS_DIR\"\nexit 1\nfi\n\nONLY_ACTIVE_PLATFORM=${ONLY_ACTIVE_PLATFORM:-$ONLY_ACTIVE_ARCH}\n\n# Short-circuit if all binaries are up to date.\n# We already checked the other platform in the prerun script.\n\nif [[ -f \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\" ]] && [[ -f \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${PRODUCT_NAME}\" ]] && [[ ! \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\" -nt \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/${PRODUCT_NAME}\" ]]\nthen\nexit 0\nfi\n\nif [ \"${ONLY_ACTIVE_PLATFORM}\" == \"YES\" ]\nthen\necho \"ONLY_ACTIVE_PLATFORM=${ONLY_ACTIVE_PLATFORM}: Skipping other platform build\"\nelse\n# Make sure the other platform gets built\n\necho \"Build other platform\"\n\necho xcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" $ACTION\nxcodebuild -project \"${PROJECT_FILE_PATH}\" -target \"${TARGET_NAME}\" -configuration \"${CONFIGURATION}\" -sdk ${UFW_OTHER_PLATFORM}${UFW_SDK_VERSION} BUILD_DIR=\"${BUILD_DIR}\" CONFIGURATION_TEMP_DIR=\"${PROJECT_TEMP_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\" $ACTION\n\n\n# Build the fat static library binary\n\necho \"Create universal static library\"\n\necho \"$PLATFORM_DEVELOPER_BIN_DIR/libtool\" -static \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -o \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\"\n\"$PLATFORM_DEVELOPER_BIN_DIR/libtool\" -static \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" \"${UFW_OTHER_BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" -o \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\"\n\necho mv \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}\"\nmv \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}.temp\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}\"\nfi\n\n# Move executable to product name location\n\necho \"Moving Executable\"\necho \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\"\necho \"to\"\necho \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\"\n\nmv \"${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}\"\n\n# Build embedded framework structure\n\necho \"Build Embedded Framework\"\n\necho rm -rf \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\nrm -rf \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework\"\necho mkdir -p \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources\"\nmkdir -p \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources\"\necho cp -a \"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/\"\ncp -a \"${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/\"\n\ndeclare -a UFW_FILE_LIST\nlist_files \"${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\" UFW_FILE_LIST\nfor filename in \"${UFW_FILE_LIST[@]}\"\ndo\nif [[ \"${filename}\" != \"Info.plist\" ]] && [[ ! \"${filename}\" =~ .*\\.lproj$ ]]\nthen\necho ln -sfh \"../${WRAPPER_NAME}/Resources/${filename}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources/${filename}\"\nln -sfh \"../${WRAPPER_NAME}/Resources/${filename}\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.embeddedframework/Resources/${filename}\"\nfi\ndone\n\n\nif [ \"${ONLY_ACTIVE_PLATFORM}\" != \"YES\" ]\nthen\n# Replace other platform's framework with a copy of this one (so that both have the same universal binary)\n\necho \"Copy from $UFW_SDK_PLATFORM to $UFW_OTHER_PLATFORM\"\n\necho rm -rf \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\nrm -rf \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\necho cp -a \"${BUILD_DIR}/${CONFIGURATION}-${UFW_SDK_PLATFORM}\" \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\ncp -a \"${BUILD_DIR}/${CONFIGURATION}-${UFW_SDK_PLATFORM}\" \"${BUILD_DIR}/${CONFIGURATION}-${UFW_OTHER_PLATFORM}\"\nfi\n"; 227 | }; 228 | /* End PBXShellScriptBuildPhase section */ 229 | 230 | /* Begin PBXSourcesBuildPhase section */ 231 | 66AFAD271A8DE02F00FD0263 /* Sources */ = { 232 | isa = PBXSourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | 66AFAD421A8DE0B000FD0263 /* SPRSimpleCloudKitManager.m in Sources */, 236 | 66AFAD401A8DE0AC00FD0263 /* CKDiscoveredUserInfo+Extras.m in Sources */, 237 | 66AFAD411A8DE0AE00FD0263 /* SPRMessage.m in Sources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | /* End PBXSourcesBuildPhase section */ 242 | 243 | /* Begin XCBuildConfiguration section */ 244 | 660549A919A29BF300F5B17F /* Debug */ = { 245 | isa = XCBuildConfiguration; 246 | buildSettings = { 247 | ALWAYS_SEARCH_USER_PATHS = NO; 248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 249 | CLANG_CXX_LIBRARY = "libc++"; 250 | CLANG_ENABLE_MODULES = YES; 251 | CLANG_ENABLE_OBJC_ARC = YES; 252 | CLANG_WARN_BOOL_CONVERSION = YES; 253 | CLANG_WARN_CONSTANT_CONVERSION = YES; 254 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 255 | CLANG_WARN_EMPTY_BODY = YES; 256 | CLANG_WARN_ENUM_CONVERSION = YES; 257 | CLANG_WARN_INT_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_UNREACHABLE_CODE = YES; 260 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 261 | COPY_PHASE_STRIP = NO; 262 | ENABLE_STRICT_OBJC_MSGSEND = YES; 263 | GCC_C_LANGUAGE_STANDARD = gnu99; 264 | GCC_DYNAMIC_NO_PIC = NO; 265 | GCC_OPTIMIZATION_LEVEL = 0; 266 | GCC_PREPROCESSOR_DEFINITIONS = ( 267 | "DEBUG=1", 268 | "$(inherited)", 269 | ); 270 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 273 | GCC_WARN_UNDECLARED_SELECTOR = YES; 274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 275 | GCC_WARN_UNUSED_FUNCTION = YES; 276 | GCC_WARN_UNUSED_VARIABLE = YES; 277 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 278 | MTL_ENABLE_DEBUG_INFO = YES; 279 | SDKROOT = iphoneos; 280 | }; 281 | name = Debug; 282 | }; 283 | 660549AA19A29BF300F5B17F /* Release */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ALWAYS_SEARCH_USER_PATHS = NO; 287 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 288 | CLANG_CXX_LIBRARY = "libc++"; 289 | CLANG_ENABLE_MODULES = YES; 290 | CLANG_ENABLE_OBJC_ARC = YES; 291 | CLANG_WARN_BOOL_CONVERSION = YES; 292 | CLANG_WARN_CONSTANT_CONVERSION = YES; 293 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 294 | CLANG_WARN_EMPTY_BODY = YES; 295 | CLANG_WARN_ENUM_CONVERSION = YES; 296 | CLANG_WARN_INT_CONVERSION = YES; 297 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 298 | CLANG_WARN_UNREACHABLE_CODE = YES; 299 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 300 | COPY_PHASE_STRIP = YES; 301 | ENABLE_NS_ASSERTIONS = NO; 302 | ENABLE_STRICT_OBJC_MSGSEND = YES; 303 | GCC_C_LANGUAGE_STANDARD = gnu99; 304 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 305 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 306 | GCC_WARN_UNDECLARED_SELECTOR = YES; 307 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 308 | GCC_WARN_UNUSED_FUNCTION = YES; 309 | GCC_WARN_UNUSED_VARIABLE = YES; 310 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 311 | MTL_ENABLE_DEBUG_INFO = NO; 312 | SDKROOT = iphoneos; 313 | }; 314 | name = Release; 315 | }; 316 | 66AFAD3B1A8DE02F00FD0263 /* Debug */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | DYLIB_COMPATIBILITY_VERSION = 1; 320 | DYLIB_CURRENT_VERSION = 1; 321 | FRAMEWORK_VERSION = A; 322 | GCC_PREPROCESSOR_DEFINITIONS = ( 323 | "DEBUG=1", 324 | "$(inherited)", 325 | ); 326 | INFOPLIST_FILE = SimpleCloudKitManager/Info.plist; 327 | ONLY_ACTIVE_PLATFORM = YES; 328 | PRODUCT_NAME = "$(TARGET_NAME)"; 329 | SKIP_INSTALL = YES; 330 | WRAPPER_EXTENSION = framework; 331 | }; 332 | name = Debug; 333 | }; 334 | 66AFAD3C1A8DE02F00FD0263 /* Release */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | DYLIB_COMPATIBILITY_VERSION = 1; 338 | DYLIB_CURRENT_VERSION = 1; 339 | FRAMEWORK_VERSION = A; 340 | INFOPLIST_FILE = SimpleCloudKitManager/Info.plist; 341 | ONLY_ACTIVE_PLATFORM = YES; 342 | PRODUCT_NAME = "$(TARGET_NAME)"; 343 | SKIP_INSTALL = YES; 344 | WRAPPER_EXTENSION = framework; 345 | }; 346 | name = Release; 347 | }; 348 | /* End XCBuildConfiguration section */ 349 | 350 | /* Begin XCConfigurationList section */ 351 | 6605499219A29BF300F5B17F /* Build configuration list for PBXProject "SimpleCloudKitManager" */ = { 352 | isa = XCConfigurationList; 353 | buildConfigurations = ( 354 | 660549A919A29BF300F5B17F /* Debug */, 355 | 660549AA19A29BF300F5B17F /* Release */, 356 | ); 357 | defaultConfigurationIsVisible = 0; 358 | defaultConfigurationName = Release; 359 | }; 360 | 66AFAD3A1A8DE02F00FD0263 /* Build configuration list for PBXNativeTarget "SimpleCloudKitManager" */ = { 361 | isa = XCConfigurationList; 362 | buildConfigurations = ( 363 | 66AFAD3B1A8DE02F00FD0263 /* Debug */, 364 | 66AFAD3C1A8DE02F00FD0263 /* Release */, 365 | ); 366 | defaultConfigurationIsVisible = 0; 367 | defaultConfigurationName = Release; 368 | }; 369 | /* End XCConfigurationList section */ 370 | }; 371 | rootObject = 6605498F19A29BF300F5B17F /* Project object */; 372 | } 373 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/SimpleCloudKitManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/SimpleCloudKitManager/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.milestonemade.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/SimpleCloudKitManager/SimpleCloudKitManager-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | 6 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/Source/CKDiscoveredUserInfo+Extras.h: -------------------------------------------------------------------------------- 1 | // 2 | // CKDisoveredUserInfo.h 3 | // LooseLeaf 4 | // 5 | // Created by Adam Wulf on 8/31/14. 6 | // Copyright (c) 2014 Milestone Made, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CKDiscoveredUserInfo (Extras) 13 | 14 | -(NSString*) initials; 15 | 16 | -(NSDictionary*) asDictionary; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/Source/CKDiscoveredUserInfo+Extras.m: -------------------------------------------------------------------------------- 1 | // 2 | // CKDisoveredUserInfo.m 3 | // LooseLeaf 4 | // 5 | // Created by Adam Wulf on 8/31/14. 6 | // Copyright (c) 2014 Milestone Made, LLC. All rights reserved. 7 | // 8 | 9 | #import "CKDiscoveredUserInfo+Extras.h" 10 | 11 | @implementation CKDiscoveredUserInfo (Extras) 12 | 13 | -(NSString*) initials{ 14 | NSString* firstLetter = self.firstName.length > 1 ? [self.firstName substringToIndex:1] : @""; 15 | NSString* lastLetter = self.lastName.length > 1 ? [self.lastName substringToIndex:1] : @""; 16 | return [firstLetter stringByAppendingString:lastLetter]; 17 | } 18 | 19 | -(NSDictionary*) asDictionary{ 20 | if(self.userRecordID){ 21 | return @{ 22 | @"recordId" : self.userRecordID, 23 | @"firstName" : self.firstName ? self.firstName : @"", 24 | @"lastName" : self.lastName ? self.lastName : @"", 25 | @"initials" : self.initials 26 | }; 27 | }else{ 28 | return @{}; 29 | } 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/Source/SPRConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.h 3 | // SimpleCloudKitManager 4 | // 5 | // Created by Adam Wulf on 8/22/14. 6 | // Copyright (c) 2014 Milestone Made, LLC. All rights reserved. 7 | // 8 | 9 | typedef NS_ENUM(NSInteger, SCKMAccountStatus); 10 | typedef NS_ENUM(NSInteger, SCKMApplicationPermissionStatus); 11 | typedef NS_ENUM(NSUInteger, SPRSimpleCloudMessengerError); 12 | 13 | #ifndef SimpleCloudKitManager_Constants_h 14 | #define SimpleCloudKitManager_Constants_h 15 | 16 | typedef NS_ENUM(NSInteger, SCKMAccountStatus) { 17 | /* An error occurred when getting the account status, consult the corresponding NSError */ 18 | SCKMAccountStatusCouldNotDetermine = 0, 19 | /* The iCloud account credentials are available for this application */ 20 | SCKMAccountStatusAvailable = 1, 21 | /* Parental Controls / Device Management has denied access to iCloud account credentials */ 22 | SCKMAccountStatusRestricted = 2, 23 | /* No iCloud account is logged in on this device */ 24 | SCKMAccountStatusNoAccount = 3 25 | }; 26 | 27 | typedef NS_ENUM(NSInteger, SCKMApplicationPermissionStatus) { 28 | /* The user has not made a decision for this application permission. */ 29 | SCKMApplicationPermissionStatusInitialState = 0, 30 | /* An error occurred when getting or setting the application permission status, consult the corresponding NSError */ 31 | SCKMApplicationPermissionStatusCouldNotComplete = 1, 32 | /* The user has denied this application permission */ 33 | SCKMApplicationPermissionStatusDenied = 2, 34 | /* The user has granted this application permission */ 35 | SCKMApplicationPermissionStatusGranted = 3 36 | }; 37 | 38 | 39 | typedef NS_ENUM(NSUInteger, SPRSimpleCloudMessengerError) { 40 | SPRSimpleCloudMessengerErrorUnexpected, 41 | SPRSimpleCloudMessengerErroriCloudAccount, 42 | SPRSimpleCloudMessengerErrorMissingDiscoveryPermissions, 43 | SPRSimpleCloudMessengerErrorNetwork, 44 | SPRSimpleCloudMessengerErrorServiceUnavailable, 45 | SPRSimpleCloudMessengerErrorRateLimit, 46 | SPRSimpleCloudMessengerErrorCancelled, 47 | SPRSimpleCloudMessengerErroriCloudAccountChanged, 48 | }; 49 | 50 | static NSString *const SPRSimpleCloudKitMessengerErrorDomain = @"com.SPRSimpleCloudKitMessenger.ErrorDomain"; 51 | static NSString *const SPRMessageRecordType = @"Message"; 52 | static NSString *const SPRMessageImageField = @"image"; 53 | static NSString *const SPRMessageSenderField = @"sender"; 54 | static NSString *const SPRMessageSenderFirstNameField = @"senderFirstName"; 55 | static NSString *const SPRMessageReceiverField = @"receiver"; 56 | static NSString *const SPRActiveiCloudIdentity = @"SPRActiveiCloudIdentity"; 57 | static NSString *const SPRSubscriptionID = @"SPRSubscriptionID"; 58 | static NSString *const SPRSubscriptionIDIncomingMessages = @"IncomingMessages"; 59 | static NSString *const SPRServerChangeToken = @"SPRServerChangeToken"; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/Source/SPRMessage+Protected.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPRMessage+Protected.h 3 | // SimpleCloudKitManager 4 | // 5 | // Created by Adam Wulf on 9/10/14. 6 | // Copyright (c) 2014 Milestone Made, LLC. All rights reserved. 7 | // 8 | 9 | #ifndef SimpleCloudKitManager_SPRMessage_Protected_h 10 | #define SimpleCloudKitManager_SPRMessage_Protected_h 11 | 12 | // 13 | // SPRMessage.h 14 | // Simple CloudKit Messenger Sample 15 | // 16 | // Created by Bob Spryn on 7/27/14. 17 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 18 | // 19 | 20 | #import 21 | #import 22 | 23 | @interface SPRMessage (Protected) 24 | 25 | - (void) updateMessageWithSenderInfo:(CKDiscoveredUserInfo*)sender; 26 | - (void) updateMessageWithMessageRecord:(CKRecord*) messageRecord; 27 | 28 | +(BOOL) isKeyValid:(NSString*)key; 29 | +(BOOL) isScalar:(id)obj; 30 | 31 | @end 32 | 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/Source/SPRMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPRMessage.h 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Bob Spryn on 7/27/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SPRMessage : NSObject 13 | 14 | // URL to local file of opaque binary blob data of the message 15 | @property (nonatomic, strong, readonly) NSURL *messageData; 16 | @property (nonatomic, strong, readonly) CKRecordID *messageRecordID; 17 | 18 | @property (nonatomic, copy, readonly) NSDictionary* senderInfo; 19 | @property (nonatomic, copy, readonly) NSString *senderFirstName; 20 | @property (nonatomic, copy, readonly) NSString *senderLastName; 21 | @property (nonatomic, strong, readonly) CKRecordID *senderRecordID; 22 | @property (nonatomic, strong, readonly) NSDictionary *attributes; 23 | 24 | - (id) initWithNotification:(CKQueryNotification *) notification; 25 | 26 | -(void) fetchDetailsWithCompletionHandler:(void (^)(NSError *error))completionHandler; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/Source/SPRMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPRMessage.m 3 | // Simple CloudKit Messenger Sample 4 | // 5 | // Created by Bob Spryn on 7/27/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import "SPRMessage.h" 10 | #import "SPRSimpleCloudKitManager.h" 11 | 12 | @implementation SPRMessage 13 | 14 | +(BOOL) isKeyValid:(NSString*)key{ 15 | return [key compare:SPRMessageImageField options:NSCaseInsensitiveSearch] != NSOrderedSame && 16 | [key compare:SPRMessageSenderField options:NSCaseInsensitiveSearch] != NSOrderedSame && 17 | [key compare:SPRMessageSenderFirstNameField options:NSCaseInsensitiveSearch] != NSOrderedSame && 18 | [key compare:SPRMessageReceiverField options:NSCaseInsensitiveSearch] != NSOrderedSame; 19 | } 20 | 21 | +(BOOL) isScalar:(id)obj{ 22 | return [obj isKindOfClass:[NSString class]] || 23 | [obj isKindOfClass:[NSNumber class]] || 24 | [obj isKindOfClass:[NSDate class]]; 25 | } 26 | 27 | - (id) initWithNotification:(CKQueryNotification *) notification{ 28 | self = [super init]; 29 | if (!self) return nil; 30 | 31 | _senderInfo = nil; 32 | _senderRecordID = [[CKRecordID alloc] initWithRecordName:notification.recordFields[SPRMessageSenderField]]; 33 | _messageRecordID = notification.recordID; 34 | return self; 35 | } 36 | 37 | -(NSString*) senderFirstName{ 38 | return [_senderInfo objectForKey:@"firstName"]; 39 | } 40 | 41 | -(NSString*) senderLastName{ 42 | return [_senderInfo objectForKey:@"lastName"]; 43 | } 44 | 45 | -(void) fetchDetailsWithCompletionHandler:(void (^)(NSError *error))completionHandler{ 46 | if(_senderInfo && _messageData){ 47 | // we already have details 48 | completionHandler(nil); 49 | return; 50 | } 51 | 52 | // Do something with the message, like pushing it onto the stack 53 | [[SPRSimpleCloudKitManager sharedManager] fetchDetailsForMessage:self withCompletionHandler:^(SPRMessage *message, NSError *error) { 54 | if(completionHandler)completionHandler(error); 55 | }]; 56 | } 57 | 58 | #pragma mark - Protected 59 | 60 | // these methods are only called by the SPRSimpleCloudKitManager 61 | 62 | -(void) updateMessageWithSenderInfo:(CKDiscoveredUserInfo*)sender{ 63 | _senderInfo = [sender asDictionary]; 64 | } 65 | 66 | - (void) updateMessageWithMessageRecord:(CKRecord*) messageRecord { 67 | CKAsset *imageAsset = messageRecord[SPRMessageImageField]; 68 | _messageData = imageAsset.fileURL; 69 | 70 | NSMutableDictionary* additionalAttributes = [NSMutableDictionary dictionary]; 71 | 72 | for(NSString* key in [messageRecord allKeys]){ 73 | if([SPRMessage isKeyValid:key]){ 74 | id obj = [messageRecord objectForKey:key]; 75 | if([SPRMessage isScalar:obj]){ 76 | [additionalAttributes setValue:obj forKey:key]; 77 | } 78 | } 79 | } 80 | _attributes = [NSDictionary dictionaryWithDictionary:additionalAttributes]; 81 | } 82 | 83 | 84 | #pragma mark - NSCoding 85 | 86 | - (id)initWithCoder:(NSCoder *)decoder { 87 | if (self = [super init]) { 88 | _messageData = [decoder decodeObjectForKey:SPRMessageImageField]; 89 | _senderInfo = [decoder decodeObjectForKey:SPRMessageSenderField]; 90 | 91 | NSData *data = [decoder decodeObjectForKey:@"senderID"]; 92 | _senderRecordID = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 93 | 94 | data = [decoder decodeObjectForKey:@"recordID"]; 95 | _messageRecordID = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 96 | _attributes = [decoder decodeObjectForKey:@"attributes"]; 97 | } 98 | return self; 99 | } 100 | 101 | - (void)encodeWithCoder:(NSCoder *)encoder { 102 | [encoder encodeObject:_messageData forKey:SPRMessageImageField]; 103 | [encoder encodeObject:_senderInfo forKey:SPRMessageSenderField]; 104 | if(_attributes){ 105 | [encoder encodeObject:_attributes forKey:@"attributes"]; 106 | }else{ 107 | [encoder encodeObject:@{} forKey:@"attributes"]; 108 | } 109 | 110 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:_senderRecordID]; 111 | [encoder encodeObject:data forKey:@"senderID"]; 112 | 113 | data = [NSKeyedArchiver archivedDataWithRootObject:_messageRecordID]; 114 | [encoder encodeObject:data forKey:@"recordID"]; 115 | } 116 | 117 | 118 | #pragma mark - NSObject 119 | 120 | -(NSString*) description{ 121 | return [NSString stringWithFormat:@"[SPRMessage: %@", self.messageRecordID]; 122 | } 123 | 124 | -(BOOL) isEqual:(id)object{ 125 | if(object == self) return YES; 126 | if([object isKindOfClass:[SPRMessage class]]){ 127 | return [self.messageRecordID isEqual:[object messageRecordID]]; 128 | } 129 | return NO; 130 | } 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/Source/SPRSimpleCloudKitManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPRSimpleCloudKitMessenger.h 3 | // CloudKit Manager 4 | // 5 | // Created by Bob Spryn on 6/11/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import "SPRMessage.h" 13 | #import "SPRConstants.h" 14 | #import "CKDiscoveredUserInfo+Extras.h" 15 | 16 | /** 17 | * Provides a messaging service using CloudKit 18 | * 19 | * This class is a bare bones implementation of messaging built on top of CloudKit 20 | */ 21 | @interface SPRSimpleCloudKitManager : NSObject 22 | 23 | // logged in user account, if any 24 | @property (nonatomic, readonly) SCKMAccountStatus accountStatus; 25 | @property (nonatomic, readonly) SCKMApplicationPermissionStatus permissionStatus; 26 | @property (nonatomic, readonly) CKRecordID *accountRecordID; 27 | @property (nonatomic, readonly) CKDiscoveredUserInfo *accountInfo; 28 | @property (nonatomic, getter=isSubscribed) BOOL subscribed; 29 | 30 | 31 | /** @return The configured SPRSimpleCloudKitMessenger instance */ 32 | + (SPRSimpleCloudKitManager *) sharedManager; 33 | 34 | -(void) reset; 35 | 36 | - (void) silentlyVerifyiCloudAccountStatusOnComplete:(void (^)(SCKMAccountStatus accountStatus, SCKMApplicationPermissionStatus permissionStatus, NSError *error)) completionHandler; 37 | 38 | - (void) silentlyFetchUserInfoOnComplete:(void (^)(CKRecordID *recordID, CKDiscoveredUserInfo * userInfo, NSError *error)) completionHandler; 39 | 40 | - (void) silentlyFetchUserRecordIDOnComplete:(void (^)(CKRecordID *recordID, NSError *error))completionHandler; 41 | 42 | -(void) silentlyFetchUserInfoForUserId:(CKRecordID*)userRecordID onComplete:(void (^)(CKDiscoveredUserInfo *, NSError *))completionHandler; 43 | 44 | /** The main entry point for using this class 45 | * 46 | * This method does the majority of the heavy lifting for setting up for the active iCloud user. 47 | * It checks if they have a valid iCloud account and prompts for them to be discoverable. It will return an error 48 | * if they don't have a valid iCloud account, or if their discovery permissions are disabled. 49 | * 50 | * This method will also return an error if the user changed iCloud accounts since the last time they used your app. 51 | * You should check for error code == SPRSimpleCloudMessengerErroriCloudAccountChanged and clean up any private user data. You may or may 52 | * not want to display the error message from this error. 53 | * This would be a good hook for "logging out" if that applies to your app. Once you have cleaned up old user data, call this method 54 | * again to prepare for the new iCloud user (or when they tap a "log in" button. 55 | * 56 | * Any errors returned from this method, or any other method on this class, will have a friendly error message in NSLocalizedDescription. 57 | * All serious errors will carry the code SPRSimpleCloudMessengerErrorUnexpected. 58 | * 59 | * Once "logged in", you should call this method every time your app becomes active so it can perform it's checks. 60 | * @param completionHandler will either return a CKDiscoveredUserInfo or an NSError 61 | */ 62 | - (void) promptAndFetchUserInfoOnComplete:(void (^)(SCKMApplicationPermissionStatus permissionStatus, 63 | CKRecordID *recordID, 64 | CKDiscoveredUserInfo * userInfo, 65 | NSError *error)) completionHandler; 66 | 67 | -(void) promptForRemoteNotificationsIfNecessary; 68 | 69 | /** Method for retrieving all discoverable friends from the user's address book. 70 | * @param completionHandler will either return an NSArray of CKDiscoveredUserInfo or an NSError 71 | */ 72 | - (void) discoverAllFriendsWithCompletionHandler:(void (^)(NSArray *friendRecords, NSError *error)) completionHandler; 73 | 74 | /** Method for retrieving all new messages 75 | * @param completionHandler that will be called after fetching new notifications. Will include an NSArray of SPRMessage objects, or an NSError param. 76 | */ 77 | - (void) fetchNewMessagesAndMarkAsReadWithCompletionHandler:(void (^)(NSArray *messages, NSError *error)) completionHandler; 78 | /** Method for retrieving the details about a certain messge 79 | * 80 | * Use this when trying to display the detail view of a message including the image. You can check for the image on the message 81 | * to decide whether to call this. It updates the existing message object, and returns it again in the callback 82 | * @param completionHandler will be called after the fetching is complete with either the full message object or an NSError 83 | */ 84 | - (void) fetchDetailsForMessage:(SPRMessage *)message withCompletionHandler:(void (^)(SPRMessage *message, NSError *error)) completionHandler; 85 | 86 | /** Method for sending a message to the specified user record ID 87 | * @param message an NSString of the text you want to send 88 | * @param imageURL a NSURL to the image on disk 89 | * @param userRecordID a valid CKRecordID for the user the message is destined for 90 | * @param completionHandler will return an NSError if the send failed 91 | */ 92 | - (void) sendFile:(NSURL *)imageURL withAttributes:(NSDictionary*)attributes toUserRecordID:(CKRecordID*)userRecordID withProgressHandler:(void (^)(CGFloat progress))progressHandler withCompletionHandler:(void (^)(NSError *error)) completionHandler; 93 | 94 | /** Method for turning a CKQueryNotification into a SPRMessage object 95 | * 96 | * Use this when trying to convert a one off CKQueryNotification into a message object. 97 | * For fetching all new message notifications and creating message objects see `fetchNewMessagesWithCompletionHandler` 98 | * @param completionHandler will be called after the fetching is complete with either the full message object or an NSError 99 | */ 100 | - (void) messageForQueryNotification:(CKQueryNotification *) notification withCompletionHandler:(void (^)(SPRMessage *message, NSError *error)) completionHandler; 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /SimpleCloudKitManager/Source/SPRSimpleCloudKitManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPRSimpleCloudKitMessenger.m 3 | // CloudKit Manager 4 | // 5 | // Created by Bob Spryn on 6/11/14. 6 | // Copyright (c) 2014 Sprynthesis. All rights reserved. 7 | // 8 | 9 | #import "SPRSimpleCloudKitManager.h" 10 | #import 11 | #import "SPRMessage.h" 12 | #import "SPRMessage+Protected.h" 13 | 14 | @interface SPRSimpleCloudKitManager () 15 | 16 | // logged in user account, if any 17 | @property (nonatomic, assign) SCKMAccountStatus accountStatus; 18 | @property (nonatomic, assign) SCKMApplicationPermissionStatus permissionStatus; 19 | @property (nonatomic, strong) CKRecordID *accountRecordID; 20 | @property (nonatomic, strong) CKDiscoveredUserInfo *accountInfo; 21 | 22 | @property (readonly) CKContainer *container; 23 | @property (readonly) CKDatabase *publicDatabase; 24 | @property (nonatomic, strong) CKServerChangeToken *serverChangeToken; 25 | 26 | @end 27 | 28 | @implementation SPRSimpleCloudKitManager{ 29 | BOOL subscribeIsInFlight; 30 | CKFetchNotificationChangesOperation *mostRecentFetchNotification; 31 | } 32 | 33 | - (id)init { 34 | self = [super init]; 35 | if (self) { 36 | _container = [CKContainer defaultContainer]; 37 | _publicDatabase = [_container publicCloudDatabase]; 38 | 39 | if(!_container.containerIdentifier){ 40 | NSLog(@"no container"); 41 | _container = nil; 42 | return nil; 43 | } 44 | } 45 | return self; 46 | } 47 | 48 | + (SPRSimpleCloudKitManager *) sharedManager { 49 | static dispatch_once_t onceToken; 50 | static SPRSimpleCloudKitManager *messenger; 51 | dispatch_once(&onceToken, ^{ 52 | messenger = [[SPRSimpleCloudKitManager alloc] init]; 53 | }); 54 | return messenger; 55 | } 56 | 57 | -(void) reset{ 58 | self.accountStatus = SCKMAccountStatusCouldNotDetermine; 59 | self.permissionStatus = SCKMApplicationPermissionStatusCouldNotComplete; 60 | self.accountInfo = nil; 61 | self.accountRecordID = nil; 62 | } 63 | 64 | #pragma mark - Account status and discovery 65 | 66 | // Verifies iCloud Account Status and that the iCloud ubiquityIdentityToken hasn't changed 67 | - (void) silentlyVerifyiCloudAccountStatusOnComplete:(void (^)(SCKMAccountStatus accountStatus, SCKMApplicationPermissionStatus permissionStatus, NSError *error)) completionHandler { 68 | // first, see if we have an iCloud account at all 69 | [self.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) { 70 | //#ifdef DEBUG 71 | // [NSThread sleepForTimeInterval:3]; 72 | //#endif 73 | _accountStatus = (SCKMAccountStatus) accountStatus; 74 | 75 | __block NSError *theError = nil; 76 | if (error) { 77 | theError = [self simpleCloudMessengerErrorForError:error]; 78 | self.permissionStatus = SCKMApplicationPermissionStatusCouldNotComplete; 79 | dispatch_async(dispatch_get_main_queue(), ^{ 80 | @autoreleasepool { 81 | // theError will either be an error or nil, so we can always pass it in 82 | if(completionHandler) completionHandler(self.accountStatus, self.permissionStatus, theError); 83 | } 84 | }); 85 | } else { 86 | // if it's not a valid account raise an error 87 | if (accountStatus != CKAccountStatusAvailable) { 88 | NSString *errorString = [self simpleCloudMessengerErrorStringForErrorCode:SPRSimpleCloudMessengerErroriCloudAccount]; 89 | theError = [NSError errorWithDomain:SPRSimpleCloudKitMessengerErrorDomain 90 | code:SPRSimpleCloudMessengerErroriCloudAccount 91 | userInfo:@{NSLocalizedDescriptionKey: errorString }]; 92 | self.permissionStatus = SCKMApplicationPermissionStatusCouldNotComplete; 93 | dispatch_async(dispatch_get_main_queue(), ^{ 94 | @autoreleasepool { 95 | // theError will either be an error or nil, so we can always pass it in 96 | if(completionHandler) completionHandler(self.accountStatus, self.permissionStatus, theError); 97 | } 98 | }); 99 | } else { 100 | [self.container statusForApplicationPermission:CKApplicationPermissionUserDiscoverability 101 | completionHandler:^(CKApplicationPermissionStatus applicationPermissionStatus, NSError *error) { 102 | // ok, we've got our permission status now 103 | self.permissionStatus = (SCKMApplicationPermissionStatus) applicationPermissionStatus; 104 | dispatch_async(dispatch_get_main_queue(), ^{ 105 | @autoreleasepool { 106 | // theError will either be an error or nil, so we can always pass it in 107 | if(completionHandler) completionHandler(self.accountStatus, self.permissionStatus, theError); 108 | } 109 | }); 110 | }]; 111 | } 112 | } 113 | }]; 114 | } 115 | 116 | 117 | // Uses internal methods to do the majority of the setup for this class 118 | // If everything is successful, it returns the active user CKDiscoveredUserInfo 119 | // All internal methods fire completionHandlers on the main thread, so no need to use GCD in this method 120 | - (void) promptAndFetchUserInfoOnComplete:(void (^)(SCKMApplicationPermissionStatus permissionStatus, CKRecordID *recordID, CKDiscoveredUserInfo * userInfo, NSError *error)) completionHandler { 121 | [self promptToBeDiscoverableIfNeededOnComplete:^(SCKMApplicationPermissionStatus applicationPermissionStatus, NSError *error) { 122 | if (error) { 123 | NSLog(@"Prompt Failed"); 124 | if(completionHandler) completionHandler(applicationPermissionStatus, nil, nil, error); 125 | } else { 126 | NSLog(@"Prompted to be discoverable"); 127 | [self silentlyFetchUserInfoOnComplete:^(CKRecordID* recordID, CKDiscoveredUserInfo* userInfo, NSError* err){ 128 | if(completionHandler) completionHandler(applicationPermissionStatus, recordID, userInfo, error); 129 | }]; 130 | } 131 | }]; 132 | } 133 | // Checks the discoverability of the active user. Prompts if possible, errors if they are in a bad state 134 | - (void) promptToBeDiscoverableIfNeededOnComplete:(void (^)(SCKMApplicationPermissionStatus applicationPermissionStatus, NSError *error)) completionHandler { 135 | [self.container requestApplicationPermission:CKApplicationPermissionUserDiscoverability completionHandler:^(CKApplicationPermissionStatus applicationPermissionStatus, NSError *error) { 136 | self.permissionStatus = (SCKMApplicationPermissionStatus) applicationPermissionStatus; 137 | 138 | __block NSError *theError = nil; 139 | if (error) { 140 | theError = [self simpleCloudMessengerErrorForError:error]; 141 | } else { 142 | // if not "granted", raise an error 143 | if (applicationPermissionStatus != CKApplicationPermissionStatusGranted) { 144 | NSString *errorString = [self simpleCloudMessengerErrorStringForErrorCode:SPRSimpleCloudMessengerErrorMissingDiscoveryPermissions]; 145 | theError = [NSError errorWithDomain:SPRSimpleCloudKitMessengerErrorDomain 146 | code:SPRSimpleCloudMessengerErrorMissingDiscoveryPermissions 147 | userInfo:@{NSLocalizedDescriptionKey: errorString }]; 148 | } 149 | } 150 | // theError will either be an error or nil, so we can always pass it in 151 | dispatch_async(dispatch_get_main_queue(), ^{ 152 | @autoreleasepool { 153 | if (completionHandler) completionHandler((SCKMApplicationPermissionStatus)applicationPermissionStatus, theError); 154 | } 155 | }); 156 | }]; 157 | } 158 | 159 | -(void) promptForRemoteNotificationsIfNecessary{ 160 | NSLog(@"promptForRemoteNotificationsIfNecessary"); 161 | UIUserNotificationSettings* settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert|UIUserNotificationTypeBadge) 162 | categories:nil]; 163 | [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; 164 | [[UIApplication sharedApplication] registerForRemoteNotifications]; 165 | } 166 | 167 | // Fetches the active user CKDiscoveredUserInfo, fairly straightforward 168 | - (void) silentlyFetchUserInfoOnComplete:(void (^)(CKRecordID *recordID, CKDiscoveredUserInfo * userInfo, NSError *error)) completionHandler { 169 | [self silentlyFetchUserRecordIDOnComplete:^(CKRecordID *recordID, NSError *error) { 170 | if (error) { 171 | // don't have to wrap this in GCD main because it's in our internal method on the main queue already 172 | if(completionHandler) completionHandler(nil, nil, error); 173 | } else { 174 | self.accountRecordID = recordID; 175 | if(self.permissionStatus == SCKMApplicationPermissionStatusGranted){ 176 | [self.container discoverUserInfoWithUserRecordID:recordID completionHandler:^(CKDiscoveredUserInfo *userInfo, NSError *error) { 177 | NSError *theError = nil; 178 | if (error) { 179 | theError = [self simpleCloudMessengerErrorForError:error]; 180 | } else { 181 | self.accountInfo = userInfo; 182 | } 183 | // theError will either be an error or nil, so we can always pass it in 184 | dispatch_async(dispatch_get_main_queue(), ^{ 185 | @autoreleasepool { 186 | if(completionHandler) completionHandler(recordID, userInfo, theError); 187 | } 188 | }); 189 | }]; 190 | }else{ 191 | NSError* theError = [NSError errorWithDomain:SPRSimpleCloudKitMessengerErrorDomain code:SPRSimpleCloudMessengerErrorMissingDiscoveryPermissions userInfo:nil]; 192 | dispatch_async(dispatch_get_main_queue(), ^{ 193 | @autoreleasepool { 194 | if(completionHandler) completionHandler(recordID, nil, theError); 195 | } 196 | }); 197 | } 198 | } 199 | }]; 200 | } 201 | 202 | -(void) silentlyFetchUserInfoForUserId:(CKRecordID*)userRecordID onComplete:(void (^)(CKDiscoveredUserInfo *, NSError *))completionHandler{ 203 | if(self.permissionStatus == SCKMApplicationPermissionStatusGranted){ 204 | [self.container discoverUserInfoWithUserRecordID:userRecordID completionHandler:^(CKDiscoveredUserInfo *userInfo, NSError *error) { 205 | NSError *theError = nil; 206 | if (error) { 207 | // NSLog(@"Failed Fetching Active User Info"); 208 | theError = [self simpleCloudMessengerErrorForError:error]; 209 | } else { 210 | // NSLog(@"Active User Info fetched"); 211 | if([self.accountRecordID isEqual:userRecordID]){ 212 | self.accountInfo = userInfo; 213 | } 214 | } 215 | // theError will either be an error or nil, so we can always pass it in 216 | dispatch_async(dispatch_get_main_queue(), ^{ 217 | @autoreleasepool { 218 | if(completionHandler) completionHandler(userInfo, theError); 219 | } 220 | }); 221 | }]; 222 | }else{ 223 | NSError* theError = [NSError errorWithDomain:SPRSimpleCloudKitMessengerErrorDomain code:SPRSimpleCloudMessengerErrorMissingDiscoveryPermissions userInfo:nil]; 224 | dispatch_async(dispatch_get_main_queue(), ^{ 225 | @autoreleasepool { 226 | if(completionHandler) completionHandler(nil, theError); 227 | } 228 | }); 229 | } 230 | } 231 | 232 | 233 | // fetches the active user record ID and stores it in a property 234 | // also kicks off subscription for messages 235 | - (void) silentlyFetchUserRecordIDOnComplete:(void (^)(CKRecordID *recordID, NSError *error))completionHandler { 236 | [self.container fetchUserRecordIDWithCompletionHandler:^(CKRecordID *recordID, NSError *error) { 237 | NSError *theError = nil; 238 | if (error) { 239 | theError = [self simpleCloudMessengerErrorForError:error]; 240 | } else { 241 | self.accountRecordID = recordID; 242 | [self subscribeFor:recordID]; 243 | } 244 | dispatch_async(dispatch_get_main_queue(), ^{ 245 | @autoreleasepool { 246 | // theError will either be an error or nil, so we can always pass it in 247 | if(completionHandler) completionHandler(recordID, theError); 248 | } 249 | }); 250 | }]; 251 | } 252 | 253 | #pragma mark - friends 254 | 255 | // grabs all friends discoverable in the address book, fairly straightforward 256 | - (void) discoverAllFriendsWithCompletionHandler:(void (^)(NSArray *friendRecords, NSError *error)) completionHandler { 257 | [self.container discoverAllContactUserInfosWithCompletionHandler:^(NSArray *userInfos, NSError *error) { 258 | NSError *theError = nil; 259 | if (error) { 260 | if (error.code != CKErrorRequestRateLimited) { 261 | NSLog(@"fetch friends error: %@", error); 262 | } 263 | theError = [self simpleCloudMessengerErrorForError:error]; 264 | } 265 | dispatch_async(dispatch_get_main_queue(), ^{ 266 | @autoreleasepool { 267 | // theError will either be an error or nil, so we can always pass it in 268 | if(completionHandler) completionHandler(userInfos, theError); 269 | } 270 | }); 271 | }]; 272 | } 273 | 274 | #pragma mark - Subscription handling 275 | // handles clearing old subscriptions, and setting up the new one 276 | - (void)subscribeFor:(CKRecordID*)recordId { 277 | if (self.subscribed == NO) { 278 | @synchronized(self){ 279 | if(subscribeIsInFlight){ 280 | return; 281 | } 282 | subscribeIsInFlight = YES; 283 | } 284 | // find existing subscriptions and deletes them 285 | [self.publicDatabase fetchSubscriptionWithID:SPRSubscriptionIDIncomingMessages completionHandler:^(CKSubscription *subscription, NSError *error) { 286 | // this operation silently fails, which is probably the right way to go 287 | if (subscription) { 288 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 289 | [defaults setBool:YES forKey:SPRSubscriptionID]; 290 | _subscribed = YES; 291 | @synchronized(self){ 292 | subscribeIsInFlight = NO; 293 | } 294 | } else { 295 | // else if there are no subscriptions, just setup a new one 296 | NSLog(@"setting up subscription"); 297 | [self setupSubscriptionFor:recordId]; 298 | } 299 | }]; 300 | }else{ 301 | // NSLog(@"subscribed"); 302 | } 303 | } 304 | 305 | - (void) setupSubscriptionFor:(CKRecordID*)recordId { 306 | // create the subscription 307 | [self.publicDatabase saveSubscription:[self incomingMessageSubscriptionFor:recordId] completionHandler:^(CKSubscription *subscription, NSError *error) { 308 | // right now subscription errors fail silently. 309 | @synchronized(self){ 310 | subscribeIsInFlight = NO; 311 | } 312 | if (!error) { 313 | // when i first create a new subscription, it's because 314 | // i'm on a brand new database. so clear out any previous 315 | // server change token, and only use new stuff going forward 316 | NSLog(@"resetting server changed token from: %@", self.serverChangeToken); 317 | [[NSUserDefaults standardUserDefaults] removeObjectForKey:SPRServerChangeToken]; 318 | NSLog(@"resetting server changed token to : %@", self.serverChangeToken); 319 | // save the subscription ID so we aren't constantly trying to create a new one 320 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 321 | [defaults setBool:YES forKey:SPRSubscriptionID]; 322 | _subscribed = YES; 323 | NSLog(@"subscribe success"); 324 | }else{ 325 | NSLog(@"subscribe fail"); 326 | // can't subscribe, so try again in a bit... 327 | dispatch_async(dispatch_get_main_queue(), ^{ 328 | @autoreleasepool { 329 | [self performSelector:@selector(subscribeFor:) withObject:recordId afterDelay:20]; 330 | } 331 | }); 332 | } 333 | }]; 334 | } 335 | 336 | // unused for now, maybe expose as a "log out" method? 337 | - (void)unsubscribe { 338 | if (self.subscribed == YES) { 339 | 340 | CKModifySubscriptionsOperation *modifyOperation = [[CKModifySubscriptionsOperation alloc] init]; 341 | modifyOperation.subscriptionIDsToDelete = @[SPRSubscriptionIDIncomingMessages]; 342 | 343 | modifyOperation.modifySubscriptionsCompletionBlock = ^(NSArray *savedSubscriptions, NSArray *deletedSubscriptionIDs, NSError *error) { 344 | // right now subscription errors fail silently. 345 | if (!error) { 346 | [[NSUserDefaults standardUserDefaults] setBool:NO forKey:SPRSubscriptionID]; 347 | } 348 | }; 349 | 350 | [self.publicDatabase addOperation:modifyOperation]; 351 | } 352 | } 353 | 354 | //- (BOOL)isSubscribed { 355 | // return [[NSUserDefaults standardUserDefaults] boolForKey:SPRSubscriptionID]; 356 | //} 357 | 358 | - (CKSubscription *) incomingMessageSubscriptionFor:(CKRecordID*)recordId { 359 | // setup a subscription watching for new messages with the active user as the receiver 360 | CKReference *receiver = [[CKReference alloc] initWithRecordID:self.accountRecordID action:CKReferenceActionNone]; 361 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@", SPRMessageReceiverField, receiver]; 362 | CKSubscription *itemSubscription = [[CKSubscription alloc] initWithRecordType:SPRMessageRecordType 363 | predicate:predicate 364 | subscriptionID:SPRSubscriptionIDIncomingMessages 365 | options:CKSubscriptionOptionsFiresOnRecordCreation]; 366 | CKNotificationInfo *notification = [[CKNotificationInfo alloc] init]; 367 | notification.alertLocalizationKey = @"%@ just sent you a page!"; 368 | notification.alertLocalizationArgs = @[SPRMessageSenderFirstNameField]; 369 | notification.desiredKeys = @[SPRMessageSenderFirstNameField, SPRMessageSenderField]; 370 | notification.shouldBadge = YES; 371 | // currently not well documented, and doesn't seem to actually startup the app in the background as promised. 372 | // notification.shouldSendContentAvailable = YES; 373 | itemSubscription.notificationInfo = notification; 374 | return itemSubscription; 375 | } 376 | 377 | 378 | #pragma mark - Messaging 379 | 380 | // Does the work of "sending the message" e.g. Creating the message record. 381 | // the attributes is a dictionary, and all of the values must be: 382 | // strings, numbers, booleans, dates. no dictionary/array values are allowed. 383 | - (void) sendFile:(NSURL *)imageURL withAttributes:(NSDictionary*)attributes toUserRecordID:(CKRecordID*)userRecordID withProgressHandler:(void (^)(CGFloat progress))progressHandler withCompletionHandler:(void (^)(NSError *error)) completionHandler { 384 | // if we somehow don't have an active user record ID, raise an error about the iCloud account 385 | if (!self.accountRecordID) { 386 | NSError *error = [NSError errorWithDomain:SPRSimpleCloudKitMessengerErrorDomain 387 | code:SPRSimpleCloudMessengerErroriCloudAccount 388 | userInfo:@{NSLocalizedDescriptionKey: [self simpleCloudMessengerErrorStringForErrorCode:SPRSimpleCloudMessengerErroriCloudAccount]}]; 389 | dispatch_async(dispatch_get_main_queue(), ^{ 390 | @autoreleasepool { 391 | if(completionHandler) completionHandler(error); 392 | } 393 | }); 394 | return; 395 | } 396 | if(!userRecordID){ 397 | dispatch_async(dispatch_get_main_queue(), ^{ 398 | if(completionHandler){ 399 | completionHandler(nil); 400 | } 401 | }); 402 | return; 403 | } 404 | 405 | // assemble the new record 406 | CKRecord *record = [[CKRecord alloc] initWithRecordType:SPRMessageRecordType]; 407 | if (imageURL) { 408 | CKAsset *asset = [[CKAsset alloc] initWithFileURL:imageURL]; 409 | record[SPRMessageImageField] = asset; 410 | } 411 | CKReference *sender = [[CKReference alloc] initWithRecordID:self.accountRecordID action:CKReferenceActionNone]; 412 | record[SPRMessageSenderField] = sender; 413 | CKReference *receiver = [[CKReference alloc] initWithRecordID:userRecordID action:CKReferenceActionNone]; 414 | record[SPRMessageReceiverField] = receiver; 415 | record[SPRMessageSenderFirstNameField] = self.accountInfo.firstName; 416 | 417 | for(NSString* key in [attributes allKeys]){ 418 | if([SPRMessage isKeyValid:key]){ 419 | id obj = [attributes objectForKey:key]; 420 | if([SPRMessage isScalar:obj]){ 421 | [record setValue:obj forKey:key]; 422 | } 423 | } 424 | } 425 | 426 | // save the record, and notify of progress + completion 427 | CKModifyRecordsOperation* saveOp = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:@[record] recordIDsToDelete:@[]]; 428 | saveOp.perRecordProgressBlock = ^(CKRecord *record, double progress){ 429 | if(progressHandler){ 430 | progressHandler(progress); 431 | } 432 | }; 433 | saveOp.perRecordCompletionBlock = ^(CKRecord *record, NSError *error){ 434 | NSLog(@"cloudkit save complete %@", record.recordID); 435 | NSError *theError = nil; 436 | if (error) { 437 | theError = [self simpleCloudMessengerErrorForError:error]; 438 | } 439 | 440 | dispatch_async(dispatch_get_main_queue(), ^{ 441 | @autoreleasepool { 442 | // theError will either be an error or nil, so we can always pass it in 443 | if(completionHandler) completionHandler(theError); 444 | } 445 | }); 446 | }; 447 | [self.publicDatabase addOperation:saveOp]; 448 | } 449 | 450 | // Method for fetching all new messages 451 | - (void) fetchNewMessagesAndMarkAsReadWithCompletionHandler:(void (^)(NSArray *messages, NSError *error)) completionHandler { 452 | @synchronized(self){ 453 | if(mostRecentFetchNotification){ 454 | return; 455 | } 456 | if(!self.isSubscribed){ 457 | NSError* err = [NSError errorWithDomain:SPRSimpleCloudKitMessengerErrorDomain 458 | code:SPRSimpleCloudMessengerErrorUnexpected 459 | userInfo:@{NSLocalizedDescriptionKey: @"Can't fetch new messages without subscription"}]; 460 | dispatch_async(dispatch_get_main_queue(), ^{ 461 | @autoreleasepool { 462 | if(completionHandler){ 463 | completionHandler(@[], err); 464 | } 465 | } 466 | }); 467 | return; 468 | } 469 | 470 | CKFetchNotificationChangesOperation *operation = [[CKFetchNotificationChangesOperation alloc] initWithPreviousServerChangeToken:self.serverChangeToken]; 471 | NSMutableArray *incomingMessages = [@[] mutableCopy]; 472 | NSMutableArray* notificationIds = [[NSMutableArray alloc] init]; 473 | operation.notificationChangedBlock = ^ (CKNotification *notification) { 474 | if([notification isKindOfClass:[CKQueryNotification class]]){ 475 | if(notification.notificationType == CKNotificationTypeQuery){ 476 | SPRMessage* potentiallyMissedMessage = [[SPRMessage alloc] initWithNotification:(CKQueryNotification*)notification]; 477 | NSLog(@"new record: %@", potentiallyMissedMessage.messageRecordID); 478 | [incomingMessages addObject:potentiallyMissedMessage]; 479 | [notificationIds addObject:notification.notificationID]; 480 | } 481 | } 482 | }; 483 | operation.fetchNotificationChangesCompletionBlock = ^ (CKServerChangeToken *serverChangeToken, NSError *operationError) { 484 | NSError *theError = nil; 485 | if (operationError) { 486 | theError = [self simpleCloudMessengerErrorForError:operationError]; 487 | dispatch_async(dispatch_get_main_queue(), ^{ 488 | @autoreleasepool { 489 | // theError will either be an error or nil, so we can always pass it in 490 | if(completionHandler) completionHandler(nil, theError); 491 | } 492 | }); 493 | } else { 494 | if([serverChangeToken isEqual:self.serverChangeToken]){ 495 | // NSLog(@"same server token, no updates"); 496 | }else{ 497 | self.serverChangeToken = serverChangeToken; 498 | NSData *data = [NSKeyedArchiver archivedDataWithRootObject:serverChangeToken]; 499 | [[NSUserDefaults standardUserDefaults] setObject:data forKey:SPRServerChangeToken]; 500 | } 501 | @synchronized(self){ 502 | mostRecentFetchNotification = nil; 503 | } 504 | 505 | dispatch_async(dispatch_get_main_queue(), ^{ 506 | @autoreleasepool { 507 | // theError will either be an error or nil, so we can always pass it in 508 | if(completionHandler){ 509 | completionHandler(incomingMessages, theError); 510 | } 511 | } 512 | }); 513 | } 514 | 515 | if([notificationIds count]){ 516 | // NSLog(@"askign to set read: %@", notificationIds); 517 | CKMarkNotificationsReadOperation* markAsRead = [[CKMarkNotificationsReadOperation alloc] initWithNotificationIDsToMarkRead:notificationIds]; 518 | markAsRead.markNotificationsReadCompletionBlock = ^(NSArray *notificationIDsMarkedRead, NSError *operationError){ 519 | // if(operationError){ 520 | // NSLog(@"couldn't mark %d as read", (int)[notificationIDsMarkedRead count]); 521 | // }else{ 522 | // NSLog(@"marked %d notifiactions as read", (int)[notificationIDsMarkedRead count]); 523 | // } 524 | // NSLog(@"result set read: %@", notificationIds); 525 | }; 526 | [self.container addOperation:markAsRead]; 527 | } 528 | }; 529 | mostRecentFetchNotification = operation; 530 | [self.container addOperation:operation]; 531 | } 532 | } 533 | 534 | - (void) fetchDetailsForMessage:(SPRMessage *)message withCompletionHandler:(void (^)(SPRMessage *message, NSError *error)) completionHandler { 535 | // first fetch the sender information 536 | [self.container discoverUserInfoWithUserRecordID:message.senderRecordID completionHandler:^(CKDiscoveredUserInfo *userInfo, NSError *error) { 537 | NSError *theError = nil; 538 | if (error) { 539 | theError = [self simpleCloudMessengerErrorForError:error]; 540 | dispatch_async(dispatch_get_main_queue(), ^{ 541 | @autoreleasepool { 542 | // error will either be an error or nil, so we can always pass it in 543 | if(completionHandler) completionHandler(message, theError); 544 | } 545 | }); 546 | } else { 547 | // next fetch the binary data 548 | [message updateMessageWithSenderInfo:userInfo]; 549 | 550 | NSLog(@"fetching message: %@", message.messageRecordID); 551 | if(message.messageRecordID){ 552 | CKFetchRecordsOperation* fetchOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:@[message.messageRecordID]]; 553 | //#ifdef DEBUG 554 | // __weak CKFetchRecordsOperation* weakFetchOp = fetchOperation; 555 | //#endif 556 | fetchOperation.perRecordProgressBlock = ^(CKRecordID *record, double progress){ 557 | NSLog(@"per record progress %f", progress); 558 | 559 | //#ifdef DEBUG 560 | // if(progress > .5 && !weakFetchOp.isCancelled){ 561 | // if(rand() % 100 < 15){ 562 | // [weakFetchOp cancel]; 563 | // } 564 | // } 565 | //#endif 566 | }; 567 | fetchOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error){ 568 | NSLog(@"per record completion"); 569 | }; 570 | fetchOperation.fetchRecordsCompletionBlock = ^(NSDictionary* records, NSError* error){ 571 | CKRecord* record = [records objectForKey:message.messageRecordID]; 572 | NSError *theError = nil; 573 | if (!error) { 574 | [message updateMessageWithMessageRecord:record]; 575 | }else{ 576 | theError = [self simpleCloudMessengerErrorForError:error]; 577 | } 578 | dispatch_async(dispatch_get_main_queue(), ^{ 579 | @autoreleasepool { 580 | // error will either be an error or nil, so we can always pass it in 581 | if(completionHandler) completionHandler(message, theError); 582 | } 583 | }); 584 | }; 585 | [self.publicDatabase addOperation:fetchOperation]; 586 | }else{ 587 | if(completionHandler) completionHandler(nil, [NSError errorWithDomain:SPRSimpleCloudKitMessengerErrorDomain code:SPRSimpleCloudMessengerErrorUnexpected userInfo:nil]); 588 | } 589 | } 590 | }]; 591 | } 592 | 593 | - (void) messageForQueryNotification:(CKQueryNotification *)notification withCompletionHandler:(void (^)(SPRMessage *message, NSError *error)) completionHandler { 594 | SPRMessage* message = [[SPRMessage alloc] initWithNotification:notification]; 595 | NSLog(@"was pushed %@", message.messageRecordID); 596 | completionHandler(message, nil); 597 | } 598 | 599 | #pragma mark - Error handling utility methods 600 | 601 | // translates CKError domain errors into SPRSimpleCloudKitMessenger Errors 602 | - (NSError *) simpleCloudMessengerErrorForError:(NSError *) error { 603 | SPRSimpleCloudMessengerError errorCode; 604 | if ([error.domain isEqualToString:CKErrorDomain]) { 605 | errorCode = [self simpleCloudMessengerErrorCodeForCKErrorCode:error.code]; 606 | } else { 607 | errorCode = SPRSimpleCloudMessengerErrorUnexpected; 608 | } 609 | NSString *errorString = [self simpleCloudMessengerErrorStringForErrorCode:errorCode]; 610 | return [NSError errorWithDomain:SPRSimpleCloudKitMessengerErrorDomain 611 | code:errorCode 612 | userInfo:@{NSLocalizedDescriptionKey: errorString, 613 | NSUnderlyingErrorKey: error}]; 614 | } 615 | 616 | // Human friendly error strings for SPRSimpleCloudKitMessenger errors 617 | - (NSString *) simpleCloudMessengerErrorStringForErrorCode: (SPRSimpleCloudMessengerError) code { 618 | switch (code) { 619 | case SPRSimpleCloudMessengerErroriCloudAccount: 620 | return NSLocalizedString(@"We were unable to find a valid iCloud account. Please add or update your iCloud account in the Settings app.", nil); 621 | case SPRSimpleCloudMessengerErrorMissingDiscoveryPermissions: 622 | return NSLocalizedString(@"Your friends are currently unable to discover you. Please enabled discovery permissions in the Settings app.", nil); 623 | case SPRSimpleCloudMessengerErrorNetwork: 624 | return NSLocalizedString(@"There was a network error. Please try again later or when you are back online.", nil); 625 | case SPRSimpleCloudMessengerErrorServiceUnavailable: 626 | return NSLocalizedString(@"The server is currently unavailable. Please try again later.", nil); 627 | case SPRSimpleCloudMessengerErrorCancelled: 628 | return NSLocalizedString(@"The request was cancelled.", nil); 629 | case SPRSimpleCloudMessengerErroriCloudAccountChanged: 630 | return NSLocalizedString(@"The iCloud account in user has changed.", nil); 631 | case SPRSimpleCloudMessengerErrorUnexpected: 632 | default: 633 | return NSLocalizedString(@"There was an unexpected error. Please try again later.", nil); 634 | } 635 | } 636 | 637 | // maps CKError domain error codes into SPRSimpleCloudKitMessenger error domain codes 638 | - (SPRSimpleCloudMessengerError) simpleCloudMessengerErrorCodeForCKErrorCode: (CKErrorCode) code { 639 | switch (code) { 640 | case CKErrorNetworkUnavailable: 641 | case CKErrorNetworkFailure: 642 | return SPRSimpleCloudMessengerErrorNetwork; 643 | case CKErrorServiceUnavailable: 644 | return SPRSimpleCloudMessengerErrorServiceUnavailable; 645 | case CKErrorNotAuthenticated: 646 | return SPRSimpleCloudMessengerErroriCloudAccount; 647 | case CKErrorPermissionFailure: 648 | // right now the ONLY permission is for discovery 649 | // if that changes in the future, will want to make this more accurate 650 | return SPRSimpleCloudMessengerErrorMissingDiscoveryPermissions; 651 | case CKErrorOperationCancelled: 652 | return SPRSimpleCloudMessengerErrorCancelled; 653 | case CKErrorRequestRateLimited: 654 | return SPRSimpleCloudMessengerErrorRateLimit; 655 | case CKErrorBadDatabase: 656 | case CKErrorQuotaExceeded: 657 | case CKErrorZoneNotFound: 658 | case CKErrorBadContainer: 659 | case CKErrorInternalError: 660 | case CKErrorPartialFailure: 661 | case CKErrorMissingEntitlement: 662 | case CKErrorUnknownItem: 663 | case CKErrorInvalidArguments: 664 | case CKErrorResultsTruncated: 665 | case CKErrorServerRecordChanged: 666 | case CKErrorServerRejectedRequest: 667 | case CKErrorAssetFileNotFound: 668 | case CKErrorAssetFileModified: 669 | case CKErrorIncompatibleVersion: 670 | case CKErrorConstraintViolation: 671 | case CKErrorChangeTokenExpired: 672 | case CKErrorBatchRequestFailed: 673 | default: 674 | return SPRSimpleCloudMessengerErrorUnexpected; 675 | } 676 | } 677 | 678 | - (CKServerChangeToken *) serverChangeToken { 679 | NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:SPRServerChangeToken]; 680 | return data ? [NSKeyedUnarchiver unarchiveObjectWithData:data] : nil; 681 | } 682 | 683 | @end 684 | --------------------------------------------------------------------------------