UIActivityIndicatorView Trouble - iphone

This is a bit of a silly question but I don't know how to get around it.
I am making an iphone app and I am just trying to display a progress wheel while my UITableView is loading. I try to test if the view is loading but I get an error:
'request for member 'loading' is something not a structure or a union'. So, I'm not sure how I am supposed to test for when I should show the wheel. maybe I have mistyped something? I don't know, but I am getting pretty frustrated with this silly problem. So, any help would be appreciated. Thanks!
- (void) updateWheel {
//curtable is a uitableView
//wheel is a uiactivityIndicatorView
if (!curTbl.loading) { //THE ERROR IS FOR THIS LINE
[wheel stopAnimating];
} else {
[wheel startAnimating];
}
}

There is no loading property of a UITableView, which would be why you are getting a compile error on that line of code. As DarkDust said, you use a data source protocol to feed data into the cells of a table view. As views come into view, the system requests the cell and data via this delegate, and you provide the cell formatting and data in these protocol methods.

I'm not sure what you mean with "while my UITableView is loading". Do you mean while it is reloading data from the data source ? Because the UITableView is not involved any loading (and has no member "loading".
If you do [myTableView reloadData] then it is querying its dataSource. See the documentation of UITableViewDataSource protocol.
So YOU are responsible for loading data and then informing the table view that something in the data source has changed, and thus you should know when you are still loading data for your data source implementation :-)

Related

Perform time consuming tasks inside UITableViewCell, pausing on scrolling

I have TableView with customs cell representing events. It looks very close to first and third image here.
As you can see (sorry for small resolution) on some events there are photos of friends that are going to participate.
Unfortunately information about friends is not loaded with other information about events.
So after I got list of events I can make request to load list of friends that are going to participate in each event.
Right now I use code like
class EventCell : UITableViewCell {
var eventForCell : Event? {
didSet {
eventTitleLabel.text = eventForCell.title
eventDateLabel.text = eventForCell.date
presentFriends(eventID : eventForCell.id)
}
}
func presentFriends(eventID : Int) {
//searching for friends for event with specific eventID
.......
.......
//for every friend found adding UIImageView in cell
for friend in friendsOnEvent {
let avatar = UIImageView(....)
self.addSubview(avatar)
}
}
}
This code works but photos are not presented in smooth way. Also if you scroll list fast they start to blink. Maybe it is even not necessary to load them if user scrolls fast list of events. So I have two questions:
How can I make smooth scrolling experience taking in consideration that presenting friends for every event can take time and sometimes it finishes after cell was scrolled away.
If I had loaded list of events and already presenting cells with them. How can I update those cells after I get information about friends that are going to participate?
When user is scrolling and I am creating async tasks to display some images in cell I think I should use weak reference to self and maybe check it not to equal nil so task would be canceled if cell is not visible now. How should it be done?
Update:
I found info about tableView(_:prefetchRowsAt:) method inside UITableViewPrefetchingDataSource protocol, should I use it for this case? Maybe someone has some experience with it?
1. (Re)creating a view objects during cellForRowAt is generally a bad practice. From the screenshot I assume that there is a limit to how many avatars are there on a single cell - I would recommend creating all the UIImageView objects in the cell initializer, and then in presentFriends just set images to them, and either hide the unused ones (isHidden = true) or set their alpha to 0 (of course, don't forget to unhide those that are used).
2. If you are using SDWebImage to load images, implement prepareForReuse and cancel current downloads to get a bit of performance boost during scrolling, and prevent undefined behaviour (when you try to set another image while the previous one was not yet downloaded). Based on this question, this one and this one I would expect something like:
override func prepareForReuse() {
super.prepareForReuse()
self.imageView.sd_cancelCurrentImageLoad()
}
Or you can use [this gist][4] for an inspiration.
P.S.: Also, you will have to count with some blinking, since the images are downloaded from web - there will always be some delay. By caching you can get instantly those that were already downloaded, but new ones will have delay - there is no way to prevent that (unless you preload all the images that can appear in tableView before presenting tableView).
P.S.2: You can try to implement prefetching using [UITableViewDataSourcePrefetching][6]. This could help you out with blinking caused by downloading the avatars from web. This would make things a bit more complicated, but if you really want to remove that blinking you will have to get your hands dirty.
First of all, as you can see from the documentation, prefetchRowsAt does not give you a cell object - thus you will have to prefetch images to your model object instead of simply using sd_setImage on the UIImageView object at a given cell. Maybe the aforementioned gist would help you out with downloading images to model.
Now also as the documentation states:
Loading Data Asynchronously
The tableView(_:prefetchRowsAt:) method is not necessarily called for every cell in the table view. Your implementation of tableView(_:cellForRowAt:) must therefore be able to cope with the following potential situations:
Data has been loaded via the prefetch request, and is ready to be displayed.
Data is currently being prefetched, but is not yet available.
Data has not yet been requested.
Simply said, when you are in cellForRowAt, you cannot rely on prefetchRowsAt being called - it might have been called, and it might not have been called (or maybe the call is in progress).
Documentation continues to suggest using Operation as a backing tool for downloading the resources - prefetchRowsAt could create Operation objects that would be responsible for downloading avatars for a given row. cellForRowAt can then ask the model for the Operation objects for the given row - if there are not any, it means that prefetchRowsAt was not called for that row, and you have to create Operation objects at that point. Otherwise you can use the operations created by prefetchRowsAt.
Anyway, if you decide that it is worth it, you can use this tutorial as an inspiration for implementing prefetching.
You can use UITableViewDataSourcePrefetching as you mentioned.
It's a protocol that calls your prefetch data source when some cells are going to be displayed but are not on the screen yet.
This way you can prepare all the resources that takes time to load before they are presented.
You just have to implement:
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])
and fetch the data related to the cells from all of the indexpaths.
Beware it's only available since iOS10.
I personally use AlamofireImage with a cache so images that have already been downloaded aren't fetched twice, there's plenty of alternatives but it's a good practice to use cached images on this kind of scenario.

How do you know when the UITableView is done updating it's view?

So I implement UITableViewDataSource protocol using an NSFetchedResultsController.
I then modify the contents of the Core Data base and the NSFetchedResultsController then update the tableView..
Is there anyway to know when the tableView has done reloading the data?
We have a complex data model that is caching against a REST implementation, and it's difficult to determine if we need to get more data to fill up the screen (because the screen might be using complex filters against the raw data loaded). Also, the UITableViewCell objects are NOT guaranteed to be the same height.
The easy answer was to simply dump the data to core data, and use an NSFetchedResultsController to serve the data to the UITableView.
Here is how it works:
We added a tableview.tableFooterView that displays our "data loading" message.
Loading up a batch of data from the REST API and then using it update our Core Data objects.
This triggers the controllerDidChangeContent method to trigger, which then triggers a tableView reloadData (eg:)
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView reloadData];
}
We then basically then "Check" to see if the Footer is still visible 250ms later by calling a method like:
[self performSelector:#selector(checkIfFooterViewIsVisible) withObject:nil afterDelay:0.25];
- (void)checkIfFooterViewIsVisible
{
BOOL viewVisible = CGRectIntersectsRect(self.tableView.bounds,self.tableView.tableFooterView.frame);
if (viewVisible)
{
[self getMoreData];
}
We also check to see if the Footer is in view everytime we scroll.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self checkIfFooterViewIsVisible];
}
If we load the "last batch" of data, we actually just remove the tableViewFooter view object from the table (so it can't ever be visible).
So the answer is cool, because we can figure out if we need to pull more data, only if the user "needs" it. Either because there wasn't much visible data in the first batch, or because they have scrolled down and want more data. Checking the current placement of the tableFooterView lets us know if we have "painted" enough data on the screen.
The PROBLEM is - can I get rid of the:
[self performSelector:#selector(checkIfFooterViewIsVisible) withObject:nil afterDelay:0.25];
If we set the delay too fast, then UITableView doesn't have time to update the screen (and change the footer's position). If we update too slowly, then it feels like the app "stutters" as it loads data, and can load data slower than it could. But different iOS devices (and different network coverage) are going to mean slightly different timings, so the "let the UITableView update and check a bit later" works most of the time, but I feel like this could work smoother.
Is there anyway (maybe by overloading UITableView) that we can determine that the UITableView is "done" loading UITableViewCell objects? (at least until the next time the scroll moves?). Adding in this weird delay works well, but it would work cleaner if we knew definitively that the [tableview reloadDate] operation has completed.

How to update UITableView at run time, after the view is loaded

I have read several articles about UITableView, including the official doc and some on SO. But my situation seems to be different.
I want to update the Table each time the view loaded. And I must fetch the data using HTTP request.
What I got now is:
When enter the table view, I should use a non-synchronous HTTP request to update the data. Because I don't want the main thread to wait. One place to do that, is in the tableView:cellForRowAtIndexPath: method. So I return 0 for no data exist at the beginning.
When I get the HTTP respond, I update rows on main thread using beginUpdates endUpdates insertRowsAtIndexPaths:withRowAnimation:
And I must update the "Data Source" at the same time, but how to do that?
Or should I make a daemon thread and update my data every once in a while? So that the data will be ready when TableView is loaded.
You would do it like this:
Have a boolean or some variable where you can reliably detect whether you have all the data.
In viewWillAppear, reset everything. Start loading your data.
If you don't have the data yet, you only display one section with one cell (a placeholder cell that reads "Loading..." and shows a spinner, for instance).
Once the data is completely loaded, you set the bool or whatever.
Call [self.tableView reloadData];
In all of your UITableViewDataSource methods you would need to check whether you've got the data already or not. If not, you return the placeholder data.
[yourtablename reloadData]; will help you relaod the data in the tableview, You can call this once you get the response from your server
I'm not sure there's a "best method" for what you're trying to accomplish here. I would suggest trying the method you have, and seeing if it provides an adequate user experience (whatever that means to you) and if it doesn't, try something else. I would definitely suggest having some sort of "loading" indicator while the table is empty and waiting for http response.
In terms of your question about the "data source", the data source of a UITableView is simply an object that implements the UITableViewDataSource protocol which you can read about here. Often times, you will have XCode set up a UITableViewController object which will act as both delegate and data source to your table view. How you actually store your data is up to you. The data source protocol simply provides the methods by which a table view will "ask" for the data it needs to load.

Problems getting an xml feed parsed and loaded into a tableview using delegates

Very new to programming for iOS and Cocoa so please take it easy on me as I try to wrap my brain around the following. I'm trying to display a tableview populated from an XML feed as the opening screen of my app. I've tried to consume the XML from inside my AppDelegate using the ApplicationDidFinishLaunching method (and then making my AppDelegate a delegate for the XML parser which I access using a NSUrlConnection and its delegate methods) but I can't figure out how to take the parsed XML file and pass it to a tableviewcontroller which can then use it as the datasource for a tableview. When I do try, I always get a blank tableview.
I've written the code a few times and nothing seems to work.. I'll post what I have here to show what I've got so far but I'm afraid its mostly vanilla AppDelegate with a few parser methods thrown in.. any pointers in the right direction would be super appreciated.
Thank you in advance!
Hmm, probably a bad idea to do the network call in the AppDelegate. Try to put all that code at the view controller level. Here's a brief structure of what I do (Since it's very similar)
View Controller listens to button events
Use ASIHTTPRequest to talk to your web service. Handles network really well, you can skip the NSURLConnection stuff.
Try to load your data source (an array?) with static values and see if they come up on the table view.
Parse the response from ASIHTTPRequest using NSXMLParser, and load the data you want into the static array you were using. More here.
Call [tableView reloadData] once you're done and the changes will reflect.
Did you specify a UITableViewDataSource for your table view and implement the two required methods?
tableView:cellForRowAtIndexPath:
and
tableView:numberOfRowsInSection:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html
When I get blank tables, this is what it is; I've forgotten to specify the data source

Disable animation of UITableView with NSFetchedResultsController when many rows are being changed

In my UIView I've got a UITableView (UITV) which is controlled by an NSFetchedResultsController (NSFRC). The UIView is inside a UINavigationController.
When the view is about to be loaded/displayed I start some background activities which fetch data from a remote server (JSON) and parse into Core Data.
The NSFRC is being called when the parsing is done and the threaded NSManagedObjectContext have been merged into the main context.
The problem is that sometimes many rows are being inserted to Core Data at once, a lot of table cells are being added and there is quite a delay from that the actual fetching and parsing is done, until the rows are being displayed.
Now I wonder if anyone knows of any solution to, for example:
hook up a spinner to some "fetched results controller inserted all its rows for this time" (or something) notification/delegate call to at least tell the user that "something is going to show up soon"?
Or might the best solution simply be to not initialize the NSFRC until the background fetching and processing is completed?
Thanks!
If I understand your question correctly, you may want to look into the NSFetchedResultsControllerDelegate methods, with documentation available here: http://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html
There are delegate methods available for pre changes with controllerWillChangeContent:, post changes with controllerDidChangeContent and during changes with didChangeSection: and didChangeObject.
I hope it helps!
Rog