I would like to know how people implement an asynchronous UIImageView loading, if you have it inside a custom UITableViewCell, I've seen quite some examples using GCD, subclassing UIImageView.. all of them must involves NSURLRequest and NSURLConnection. So how do people do this in a subclass of UITableViewCell that has an UIImageView in it?
Look at this category: SDWebImage. It's a image downloader for UIImageView. You place you're image view in your cell, and in the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method, just use:
[myImageView setImageWithURL:[NSURL URLWithString:#"example.com/myimage.jpg"]];
Here is a very good tutorial which explains asynchronous loading in general....
http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial
You can use the same idea to load your image view..
I think the simple and ideal way to do this is to do the following steps
Inside cellForRowAtIndexPath: check if the image is available locally load it, else fire off nsurl request to download it in a separate thread and show a loading indicator image.
On completion of request thread, store the image locally reload tableView.
Related
So, I've been implementing Automated testing for my iOS app and I've come across a strange issue.
With the testing framework I'm using (Frank), it attempts to Touch views/buttons/everything based on Accessibility labels. This works great, except for with UITableViews because of how they cache and reuse UITableViewCells. If I "delete" a cell from the table, the table caching system will flip the cell to Hidden. But it will still be there waiting to be reused, and my framework can still see it, which is causing issues.
So, Question: How can I force a UITableView to release all deleted/cached cells so they will no longer be part of the View hierarchy?
This trick might help:
while([tableView dequeueReusableCellWithIdentifier:cellId]!=nil);
Of course, it needs manually repeat it for every possible cellId.
Note, cached cells is not a part of view hierarchy.
One more option. If UITableViewController is not currently visible, then invocation of didReceiveMemoryWarning method releases all its UITableViewCells.
In the method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath, use this:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]`
then check if your cell is nil. If it is, allocate a new instance of the cell.
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
[cell setDelegate:self];
}
I have created a custom UITableViewCell using storyboards.
I am loading it like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
HJAddCell *cell = [tableView dequeueReusableCellWithIdentifier:#"addCell"];
// setting properties of cell
return cell;
}
Now I need to add some more customization programmatically during initialization. But the problem is that in my HJAddCell, the init methods are not called. What should I do so that the the init method is called or is there any other way to add more customization code to the custom cell class at init time with minimum effort.
Update: I need to add borders to UILabels. I didn't find any thing to do this in storyboard.
UIViews have two designated initializers: initWithFrame: and initWithCoder:. initWithCoder: is used if view is loaded from nib/storyboard, initWithFrame - if view is created programmatically.
Of course the init methods are not being called. You are not calling any init methods yourself. If you are registering HJAddCell's nib with the table view, the initialization method being called is initWithCoder. If you are using a nib, you should also ask yourself about why do you need to customize a custom UITableViewCell on the runtime. I mean, it's already a custom cell. You should create the cell with the properties you need to have.
How about adding a new method to HJAddCell called reset? Move the initialization code that needs to happen before each use into the reset method and call reset from your initializer.
Then you would call the reset method after dequeue-ing your cell.
I've read quite a few different ways to tackle the problem of setting the UITableViewCell color. Was just after a quick confirmation that the code/approach below is fine re a best practice / performance point of view?
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor = [UIColor redColor];
}
Some notes/questions associated with this:
Would this be the best re performance in the sense that it seems like this code will be called quite a lot? i.e. as opposed to if the background color was just set once for the cell design, say in IB (which I know doesn't quite work) see here
I see some sample code that loops through the various sub-views of a cell to set them, but this wouldn't normally be required? I've tried this code above and it seemingly works ok.
You are correct, this is the way to do it. The UITableViewCell documentation even explicitly states that background colors should be set in tableView:willDisplayCell:forRowAtIndexPath: as opposed to tableView:cellForRowAtIndexPath:. Don't fear, you are on the right path sir.
Wow, was that ever a mouthful. :)
We've already seen a good thread on customizing the more menu (table view) in a tab bar.
For our next trick … how might one add a background image to the table cells?
I thought I could get away with overriding tableView:cellForRowAtIndexPath: in my More Table View's Data Source class (see earlier link for the methodology). Alas, it only changes the background under the image and accessory views to the left and right of each cell.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Let the original Data Source get a hold of the cell first.
UITableViewCell *cell = [originalDataSource tableView:tableView cellForRowAtIndexPath:indexPath];
// If we override the textColor, that works fine. (Commented out for now.)
//cell.textLabel.textColor = [UIColor whiteColor];
// If we override the background view … that doesn't work so well.
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"cellBackground.png"]];
return cell;
}
I know, imageNamed: is evil, and perhaps I should create one background image and just reuse it throughout (vs. allocating multiple UIImageView objects). Apart from those items, any thoughts? (I tried adjusting the textLabel's backgroundColor too, to no avail … unless I'm doing something wrong there.)
Here is the answer...
Add the code in
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
method of MoreTableViewDataSource
In addition to Sijo's answer post (thank you!), I just realized there was another thread on SO that takes a similar tack ... except I understand this code a bit more. However, I never cross-referenced it back to this question/thread! So here it is. See Ian1971's response in particular.
I have a UITableView subclass and a UITableViewCell subclass that I'm using for cells. I'm bulding all my cells in advance and store them in an array from where I use them in cellForRowAtIndexPath. Aside from this I have a thread that loads some images in each cell, in the background. The problem is that the cells don't get refreshed as fast as the images are loaded. For example, if I don't scroll my table view, the first cells only get refreshed when all cells have been modified and the thread has exited.
Any ideas on how I can effectively refresh my tableview/cell?
Have you tried calling [cell setNeedsDisplay] but on the main thread?
setNeedsDisplay when called on a background thread does pretty much nothing,
try this:
[cell performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
Are you using a callback to notify the controller of your tableview that the images have been loaded? If not, that would be an ideal method.
When the image loads, fire off a callback to the table view controller that sets the image on the cell, and then calls reloadData on the tableView.
This way whenever a new image loads, the table will update to display it.
Not sure exactly what you are trying to achieve with the images - but can I guess they are coming from a server and that is why you want to download them in another thread?
I would not try to load up the cells before you display the table - you should use lazy loading as much as possible to make sure you are making the most of the memory on a device.
My suggestion would be to look at using a subclass of NSOperation to manage the loading of images. Firstly NSOperation will handle all the complexity of threading for you and allow you to queue up the operations. You will then be able to prioritise the operations that you want completed for the cells at the top.
As each operation completes you can make a call back to the cell or tableViewController (perhaps create a delegate protocol to make this really easy).
If you have an operation per image/cell combination then you should be able to refresh each cell as the operation completes. Doing this along with prioritising the operations will give you an optimal solution.
If the NSOperations sound complex or you are put off by this - please do try it - it is a lot simpler than I might have made it sound.
Have you tried calling [cell setNeedsDisplay]?