Ответ 1
Решение iOS 8:
OK... тогда.... лол. Я думаю, что решил.
Новое открытие. После прокрутки этой страницы:
http://www.tuaw.com/2014/09/17/psa-do-not-upgrade-to-icloud-drive-during-ios-8-installation/
В нем говорится:
iCloud Drive - это новая и улучшенная iCloud синхронизация и файл Apple который позволяет вам обмениваться документами между вашим iOS 8 устройств и вашего Mac, работающего под управлением OS X 10 Yosemite.
Итак, я решил укусить пулю и обновить свою учетную запись iCloud до диска iCloud (бесплатно для обновления).
После перехода на iCloud drive и повторного запуска моего приложения с несколькими изменениями Xcode 6, он теперь работает.
Некоторые важные замечания:
- iCloud Drive несовместим с предыдущим документом iCloud и хранилищем данных. Поэтому, если вы собираетесь протестировать, убедитесь, что все ваши устройства используют iCloud drive и iOS 8.
- Симулятор только синхронизируется один раз, после запуска приложения, пока устройство непрерывно синхронизирует каждый интервал. Не уверен, что это ошибка симулятора или нет. Или, может быть, моя конфигурация не идеальна.
- Использование "Использовать контейнеры по умолчанию" не работает в симуляторе для меня (но на устройстве это работает) с первой попытки, возможно, потребуется удалить предыдущую копию приложения и переустановить. Сначала попробуйте использовать контейнеры по умолчанию и посмотрите, работает ли он, иначе прочитайте следующий пункт ниже.
-
По этой причине я перешел на использование контейнера Ubiquity с этим шаблоном:
ICloud. $(CFBundleIdentifier)
Так что-то вроде:
iCloud.com.xxxxxxxx.iCloudCoreDataDemo
Где "xxxxxxxx" - это мой идентификатор имени компании.
Я сделал вышеупомянутый контейнер iCloud, войдя в мой центр разработчиков iOS, возможно, вы просто можете нажать знак "+" внутри Xcode 6 и ввести его там, Xcode должен автоматически настроить все для вас.
Один блок кода, который я использовал, чтобы проверить, работает ли он:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.persistentStack = [[PersistentStack alloc] initWithStoreURL:self.storeURL modelURL:self.modelURL];
self.managedObjectContext = self.persistentStack.managedObjectContext;
NSURL *containerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:@"iCloud.com.xxxxxxxxxx.iCloudCoreDataDemo"];
if(containerURL == nil)
{
NSLog(@"containerURL == nil");
}
else
{
NSLog(@"hurray?");
}
return YES;
}
Если вы видите "ура?" то это прекрасно, вы также должны увидеть этот образец текста на вашем консольном выходе Xcode:
2014-10-07 17:37:23.196 iCloudCoreDataDemo[8104:130250] documentsDirectory = file:///Users/xxxxxxxx/Library/Developer/CoreSimulator/Devices/9FAFE881-13CA-4608-8BE6-728C793FAFFB/data/Containers/Data/Application/BC6CA07D-605A-4927-94AF-E9E21E204D2B/Documents/
2014-10-07 17:37:23.386 iCloudCoreDataDemo[8104:130250] storeDidChange
2014-10-07 17:37:23.390 iCloudCoreDataDemo[8104:130250] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](808): CoreData: Ubiquity: nobody~sim301AE3E8-16B2-5A08-917D-7B55D1879BE4:iCloudStore
Using local storage: 1
2014-10-07 17:37:23.402 iCloudCoreDataDemo[8104:130250] hurray?
2014-10-07 17:37:33.909 iCloudCoreDataDemo[8104:130250] storeWillChange
2014-10-07 17:37:33.933 iCloudCoreDataDemo[8104:130250] storeDidChange
2014-10-07 17:37:33.933 iCloudCoreDataDemo[8104:130330] -[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](808): CoreData: Ubiquity: nobody~sim301AE3E8-16B2-5A08-917D-7B55D1879BE4:iCloudStore
Using local storage: 0
Обратите внимание на две важные строки:
Using local storage: 1
позже становится:
Using local storage: 0
Локальное хранилище 1 означает, что в настоящее время оно использует локальное хранилище, а локальное хранилище 0 означает, что оно переместило данные в хранилище iCloud.
Я надеюсь, что это принесет пользу всем остальным.
Только для iOS 7 Решение:
ОК, поэтому я только что что-то обнаружил и сумел заставить его работать только для iOS 7. Я все еще не понял, как это сделать в iOS 8, но я заметил что-то важное.
На моем iPhone 5, работающем под управлением iOS 8.0.2, у меня больше нет опции Документ и данные "в меню настроек iCloud.
Однако на моем iPad под управлением iOS 7 я вижу опции "Документ и данные".
Возможно, именно поэтому он не работает на iOS 8, у нас больше нет хранилища документов и данных?
Во всяком случае, вот что я обнаружил для решения только для iOS 7.
Я нашел эту страницу здесь
и одна из строк говорит:
- Хранилище документов iCloud для видимого пользователем содержимого на основе файлов, Хранилище основных данных или для другого сложного содержимого на основе файлов.
Конечно, я вошел в файл проекта Xcode 6 и пометил "Документы iCloud". Это не было серых кнопок радио, но я все равно оставил его в "Использовать контейнеры по умолчанию".
Одна вещь, которую я узнал, это то, что мне нужно запустить мой PersistentStack в appDelegate. Раньше я пытался инициализировать постоянный стек внутри метода + (id) sharedInstance, но он заставил iCloud только синхронизировать первый раз, поэтому после начальной загрузки и синхронизации добавление новой записи впоследствии не синхронизируется.
Я переписал базовое приложение и немного изменил стойку:
App Delegate.h
#import <UIKit/UIKit.h>
#import "PersistentStack.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) NSManagedObjectContext* managedObjectContext;
@property (nonatomic, strong) PersistentStack* persistentStack;
@end
App Delegate.m
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.persistentStack = [[PersistentStack alloc] initWithStoreURL:self.storeURL modelURL:self.modelURL];
self.managedObjectContext = self.persistentStack.managedObjectContext;
return YES;
}
...
- (NSURL*)storeURL
{
NSURL* documentsDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:NULL];
return [documentsDirectory URLByAppendingPathComponent:@"MyApp.sqlite"];
}
- (NSURL*)modelURL
{
return [[NSBundle mainBundle] URLForResource:@"MyApp" withExtension:@"momd"];
}
Persistent Stack.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Book.h"
#import <UIKit/UIKit.h>
@interface PersistentStack : NSObject
+(id)sharedInstance;
- (id)initWithStoreURL:(NSURL *)storeURL modelURL:(NSURL *)modelURL;
@property (nonatomic,strong,readonly) NSManagedObjectContext *managedObjectContext;
#pragma mark - Regular Methods -
-(Book *)insertNewBookWithDate:(NSDate *)newDate;
-(void)deleteBook:(Book *)book;
-(NSArray *)fetchEntityOfType:(NSString *)entityType withPredicate:(NSPredicate *)predicate andSortKey:(NSString *)sortKey;
@end
Постоянный Stack.m
#import "PersistentStack.h"
#import "AppDelegate.h"
@interface PersistentStack ()
@property (nonatomic,strong,readwrite) NSManagedObjectContext* managedObjectContext;
@property (nonatomic,strong) NSURL* modelURL;
@property (nonatomic,strong) NSURL* storeURL;
@end
@implementation PersistentStack
+(id)sharedInstance
{
static PersistentStack *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
sharedInstance = appDelegate.persistentStack;
});
return sharedInstance;
}
- (id)initWithStoreURL:(NSURL*)storeURL modelURL:(NSURL*)modelURL
{
self = [super init];
if (self) {
self.storeURL = storeURL;
self.modelURL = modelURL;
[self setupManagedObjectContext];
}
return self;
}
- (void)setupManagedObjectContext
{
self.managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
//__weak NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
// iCloud notification subscriptions
NSNotificationCenter *dc = [NSNotificationCenter defaultCenter];
[dc addObserver:self
selector:@selector(storesWillChange:)
name:NSPersistentStoreCoordinatorStoresWillChangeNotification
object:self.managedObjectContext.persistentStoreCoordinator];
[dc addObserver:self
selector:@selector(storesDidChange:)
name:NSPersistentStoreCoordinatorStoresDidChangeNotification
object:self.managedObjectContext.persistentStoreCoordinator];
[dc addObserver:self
selector:@selector(persistentStoreDidImportUbiquitousContentChanges:)
name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:self.managedObjectContext.persistentStoreCoordinator];
NSError* error;
// the only difference in this call that makes the store an iCloud enabled store
// is the NSPersistentStoreUbiquitousContentNameKey in options. I use "iCloudStore"
// but you can use what you like. For a non-iCloud enabled store, I pass "nil" for options.
// Note that the store URL is the same regardless of whether you're using iCloud or not.
// If you create a non-iCloud enabled store, it will be created in the App Documents directory.
// An iCloud enabled store will be created below a directory called CoreDataUbiquitySupport
// in your App Documents directory
[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.storeURL
options:@{ NSPersistentStoreUbiquitousContentNameKey : @"iCloudStore" }
error:&error];
if (error) {
NSLog(@"error: %@", error);
}
}
- (NSManagedObjectModel*)managedObjectModel
{
return [[NSManagedObjectModel alloc] initWithContentsOfURL:self.modelURL];
}
// Subscribe to NSPersistentStoreDidImportUbiquitousContentChangesNotification
- (void)persistentStoreDidImportUbiquitousContentChanges:(NSNotification*)note
{
NSLog(@"%s", __PRETTY_FUNCTION__);
NSLog(@"%@", note.userInfo.description);
NSManagedObjectContext *moc = self.managedObjectContext;
[moc performBlock:^{
[moc mergeChangesFromContextDidSaveNotification:note];
[[NSNotificationCenter defaultCenter] postNotificationName:@"notifiCloudStoreDidChange" object:nil];
/*
// you may want to post a notification here so that which ever part of your app
// needs to can react appropriately to what was merged.
// An exmaple of how to iterate over what was merged follows, although I wouldn't
// recommend doing it here. Better handle it in a delegate or use notifications.
// Note that the notification contains NSManagedObjectIDs
// and not NSManagedObjects.
NSDictionary *changes = note.userInfo;
NSMutableSet *allChanges = [NSMutableSet new];
[allChanges unionSet:changes[NSInsertedObjectsKey]];
[allChanges unionSet:changes[NSUpdatedObjectsKey]];
[allChanges unionSet:changes[NSDeletedObjectsKey]];
for (NSManagedObjectID *objID in allChanges) {
// do whatever you need to with the NSManagedObjectID
// you can retrieve the object from with [moc objectWithID:objID]
}
*/
}];
}
// Subscribe to NSPersistentStoreCoordinatorStoresWillChangeNotification
// most likely to be called if the user enables / disables iCloud
// (either globally, or just for your app) or if the user changes
// iCloud accounts.
- (void)storesWillChange:(NSNotification *)note {
NSLog(@"storeWillChange");
NSManagedObjectContext *moc = self.managedObjectContext;
//[moc performBlockAndWait:^{
[moc performBlock:^{
NSError *error = nil;
if ([moc hasChanges]) {
[moc save:&error];
}
[moc reset];
}];
// now reset your UI to be prepared for a totally different
// set of data (eg, popToRootViewControllerAnimated:)
// but don't load any new data yet.
}
// Subscribe to NSPersistentStoreCoordinatorStoresDidChangeNotification
- (void)storesDidChange:(NSNotification *)note {
// here is when you can refresh your UI and
// load new data from the new store
NSLog(@"storeDidChange");
[[NSNotificationCenter defaultCenter] postNotificationName:@"notifiCloudStoreDidChange" object:nil];
}
#pragma mark - Regular Methods -
-(Book *)insertNewBookWithDate:(NSDate *)newDate
{
Book *newBook = [NSEntityDescription insertNewObjectForEntityForName:@"Book" inManagedObjectContext:self.managedObjectContext];
newBook.bookName = @"Book";
newBook.publishDate = newDate;
[self.managedObjectContext save:nil];
return newBook;
}
-(void)deleteBook:(Book *)book
{
[self.managedObjectContext deleteObject:book];
[self.managedObjectContext save:nil];
}
-(NSArray *)fetchEntityOfType:(NSString *)entityType withPredicate:(NSPredicate *)predicate andSortKey:(NSString *)sortKey
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityType inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Specify criteria for filtering which objects to fetch
[fetchRequest setPredicate:predicate];
// Specify how the fetched objects should be sorted
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey
ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
NSError *error = nil;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil)
{
NSLog(@"couldn't fetch entity of type '%@', error: %@", entityType, error.localizedDescription);
}
return fetchedObjects;
}
@end