why does "numberOrRowsInSection" get called mid-way through "viewDidLoad"? - iphone

BACKGROUND:
Within my viewDidLoad I am loading test calendar data (via EventKit) to the iPhone. I am noting that when the table view loads the first time it does not see this data.
When I look at the sequence of log messages I see that it goes something like this:
Logging here:
[AppointmentListController viewDidLoad] Starting
[AppointmentListController populateTestData] Populating the test data now
[AppointmentListController tableView:numberOfRowsInSection:] Number of rows = 0
[AppointmentListController viewDidLoad] Loading the existing calendar events
[AppointmentListController viewDidLoad] Ending
QUESTION(S)
Why does "numberOrRowsInSection" get called mid-way through "viewDidLoad"? I ask this as this is what I am seeing (is this what should happen?).
If this is normal that you can't be sure viewDidLoad will complete before numberOrRowsInSection, then this would imply you need a "[self.tableView reloadData]" at the end of your "viewDidLoad" method to make sure it does update?

The tableView:numberOfRowsInSection: method is likely called because you configured something on your table view (maybed you've added it to its superview) and it therefor needed to know its metrics. It does not magically trigger by itself out of the blue. Either make sure to prepare your data before you configure the UI elements or call reloadData near the end, but I'd find the former to be "cleaner".

This is perfectly normal....
Call [self.tableView reloadData]
; at the end of viewdidload

Related

reload table view : indexPath isn't reset ios 6

I'm making an iOS 6 program which downloads JSON data from a website and displays it in a table view. I added a pull to refresh method witch works fine. I can go in the settings view controller (secondViewController) and change the address, everything works. Then, I use the pull to refresh method and my tableView is reloaded. But if I reload 3 times AFTER changing the address, my app crashes. I get this error :
*** Terminating app due to uncaught exception 'NSRangeException',
reason: '*** -[__NSArrayM objectAtIndex:]: index 10 beyond bounds for empty array'
*** First throw call stack:
(0x1ca1012 0x10dee7e 0x1c430b4 0x3084 0xdd8fb 0xdd9cf 0xc61bb 0xd6b4b 0x732dd 0x10f26b0 0x229dfc0 0x229233c 0x2292150 0x22100bc 0x2211227 0x22bb333 0x22bb75f 0x1c60376 0x1c5fe06 0x1c47a82 0x1c46f44 0x1c46e1b 0x1bfb7e3 0x1bfb668 0x22ffc 0x1fbd 0x1ee5)
libc++abi.dylib: terminate called throwing an exception
What am I doing wrong ? And how can I fix that problem ? Thanks for your help !
The key design consideration that leaps out is that your retreiveData method is clearly updating the model (the citiesArray) asynchronously, which means that any interaction with the tableview while this is taking place may fail. You should never asynchronously update the actual citiesArray itself. The update to that array should happen in the main queue.
You should change retrieveData to not touch the existing citiesArray, but rather create and return a new array, and then, in the code you dispatch back to the main queue, only then replace the existing citiesArray and call reloadData, something like:
- (void)refresh:(UIRefreshControl *)refreshControl {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *newCitiesArray = [self retreiveData];
dispatch_async(dispatch_get_main_queue(), ^{
if (newCitiesArray) {
// you presumably only want to reload the data if `retrieveData` was successful
citiesArray = newCitiesArray
[myTableView reloadData];
}
[refreshControl endRefreshing];
});
});
}
Clearly, this will involve some changes to retrieveData, too, but hopefully that's self explanatory. If not, update your question with that code, and we can make further suggestions. But we really shouldn't need to go there, as I suspect you understand the change that needs to take place there.
There are other, more subtle issues you might want to tackle, too, such as thinking about whether you really want to use a global queue, which is concurrent (e.g. if you hit refresh while the previous refresh going, do you really want two queries going on concurrently with your server ... because you dispatch the updates back to the main queue you won't crash, but it's inefficient, you're not guaranteed the order that they'll complete, etc.). You might also want to use NSOperationQueue in which you can write code to permit the cancellation of previous requests, etc.
But all of this is a little complicated and is of secondary concern to your main issue, the crashing. Refactoring the retrieveData code to ensure you don't touch citiesArray, itself, as outlined above, should address that.

Problem showing tableView updates while parsing xml with AQXMLParser StreamingParser

I have a tableview which gets filled by a StreamingXMLParser (https://github.com/AlanQuatermain/aqtoolkit).
When I run my app, the tableView appears empty.
First I thought that my tableView didn't show anything until the async xml parser was ready, but if I start to scroll during the parsing the view is suddenly filled.
My code is based on ParserExample (https://github.com/AlanQuatermain/ParserExample) and the parser is called using the same method as the example:
[parser parseAsynchronouslyUsingRunLoop: [NSRunLoop currentRunLoop]
mode: NSDefaultRunLoopMode
notifyingDelegate: self
selector: #selector(parser:completedOK:)
context: NULL]
I think my problem is that my async task is taking up too much processing power.
Who has experience with the StreamingXMLParser and can help me out?
How can I fix my app so the tableView has more priority than the xml parsing ?
The problem can be simulated by downloading the example project and inserting
[NSThread sleepForTimeInterval:0.2];
After the [self.managedObjectContext save: NULL]; statement in the endPost method of the parserDelegate.
I've been able to circumvent the problem by calling the synchronous method [parser parse] via a small utility called MBProgressHUD Created by Matej Bukovinski.
The MBProgressHUD now calls the parsing method async & fixes my problem. (And gives a nice indicator).
I think the problem is in your parsing. Because when you parse some tags they have some empty or \n string so the first few have empty rows. One more thing that can happen is that the response from the server is coming too late. That's why it takes so much time.
You should place some break points and then check each data.

When are a methods GUI operations actually carried out?

I am working on a web-services data processing app and I am trying to make the app run as quickly as possible. When a certain 3 finger pan gesture is performed, I call a method that sends updated information off to the server to get a new batch of images to update the existing ones with.
So lets say there are 15 images in an array, I filter through them with a 2 finger gesture, and then if I want to change something about them, I can do the 3 finger gesture, and I get that same set back, just tweaked a bit (contrast/brightness, etc.).
Is what I want though is to be able to update the imageView that is displaying the images after the first image has been retrieved, so as to give the user a feel for what the rest in the series are going to look like. But no matter what I try, and no matter how many different threads I try and implement, I can't get the imageView to update before the entire download is complete. Once the batch download is done (which is handled on a separate thread) the imageView updates with the new images and everything is great.
The first step in the process is this:
if(UIGestureRecognizerStateEnded == [recognize state]){
[self preDownload:windowCounter Level:levelCounter ForPane:tagNumber];// Where this method is what gets the first image, and tries to set it to the imageView
[self downloadAllImagesWithWL:windowCounter Level:levelCounter ForPane:tagNumber]; //And this method goes and gets all the rest of the images
}
This is my preDownload method:
-(void)preDownload:(int)window Level:(int)level ForPane:(int) pane{
int guidIndex = [[globalGuids objectAtIndex:pane] intValue];
UIImage *img = [DATA_CONNECTION getImageWithSeriesGUID:[guids objectAtIndex:guidIndex] ImageID:counter Window:window Level:level];
if(pane==0){
NSLog(#"0");
[imageView3 setImage:img];
}else if(pane==1){
NSLog(#"1");
[imageView31 setImage:img];
}else if(pane==2){
NSLog(#"2");
[imageView32 setImage:img];
}else if(pane==3){
NSLog(#"3");
[imageView33 setImage:img];
}
}
So by separating this out into two different methods (there are no threads being implemented at this point, these methods are being called before all that) I was thinking that after the preDownload method completed, that the imageView would update, and then control would continue on down into the downloadAllImagesWithWL method, but that doesn't appear to be the case.
Am I missing something simple here? What can I do to update my GUI elements before that second method is through running?
You are right. However the viewn won't refresh until your code reaches runloop. You can do 2 things:
Make your downloadAllImagesWithWL method async, so it will return after you called it, your main thread reaches runloop, gui updates, and the download method will tell your logic through a callback when its done.
OR
A simplier hackier (and bad) solution would be to run runloop for some time before you call your download method. Something like this: [[NSRunloop currentRunLoop] runUnitlDate: [Date dateWithTimeIntervalSinceNow: 0.1]]; It will run runloop for 0.1 second.
When the image is set, the image view will mark itself as needing display. The actual display won't occur until the beginning of the next run loop. In OS X, you can use -display to draw the view immediately, but I don't think Apple created a public method to do this on iOS. However, if the next method simply creates the background thread, then it will return quickly and the display update will probably occur before the thread finishes.

How to Load an array into OpenFlow

I'm trying to implement openFlow in my project but I cant seem to get the images to show up on my uiview. What isnt clear to me is once I have the dictionary of image links, how do i tell AFOpenView that I want to use that dictionary object as my data source?
I've looked at the demo code and I see that when the flickr request finishes, he saves a copy of the dictionary results, counts them, and then tells OpenFlowView that there are x number of images, but what is never clear is how he tells OpenFlowView to use the dictionary with the results?
- (void)flickrAPIRequest:(OFFlickrAPIRequest *)inRequest didCompleteWithResponse:(NSDictionary *)inResponseDictionary
{
// Hold onto the response dictionary.
interestingPhotosDictionary = [inResponseDictionary retain];
int numberOfImages = [[inResponseDictionary valueForKeyPath:#"photos.photo"] count];
[(AFOpenFlowView *)self.view setNumberOfImages:numberOfImages];
}
See here: http://blog.objectgraph.com/index.php/2010/04/09/how-to-add-coverflow-effect-on-your-iphone-app-openflow/
This tutorial seems to suggest that you have to call the view's setImage method multiple times, once per image.
This tells me that the implementation is confusing and weird, but for this you have to blame the component's author.
The images are loaded on demand in the 'updateCoverImage:' method of AFOpenFlowView.m
'updateCoverImage:' calls 'openFlowView:requestImageForIndex:' in AFOpenFlowViewController.m, which uses interestingPhotosDictionary.
So, it is called on demand whenever an image needs to be loaded. It wraps an operation queue so the images are loaded outside the main thread.

NSFetchedResultsController not updating UITableView's section indexes

I am populating a UITableViewController with an NSFetchedResultsController with results creating sections that populate section headers and a section index. I am using the following method to populate the section index:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [fetchedResultsController_ sectionIndexTitles];
}
and now I've run into a problem. When I add a new element to the NSManagedObjectContext associated with the NSFetchedResultsController, the new element is saved and appropriately displayed as a cell in the UITableView ... except for one thing. If the new element creates a new SECTION, the new section index does not show up in the right hand margin unless I pop the UINavigationController's stack and reload the UITableViewController.
I have conformed to the NSFetchedResultsControllerDelegate's interface and manually invoke
[self.tableView reloadSectionIndexTitles];
at the end of both these delegate methods:
controller:didChangeSection...
controller:didChangeObject...
and while I can debug and trace the execution into the methods and see the reload call invoked, the UITableView's section index never reflects the section changes.
Again, the data shows up - new sections are physically visible (or removed) in the UITableView but the section indexes are not updated.
Am I missing something?
Looks like this is a bug we're all having. See http://iphonedevelopment.blogspot.com/2009/11/i-know-youre-tired-of-hearing-about.html for what looks to me like a fairly nasty too-many-lines-of-code solution. I went with this:
- (void)viewWillAppear:(BOOL)animated; {
// This is a dumb hack required by this bug: http://iphonedevelopment.blogspot.com/2009/11/i-know-youre-tired-of-hearing-about.html
[self.tableView reloadData];
}
It may be inefficient but unless you have reams and reams of data it probably won't do any harm. And it's only 1 line of code. So, when apple fixes their bug, you can easily take it out.
Question already 2 months old, but I ran into the same problem today. It seems like -reloadSectionIndexTitles is not working at all, so I tried a couple of potential hacks which of the following works for me:
#implementation UITableView (JKAdditions)
- (UIView *)indexView {
Class indexClass = NSClassFromString(#"UITableViewIndex");
for(UIView *subview in self.subviews){
if([subview isKindOfClass:indexClass]) return subview;
}
return nil;
}
- (void)reloadSectionIndexTitles {
UIView *indexView = [self indexView];
[indexView performSelector:#selector(setTitles:) withObject:[self.dataSource sectionIndexTitlesForTableView:self]];
[indexView setNeedsDisplay];
}
#end
I really have no idea if Apple would reject your App because of this hack, but it seems like the only option for me. Reloading the whole tableView is simply not what I want since I then have to deal with all kinds of animation problems.
I hope this helps anyone having the same problems!
To combine the accepted answer with Alex Reynolds's answer with the delay, just call reloadData with a delay that corresponds to the animation duration, so 0.4 or 0.3 seconds.
In my case, I stick the delayed method call into controller:didChangeSection:atIndex:forChangeType: (it's a Core Data app).
The result, when a section is added or deleted, is the standard animation of the cell, followed by the index being updated when the data is reloaded.
It's ugly and makes me cringe, but I am okay with the result. I also submitted a bug to Apple, #8589547.
Try putting it at the end of -controllerDidChangeContent:, somewhere after [self.tableView endUpdates].
Another thing I do (that works for me, can't guarantee it will work for you) is perform a selector after a very short delay, e.g.:
[self performSelector:(#selector(refreshSectionIndex)) withObject:nil afterDelay:0.2];
// ...
- (void) refreshSectionIndex {
[self.tableView reloadSectionIndexTitles];
}
Core Data and NSFetchedResultsController in particular seem buggy as hell, where delegate table view updates get out of sync with the fetched data, causing the application to crash. I really hope Apple is taking steps to fix the bugs in these frameworks in the 4.0 SDK. It's pretty frustrating.