table cells get duplicated when I use EGOTableViewPullRefresh - objective-c

I'm working on a RSS Reader using this tutorial. All table cells data come from a NSMutableArray instance (_allEntries). Then I import
EGOTableViewPullRefresh and add [self refresh] in -(void)reloadTableViewDataSource (self.refresh is a method to populate data of allEntries).
Then pull to refresh works but cells got duplicated every time I refresh. I tried to solve it in two ways.
When download data from internet, add if (![_allEntries containsObject:entry]) before [_allEntries insertObject:entry atIndex:insertIdx] but it didn't work, maybe I should use entry.title or some other attribute in the object to compare but it's not effective.
Like what I did in -viewDidLoad, add self.allEntries = [NSMutableArray array], but I don't know where should I put this line.
Is there anyone who can give me a direction?
[EDIT]
There's no too much logic in viewDidLoad, just
self.allEntries = [NSMutableArray array];
self.queue = [[NSOperationQueue alloc] init]; //add download&parse operation to a queue
self.feeds = [self getFeeds]; //load feeds from local file
And I put [self refresh] in reloadTableViewDataSource, the first time I open my app, there's nothing showed in the tableview. Then I pull to refresh, it works. Then pull to refresh again, it got duplicated.This is the "refresh" method.
- (void)refresh {
for (NSString *feed in _feeds) {
NSURL *url = [NSURL URLWithString:feed];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[_queue addOperation:request];
}
}
I want to rebuild the array so I write self.allEntries = [NSMutableArray array] again but it turns out "Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (140)". So as mentioned, I really get confused about where should I put this line.Thx~~

The logic you have in viewDidLoad that builds your array should be moved to its own method (reloadTableViewData), and then you would just call that method in viewDidLoad.
[self reloadTableViewData];
You would also call that same method when you do the pull to refresh.
Make sure you are rebuilding that array and not just adding objects to the existing one.

Related

How to modify CloudKit Reference Lists

I can modify the Reference List attribute of my CKRecord in the dashboard without problems, but how can I modify it programmatically?
I currently try modifying it as a NSArray. It does not give me any sort of error but even though the array content is fine, the attribute does not get set.
Also the documentation on reference lists is either well-hidden or non-existent.
CKReference *reference = [[CKReference alloc] initWithRecord:connectionRecord action:CKReferenceActionNone];
NSMutableArray *list_a = [record_a[#"connections"] mutableCopy];
if (!list_a) list_a = [NSMutableArray array];
[list_a addObject:reference];
record_a[#"connections"] = list_a;
[publicDatabase saveRecord:record_a completionHandler:^(CKRecord *artworkRecord, NSError *error){
if (!error) {
// Insert successfully saved record code
}
else {
// Insert error handling
}
}];
I am thankful for any ideas or suggestions.
Turns out that one has to use CKModifyRecordsOperation to modify existing CKRecords.

NSManagedContext issue fetching from context multiple times

I have two view controllers. One a searchResults tableview controller (VC1) where the user see a list of rows matching a selection criteria and other ViewDetail (tableview controller) (VC2) where the user sees Detail for the chosen row of VC1. The info required to fetch detail for the chosen row along with the managedContext reference are passed from VC1 to VC2 in the prepareforsegue method of VC1 by setting the relevant properties of VC2. During my test, I switched between (using the navigation controller back button) VC1 and VC2 each time selecting a different row on VC1 to see the detail of a different item. This works normally for 7-15 times of switching but crashes suddenly after some attempts of switching. I have investigated this as far as I could but stuck without a solution and hence posting this. Please help. The error is that a particular Array is out of bounds for index 0. While I understand however, I do not expect this array which is populated by results of a fetch request to be empty. Hence I suspect that there is something wrong with the managedcontext. Snippet of code from VC2 is provided
//All this is in ViewDidLoad of VC2 App crashes at the last line of this snippet. trying to get an object at index 0 which is non existent but should not be ...
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#" cameraid == %#",(NSNumber *)self.selectedCameraid];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Cameras"
inManagedObjectContext:self.managedContext];
//Configure core data request
[request setEntity:entity];
[request setPredicate:predicate];
//Execute request
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[self.managedContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"Some error in fetching results");
}
NSLog(#"Mutable fetch results data %#",mutableFetchResults);
self.resultsArray = mutableFetchResults;
Cameras *rowdata = [self.resultsArray objectAtIndex:0]; //Cameras is a managed object
After a bit of further digging I found the issue... I am type casting a string as NSNumber (NSNumber *)self.selectedCameraid in the above code. Apparently this is what has been causing the crash. I converted the NSString object to a NSNumber object using NSNumberformatter and everything seems to work fine. I realised it is not safe to type cast objects in this manner.

iCloud NSMetadata query results are blank

I've been working on adding icloud to my project (which is quite a pain in the buns) and I'm able to save and remove files, but I can't get a list of the files stored in iCloud. I've tried solutions from about 10 different websites (including the Apple documentation). Whenever I call [self.query startQuery]; everything seems to be working: The correct methods get called, the methods execute exactly as they should. Then when I ask for an nsarray of the files in my app's iCloud Documents directory I get two parenthesis with nothing between them (when I view in NSLog): File List: ( ). I know for a fact that there are many different documents of all shapes, extensions, sizes, and names in my app's iCloud Documents directory because I've been using the iCloud Developer site to check if things are working. My first method to setup the query is as follows:
- (void)syncWithCloud {
self.query = [[NSMetadataQuery alloc] init];
NSURL *mobileDocumentsDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
[query setSearchScopes:[NSArray arrayWithObjects:NSMetadataQueryUbiquitousDocumentsScope, nil]];
//[query setPredicate:[NSPredicate predicateWithFormat:#"%K LIKE '*'", NSMetadataItemFSNameKey]];
[query setPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%%K like \"%#*\"", [mobileDocumentsDirectoryURL path]], NSMetadataItemPathKey]];
//Pull a list of all the Documents in The Cloud
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(processFiles:)
name:NSMetadataQueryDidFinishGatheringNotification object:self.query];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(processFiles:)
name:NSMetadataQueryDidUpdateNotification object:self.query];
[self.query startQuery];
}
The process files method provided by Apple is next:
- (void)processFiles:(NSNotification*)aNotification {
NSMutableArray *discoveredFiles = [NSMutableArray array];
//Always disable updates while processing results.
[query disableUpdates];
//The query reports all files found, every time.
NSArray *queryResults = [query results];
for (NSMetadataItem *result in queryResults) {
NSURL *fileURL = [result valueForAttribute:NSMetadataItemURLKey];
NSNumber *aBool = nil;
// Don't include hidden files.
[fileURL getResourceValue:&aBool forKey:NSURLIsHiddenKey error:nil];
if (aBool && ![aBool boolValue])
[discoveredFiles addObject:fileURL];
}
//Update the list of documents.
[FileList removeAllObjects];
[FileList addObjectsFromArray:discoveredFiles];
//[self.tableView reloadData];
//Reenable query updates.
[query enableUpdates];
NSLog(#"File List: %#", FileList);
}
Why doesn't this give me a list of files or at least some kind of data? Am I defining the NSMetadata query wrong, maybe my predicate isn't formatted right? I know I'm doing something wrong because there's no way iCloud could be this complicated (or could it?).
Thanks for the help in advance!
Edit #1: I am continuing to try different approaches to this problem. Using one of the answers below I have changed the predicate filter as follows:
[query setPredicate:[NSPredicate predicateWithFormat:#"NSMetadataItemFSNameKey LIKE '*'"]];
I have also added the following lines before the [query enableUpdates] call:
for (NSMetadataItem *item in query.results) {
[FileList addObject:[item valueForAttribute:NSMetadataItemFSNameKey]];
}
In the processFiles method, I've tried placing all of the code on the background thread, but this makes no difference - as a matter of fact, when the code is not executed on the background thread FileList gives me this (null) instead of this ( ).
Could my problem have to do with thread management or memory allocation? Please note that I am using ARC.
Edit #2: The FileList variable is an NSMutableArray defined in my #interface and initialized in the -(id)init method before calling the processFiles method.
Edit #3: When testing my code with breakpoints I found that the following for-loop never gets run through - not even once. This leads me to believe that:
A. The proper directory isn't being connected with
B. iCloud can't see the files in the directory
C. My NSMetadataQuery isn't being setup properly
D. Something completely different
Here's the code that starts the for-loop which never gets run:
NSArray *queryResults = [query results];
for (NSMetadataItem *result in queryResults) {
Since you already set the search scope correct, there's no need to use special filters in the predicate.
Just use:
query.predicate = [NSPredicate predicateWithFormat:#"NSMetadataItemFSNameKey == '*'"];
And to get the array use:
NSMutableArray *array = [NSMutableArray array];
for (NSMetadataItem *item in query.results) {
[array addObject:[item valueForAttribute:NSMetadataItemFSNameKey]];
}
I've solved my problem. Getting the list of files in iCloud was just a matter of correctly defining, allocating, and initializing properties. SAE's answer and this SO posting helped me solve my problem and create this GitHub Repo called iCloud Document Sync. The iCloud Document Sync class simplifies the whole iCloud Document storage process down to a few lines of code. The commit linked here fixes the issues from my question.

Correct way to multithread in objective-c?

I have a UITableView which displays images. Every cell has an image and every time a cell loads, I call a selector (from the cellForRowAtIndexPath) in the background like this:
[self performSelectorInBackground:#selector(lazyLoad:) withObject:aArrayOfData];
The only problem is that sometimes I get a crash (because I am changing data in the background while it's trying to be read elsewhere). Here's the error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <CALayerArray: 0xce1a920> was mutated while being enumerated.'
When updating the data in the background, should I move it to the main selector and change it? Or should I call the #selector() differently?
Thanks!
If you can leave the operation on the main thread and have no lagginess nor problems you are done.
However: Let's assume you've already done that and encounter problems. The answer is: don't modify the array in the lazy load. Switch to the main thread to modify the array. See Brad's answer here:
https://stackoverflow.com/a/8186206/8047
for a way to do it with blocks, so you can send your objects over to the main queue (you should probably also use GCD for the call to the lazy load in the first place, but it's not necessary).
You can use #synchronized blocks to keep the threads from walking over each other. If you do
#synchronized(array)
{
id item = [array objectAtIndex:row];
}
in the main thread and
#synchronized(array)
{
[array addObject:item];
}
in the background, you're guaranteed they won't happen at the same time. (Hopefully you can extrapolate from that to your code—I'm not sure what all you're doing with the array there..)
It seems, though, like you'd have to notify the main thread anyway that you've loaded the data for a cell (via performSelectorOnMainThread:withObject:waitUntilDone:, say), so why not pass the data along, too?
Given the term 'lazy load' I am assuming that means you are pulling your images down from a server. (If the images are local then there is really no need for multithreading).
If you are downloading images off a server I would suggest using something along these lines (using ASIHTTPRequest)
static NSCache *cellCache; //Create a Static cache
if (!cellCache)//If the cache is not initialized initialize it
{
cellCache = [[NSCache alloc] init];
}
NSString *key = imageURL;
//Look in the cache for image matching this url
NSData *imageData = [cellCache objectForKey:key];
if (!imageData)
{
//Set a default image while it's loading
cell.icon.image = [UIImage imageNamed:#"defaultImage.png"];'
//Create an async request to the server to get the image
__unsafe_unretained ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:imageURL]];
//This code will run when the request finishes
[request setCompletionBlock:^{
//Put downloaded image into the cache
[cellCache setObject:[request responseData] forKey:key];
//Display image
cell.icon.image = [UIImage imageWithData:[request responseData]];
}];
[request startAsynchronous];
}
else
{
//Image was found in the cache no need to redownload
cell.icon.image = [UIImage imageWithData:imageData];
}

NSFetchedResultsController predicate is a little too real time...?

So I've got an NSFetchedResultsController that I activate on ViewDidLoad with the managedobjectcontext that has been passed on from the appdelegate on load.
I put a predicate on some field let's call it "sectionNumber" and say it needs to equal 1 in my predicate.
NSFetchResultsController works fine until I add a new object to the MOContext...
I use MyMO *newObj = [NSEntityDescription insertnewentity]...
start filling the different fields
[newobj setName:#"me"];
[newobj setAge:12];
etc...
Once I put [newobj setSectionNumber:1] - it finds it at that very instant and causes the app to crash with different weird errors that all lead to EXC_BAD_ACCESS.
All of this happens on the MAIN THREAD.
Any ideas why? How could one get around that?
UPDATE:
It only happens when I use my saveMOC method which is called at the end of an NSXMLParser specific thread I spawned off. The saveMOC is called on a successful parse with the [self performSelectorOnMainThread].... If i just added the extra managedobject via ViewDidLoad (just to check weather this is related somehow to to threading) the problem does NOT occur.
So it's obviously something with the new thread even tho the selector should have been run on the main thread.
UPDATE #2:
This is my spawned thread for the XML Parser:
-(void)getAndParseXML {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
DLog(#"Online storage");
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:theUrl];
XMLTranslator *translator = [[XMLTranslator alloc] init];
[parser setDelegate:translator];
if ([parser parse]) {
//success call MOC change routine on main thread
DLog(#"success parsing");
[self performSelectorOnMainThread:#selector(saveMOC:) withObject:translator waitUntilDone:NO];
} else {
DLog(#"error: %#",[parser parserError]);
}
[parser setDelegate:nil];
[parser release];
DLog(#"XML parsing completed");
[pool release];
}
Then this is my saveMOC:
-(void)saveMOC:(XMLTranslator*)translator {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
for (NSDictionary *dict in [translator retrievedData]) {
APost *newPost = [NSEntityDescription insertNewObjectForEntityForName:#"APost"
inManagedObjectContext:managedObjectContext];
//parse time into NSDate
[newPost setTime:[formatter dateFromString:[dict objectForKey:#"time"]]];
//author, category, description
[newPost setAuthor:[dict objectForKey:#"author"]];
[newPost setCategory:[dict objectForKey:#"category"]];
[newPost setContent:[dict objectForKey:#"description"]];
//create a post id so that the validation will be alright
[newPost setPostid:[NSNumber numberWithInteger:[[dict objectForKey:#"postid"] integerValue]]];
[newPost setSectionDesignator:sectionDesignator];
}
This saveMoc method continues and has a [managedobjectcontext save:&error] and more... but it's not relevan to our case as my method crashes I've discovered thru commenting one line after another at the point where I set the sectionDesignator since it equals to the current predicate in my NSFetchedResultsController.
The problem is most likely in the NSFetchedResultsController delegate methods or the lack thereof.
When you add a new object to any context and then save the context, that changes the persistent store which triggers the FRC on any thread to begin an update of the tableview. All the index paths change, especially if you set a value for an attribute used as a sectionNameKeyPath. If the table ask for a cell during the update, it will cause a crash because the table can ask for a cell at a index path rendered invalid by the insertion of the new managed object.
You need to make sure you implement the FRC's delegate methods and that you send the table a beginUpdate message to freeze it while the FRC changes all its index paths.
I am sorry to admit that the problem this whole time was releasing an array that held the sort descriptors in the fetch request that was used within the FRC.
Looking at alot of examples I released that array tho unlike the examples I created my array with [NSArray arrayWithObject:.............];
So there was an overrelease each time the fetch request was accessed more than once.
Feel free to close this. Thank you everybody for your help. I discovered this when peter wrote to look at the whole stack and not just one frame.
I have further analyzed the problem and have realized it occurs inside the loop.
I have further understood that it only happens when I have more than one object, meaning that one FRC takes over after an object insertion into MOC and tries to come back to the for loop, it tries to access an object or a reference that's not there. I haven't found what object causes it and how to retain it properly.
Any suggestions?
Consider the following:
for (int i=0; i<2; i++) {
NSLog(#"%i",i);
APost *thePost = [NSEntityDescription insertNewObjectForEntityForName:#"HWBPost" inManagedObjectContext:managedObjectContext];
[thePost setCategory:#"CAAA"];
[thePost setContent:#"SSSSSS"];
[thePost setSectionDesignator:sectionDesignator];
}
If I change the for loop to i<1 meaning it only runs once, the app does NOT crash. As soon as it is more than one object insertion the app crashes.