Ok. 이 하나가 나를 벽으로 몰아 넣고있다. 나는UIManagedDocument의 상위 NSManagedObjectContext에 대한 백그라운드 큐 변경으로 인해 NSFetchedresultsController가 중복됩니다.
- UIManagedDocument과 2 MOContexts이 (일반 및 부모를.)
- 있는 UITableViewController이는
- NSFetchedResultsController
- 오프 실행 (폴 Hegarty에 의해 CoreDataTableViewController에 서브 클래스) 동기화에 대 한 배경 GCD 큐 부모 큐가 액세스하는 서버와 함께
나는 이것을 여러 가지 방법으로 시도해 봤는데 매번 문제가 생겼다.
"동물"엔티티를 새로 추가 할 때 아무런 문제가없고 즉시 테이블에 나타납니다. 하지만 업로드 대기열에있는 서버로 업로드하고 업로드 된 섹션에 있어야하도록 부모 컨텍스트가있는 "상태"를 변경하면 거기에 나타나지만 업로드되지 않은 섹션에서는 사라지지 않습니다. .
내가 끝내지 않은 2 개로 끝났다. 또는 심지어 때로는 올바른 것을 만들지 않고 단지 잘못된 것을 유지합니다.
***하지만 앱을 종료하고 다시로드하면 추가 정보가 사라집니다. 그래서 그것은 어딘가에있는 기억에 있습니다. 나는 상점에서 모든 것이 정확하다는 것을 확인할 수 있습니다. 하지만 NSFetchedResultsController는 controllerDidChange ... stuff를 실행하지 않습니다. 누군가를 통해 웨이드 할 수 있도록
- (NSArray *)sectionHeaderTitles
{
if (_sectionHeaderTitles == nil) _sectionHeaderTitles = [NSArray arrayWithObjects:@"Not Yet Uploaded", @"Uploaded But Not Featured", @"Previously Featured", nil];
return _sectionHeaderTitles;
}
- (NSDictionary *)selectedEntry
{
if (_selectedEntry == nil) _selectedEntry = [[NSDictionary alloc] init];
return _selectedEntry;
}
- (void)setupFetchedResultsController
{
[self.photoDatabase.managedObjectContext setStalenessInterval:0.0];
[self.photoDatabase.managedObjectContext.parentContext setStalenessInterval:0.0];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Animal"];
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:@"status" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:@"unique" ascending:NO], nil];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.photoDatabase.managedObjectContext sectionNameKeyPath:@"status" cacheName:nil];
NSError *error;
BOOL success = [self.fetchedResultsController performFetch:&error];
if (!success) NSLog(@"error: %@", error);
else [self.tableView reloadData];
self.fetchedResultsController.delegate = self;
}
- (void)useDocument
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.photoDatabase.fileURL path]]) {
[self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.photoDatabase.documentState == UIDocumentStateClosed) {
[self.photoDatabase openWithCompletionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.photoDatabase.documentState == UIDocumentStateNormal) {
[self setupFetchedResultsController];
}
}
- (void)setPhotoDatabase:(WLManagedDocument *)photoDatabase
{
if (_photoDatabase != photoDatabase) {
_photoDatabase = photoDatabase;
[self useDocument];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont fontWithName:@"AmericanTypewriter" size:20];
label.shadowColor = [UIColor colorWithWhite:0.0 alpha:0.5];
label.textAlignment = UITextAlignmentCenter;
label.textColor = [UIColor whiteColor];
self.navigationItem.titleView = label;
label.text = self.navigationItem.title;
[label sizeToFit];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Get CoreData database made if necessary
if (!self.photoDatabase) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:@"Default Photo Database"];
self.photoDatabase = [[WLManagedDocument alloc] initWithFileURL:url];
NSLog(@"No existing photoDatabase so a new one was created from default photo database file.");
}
self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"DarkWoodBackGround.png"]];
}
- (void)syncWithServer
{
// This is done on the syncQ
// Start the activity indicator on the nav bar
dispatch_async(dispatch_get_main_queue(), ^{
[self.spinner startAnimating];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.spinner];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:self.photoDatabase.managedObjectContext.parentContext];
});
// Find new animals (status == 0)
NSFetchRequest *newAnimalsRequest = [NSFetchRequest fetchRequestWithEntityName:@"Animal"];
newAnimalsRequest.predicate = [NSPredicate predicateWithFormat:@"status == 0"];
NSError *error;
NSArray *newAnimalsArray = [self.photoDatabase.managedObjectContext.parentContext executeFetchRequest:newAnimalsRequest error:&error];
if ([newAnimalsArray count]) NSLog(@"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
if (error) NSLog(@"fetchError: %@", error);
// Get the existing animals from the server
NSArray *parsedDownloadedAnimalsByPhoto = [self downloadedAllAnimalsFromWeb];
// In the parent context, insert downloaded animals into core data
for (NSDictionary *downloadedPhoto in parsedDownloadedAnimalsByPhoto) {
[Photo photoWithWebDataInfo:downloadedPhoto inManagedObjectContext:self.photoDatabase.managedObjectContext.parentContext];
// table will automatically update due to NSFetchedResultsController's observing of the NSMOC
}
// Upload the new animals if there are any
if ([newAnimalsArray count] > 0) {
NSLog(@"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
for (Animal *animal in newAnimalsArray) {
// uploadAnimal returns a number that lets us know if it was accepted by the server
NSNumber *unique = [self uploadAnimal:animal];
if ([unique intValue] != 0) {
animal.unique = unique;
// uploadThePhotosOf returns a success BOOL if all 3 uploaded successfully
if ([self uploadThePhotosOf:animal]){
[self.photoDatabase.managedObjectContext performBlock:^{
animal.status = [NSNumber numberWithInt:1];
}];
}
}
}
}
[self.photoDatabase.managedObjectContext.parentContext save:&error];
if (error) NSLog(@"Saving parent context error: %@", error);
[self performUpdate];
// Turn the activity indicator off and replace the sync button
dispatch_async(dispatch_get_main_queue(), ^{
// Save the context
[self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
if (success)
{
NSLog(@"Document was saved");
[self.photoDatabase.managedObjectContext processPendingChanges];
} else {
NSLog(@"Document was not saved");
}
}];
[self.spinner stopAnimating];
self.navigationItem.leftBarButtonItem = self.syncButton;
});
// Here it skips to the notification I got from saving the context so I can MERGE them
}
- (NSNumber *)uploadAnimal:(Animal *)animal
{
NSURL *uploadURL = [NSURL URLWithString:@"index.php" relativeToURL:self.remoteBaseURL];
NSString *jsonStringFromAnimalMetaDictionary = [animal.metaDictionary JSONRepresentation];
NSLog(@"JSONRepresentation of %@: %@", animal.namestring, jsonStringFromAnimalMetaDictionary);
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
[request setPostValue:jsonStringFromAnimalMetaDictionary forKey:@"newmeta"];
[request startSynchronous];
NSError *error = [request error];
NSString *response;
if (!error) {
response = [request responseString];
NSNumber *animalUnique = [(NSArray *)[response JSONValue]objectAtIndex:0];
return animalUnique;
} else {
response = [error description];
NSLog(@"%@ got an error: %@", animal.namestring, response);
return [NSNumber numberWithInt:0];
}
}
- (BOOL)uploadThePhotosOf:(Animal *)animal
{
NSURL *uploadURL = [NSURL URLWithString:@"index.php" relativeToURL:self.remoteBaseURL];
int index = [animal.photos count];
for (Photo *photo in animal.photos) {
// Name the jpeg file
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
NSString *imageServerPath = [NSString stringWithFormat:@"%lf-Photo.jpeg",timeInterval];
// Update the imageServerPath
photo.imageURL = imageServerPath;
NSData *photoData = [[NSData alloc] initWithData:photo.image];
NSString *photoMeta = [photo.metaDictionary JSONRepresentation];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
[request addPostValue:photoMeta forKey:@"newphoto"];
[request addData:photoData withFileName:imageServerPath andContentType:@"image/jpeg" forKey:@"filename"];
[request setUploadProgressDelegate:self.progressView];
[request startSynchronous];
NSLog(@"%@ progress: %@", animal.namestring, self.progressView.progress);
NSString *responseString = [request responseString];
NSLog(@"uploadThePhotosOf:%@ photo at placement: %d has responseString: %@", animal.namestring, [photo.placement intValue], responseString);
SBJsonParser *parser= [[SBJsonParser alloc] init];
NSError *error = nil;
id jsonObject = [parser objectWithString:responseString error:&error];
NSNumber *parsedPhotoUploadResponse = [(NSArray *)jsonObject objectAtIndex:0];
// A proper response is not 0
if ([parsedPhotoUploadResponse intValue] != 0) {
photo.imageid = parsedPhotoUploadResponse;
--index;
}
}
// If the index spun down to 0 then it was successful
int success = (index == 0) ? 1 : 0;
return success;
}
- (NSArray *)downloadedAllAnimalsFromWeb
{
NSURL *downloadURL = [NSURL URLWithString:@"index.php" relativeToURL:self.remoteBaseURL];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:downloadURL];
[request setPostValue:@"yes" forKey:@"all"];
request.tag = kGetHistoryRequest;
[request startSynchronous];
NSString *responseString = [request responseString];
NSLog(@"downloadedAllAnimalsFromWeb responseString: %@", responseString);
SBJsonParser *parser= [[SBJsonParser alloc] init];
NSError *error = nil;
id jsonObject = [parser objectWithString:responseString error:&error];
NSArray *parsedDownloadedResponseStringArray = [NSArray arrayWithArray:jsonObject];
return parsedDownloadedResponseStringArray;
}
- (void)performUpdate
{
NSManagedObjectContext * context = self.photoDatabase.managedObjectContext.parentContext;
NSSet * inserts = [context updatedObjects];
if ([inserts count])
{
NSError * error = nil;
NSLog(@"There were inserts");
if ([context obtainPermanentIDsForObjects:[inserts allObjects]
error:&error] == NO)
{
NSLog(@"BAM! %@", error);
}
}
[self.photoDatabase updateChangeCount:UIDocumentChangeDone];
}
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.photoDatabase.managedObjectContext.parentContext];
NSLog(@"userInfo from the notification: %@", [notification userInfo]);
// Main thread context
NSManagedObjectContext *context = self.fetchedResultsController.managedObjectContext;
SEL selector = @selector(mergeChangesFromContextDidSaveNotification:);
[context performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
NSLog(@"ContextDidSaveNotification was sent. MERGED");
}
#pragma mark - Table view data source
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"EntryCell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"EntryCell"];
}
// Configure the cell here...
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = animal.namestring;
if (([animal.numberofanimals intValue] > 0) && animal.species) {
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@s", animal.species];
} else {
cell.detailTextLabel.text = animal.species;
}
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
// be somewhat generic here (slightly advanced usage)
// we'll segue to ANY view controller that has a photographer @property
if ([segue.identifier isEqualToString:@"newAnimal"]) {
NSLog(@"self.photodatabase");
[(NewMetaEntryViewController *)[segue.destinationViewController topViewController] setPhotoDatabaseContext:self.photoDatabase.managedObjectContext];
} else if ([segue.destinationViewController respondsToSelector:@selector(setAnimal:)]) {
// use performSelector:withObject: to send without compiler checking
// (which is acceptable here because we used introspection to be sure this is okay)
[segue.destinationViewController performSelector:@selector(setAnimal:) withObject:animal];
NSLog(@"animal: %@ \r\n indexPath: %@", animal, indexPath);
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30;
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return nil;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
NSLog(@"header for section called for section: %d", section);
NSLog(@"fetchedResultsController sections: %@", self.fetchedResultsController.sections);
CGRect headerRect = CGRectMake(0, 0, tableView.bounds.size.width, 30);
UIView *header = [[UIView alloc] initWithFrame:headerRect];
UILabel *headerTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, tableView.bounds.size.width - 10, 20)];
if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:0]) {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:0];
} else if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:1]) {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:1];
} else {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:2];
}
headerTitleLabel.textColor = [UIColor whiteColor];
headerTitleLabel.font = [UIFont fontWithName:@"AmericanTypewriter" size:20];
headerTitleLabel.backgroundColor = [UIColor clearColor];
headerTitleLabel.alpha = 0.8;
[header addSubview:headerTitleLabel];
return header;
}