iOS- Update UITableViewCell information without reloading the row? - ios

Let's say you have a UITableView that displays a list of file metadata, and you want to show the download_progress of each file in a UILabel of a custom UITableViewCell. (This is an arbitrarily long list - thus dynamic cells will be reused).
If you want to update the label without calling either reloadData or reloadRowsAtIndexPaths, how can you do it?
For those who are wondering - I don't want to call either of the reload... methods because there's no need to reload the entire cell for each percentage point update on download_progress.
The only solutions I've come across are:
Adding the cell as a key-value observer for the file's download_progress.
Calling cellForRowAtIndexPath... directly to obtain the label and change it's text.
However,
KVO in general isn't a fun api to work with - and even less so when you add cell reuse into the mix. Calling cellForRowAtIndexPath directly each time a percentage point is added feels dirty though.
So, what are some possible solutions? Any help would be appreciated.
Thanks.

As a corollary to Doug's response, here is what I ended up going with:
Each file has a unique identifier, so I made it responsible for posting notifications about updates to its attributes (think KVO, but without the hassle):
I made a FileNotificationType enum (i.e. FileNotificationTypeDownloadTriggered, and FileNotificationTypeDownloadProgress). Then I would send the progress into the NSNotification's userInfo NSDictionary along with the FileNotificationType.
- (void)postNotificationWithType:(FileNotificationType)type andAttributes:(NSDictionary *)attributes
{
NSString *unique_notification_id = <FILE UNIQUE ID>;
NSMutableDictionary *mutable_attributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
[mutable_attributes setObject:#(type) forKey:#"type"];
NSDictionary *user_info = [NSDictionary dictionaryWithDictionary:mutable_attributes];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:unique_notification_id object:nil userInfo:user_info];
});
}
The file object also has a method to enumerate what types of notifications it could send:
- (NSArray *)notificationIdentifiers
{
NSString *progress_id = <FILE UNIQUE ID + FILENOTIFICATIONTYPE>;
NSString *status_id = <FILE UNIQUE ID + FILENOTIFICATIONTYPE>
NSString *triggered_id = <FILE UNIQUE ID + FILENOTIFICATIONTYPE>
NSArray *identifiers = #[progress_id, status_id, triggered_id];
return identifiers;
}
So when you update an attribute of a file elsewhere, simply do this:
NSDictionary *attributes = #{#"download_progress" : #(<PROGRESS_INTEGER>)};
[file_instance postNotificationWithType:FileNotificationTypeDownloadProgress andAttributes:attributes];
On the receiving end, my table view delegate implemented these methods to add / remove my custom UITableViewCells as observers for these notifications:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
File *file = [modelObject getFileAtIndex:indexPath.row];
for (NSString *notification_id in file.notificationIdentifiers)
{
[[NSNotificationCenter defaultCenter] addObserver:cell selector:#selector(receiveFileNotification:) name:notification_id object:nil];
}
}
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
[[NSNotificationCenter defaultCenter] removeObserver:cell];
}
Finally, the custom UITableViewCell has to implement the receiveFileNotification: method:
- (void)receiveFileNotification:(NSNotification *)notification
{
FileNotificationType type = (FileNotificationType)[notification.userInfo[#"type"] integerValue];
// Access updated property info with: [notification.userInfo valueForKey:#"<Your key here>"]
switch (type)
{
case FileNotificationTypeDownloadProgress:
{
// Do something with the progress
break;
}
case FileNotificationTypeDownloadStatus:
{
// Do something with the status
break;
}
case FSEpisodeNotificationTypeDownloadTriggered:
{
// Do something if the download is triggered
break;
}
default:
break;
}
}
Hopefully this helps someone who is looking to update tableview cells without having to reload them! The benefit over key-value observing is that you won't get issues if the File object is deallocated with the cell still observing. I also don't have to call cellForRow....
Enjoy!

I would create a custom cell, which I'm guessing you've done. Then I'd have the cell listen for a specific notification that your download progress method would post, then update the label there. You'd have to figure out a way for your download progress to specify a certain cell, maybe by a title string or something that would be unique that your download progress method could be told, so your cell update method could make sure the note was meant for it. Let me know if you need me to clarify my thought process on this.

Related

How to make API call in UITableVIewCell

I have a Scenario in which I have to make a API request to update UILables in TableViewCell .
The problem is that for each cell I have to make a Unique API request. The API url is same but the parameter is different.
Currently I am making calls in cellForRowAtIndex and In success block I am using dispatch_async to update the array and reloading the UITableView.
My cellForRowAtIndexMethod :
if(!apiResponded) //Bool value to check API hasn't responded I have to make API request
{
cell.authorLabel.text = #"-------";// Set Nil
NSString *userId =[CacheHandler getUserId];
[self.handleAPI getAuthorList:userId]; //make API Call
}
else
{
cell.authorLabel.text = [authorArray objectAtIndex:indexPath.row];// authorArray is global Array
}
My success Block of API Request :
numOfCallsMade = numOfCallsMade+1; //To track how manny calls made
apiResponded = YES; // to check API is reponded and I have to update the UILables
dispatch_async(kBgQueue, ^{
if(!authorArray)
authorArray = [[NSMutableArray alloc]init];
NSArray *obj = [responseData valueForKey:#"aName"];
if(obj == nil)
{
[authorArray addObject:#"N/A"];
}
else
{
[authorArray addObject:[obj valueForKey:#"authorName"]];
}
dispatch_async(dispatch_get_main_queue(), ^{
if(numOfCallsMade == [self.mCarsArray count]) // this is to check if I have 10 rows the 10 API request is made then only update
[self.mTableView reloadData];
});
});
When I run this code I am getting Same Value for each Label. I don't know my approach is good or not. Please any one suggest how can Achieve this.
From your code, I’m not really sure what you want to achieve. All I know is that you want to make a request per each cell, and display received data. Now I don’t know how you’d like to store your data, or how you’ve setup things, but I’ll give you a simple suggestion of how you could set this up, and then you can modify as needed.
I assume you only need to make this request once per cell. For simplicity, we could therefore store a dictionary for the received data (author names?).
#property (nonatomic, strong) NSMutableDictionary *authorNames;
We need to instantiate it before usage, inside init or ViewDidLoad, or wherever you see fit (as long as it's before TableView calls cellForRowAtIndexPath:).
authorNames = [[NSMutableDictionary alloc] init];
Now in cellForRowAtIndexPath, you could do the following:
NSInteger index = indexPath.row
cell.authorLabel.text = nil;
cell.tag = index
NSString *authorName = authorNames[#(index)];
if (authorName) { // Check if name has already exists
cell.authorLabel.text = authorName;
} else {
// Make request here
}
In your requests completion block (inside CellForRowAtIndexPath:), you add this:
NSString *authorName = [responseData valueForKey:#“aName”];
authorNames[#(index)] = authorName; // Set the name for that index
if (cell.index == index) { // If the cell is still being used for the same index
cell.authorLabel.text = authorName;
}
When you scroll up and down in a TableView, it will reuse cell that are scrolled outside of the screen. That means that when a request has finished, the cell could have been scrolled offscreen and reused for another index. Therefore, you want to set the cell tag, and when the request has completed, check if the cell is still being used for the index you made the request for.
Potential issues: When scrolling up and down fast, when your requests are still loading, it could potentially make multiple requests for each cell. You'll have to add some way to just make each request once.
You can declare a method in your custom cell and then call it from cellForRowAtIndex , the method will call the API and update the label present only in that cell.
So for each cell you will have separate method calls & each success block will update the particular cell Label text only.

Correctly set numberOfRowsInSection

I have an app that load data using AFNetworking, parsing income JSON data and populate table. I use different class to manage JSON and to populate UITableView.
I need to correctly set number of rows. Problem is, i guess its method load too early, before we get any data from web. I tried following:
-(NSInteger)numberOfElements{
return [self.dataDictArray count];
}
And then:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.handler numberOfElements];
}
When handler is an instance type of class, that deal with JSON and declare -(NSInteger)numberOfElements.
How can i set correct number of elements in such situation? Apparently tableView load before we got web data, that's kind of confusing.
One way to do this is:
In success block of AFNetworking get method fire a notification
#define NOTIFICATION_NAME #"LoadNotification"
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_NAME object:self];
In viewDidLoad method of UITableViewController subclass set the class as observer of notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedLoadingNotification:) name:NOTIFICATION_NAME object:nil];
In 'receivedLoadingNotification' method set the delegate and datasourceDelegate
-(void)receivedLoadingNotification:(NSNotification *) notification
{
if ([[notification name] isEqualToString:NOTIFICATION_NAME])
{
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
}
}
So, the controller will only call the dataSource and delegate methods when JSON data is successfully loaded from AFNetworking.
Hope it helps.

How to add reloadData to Xcode default Master-Detail Tableview application

I've created the master-detail view app using xcode and let xcode generate all the things it should do. I added a few buttons to the TableView and finally hooked up a responder method to these buttons: looking something like this (not all code pasted):
-(IBAction)orderBy:(UIBarButtonItem *)sender
{
int lv_temp = sender.tag;
choice = lv_temp;
switch (lv_temp) {
case 0:
break;
case 1:
{
//This gives us all the different sections available.
typeSections = [Items valueForKeyPath:#"#distinctUnionOfObjects.type"];
//Do some more stuff and create the Dictionary so we hold an array with a key for each section
[ItemsByType2 setObject:ItemsTemp forKey:[NSString stringWithFormat:#"%#", id]];
[ItemsTemp removeAllObjects];
}
The end result is that I finally have a NSDictionary object containing arrays with the section ID's as a key. I'd like to reload these sections so we have a kind of sorting.
The problem is, how and where to call the reloadData of the generated tableView? Looks like the tableView itself is not accessible in the Sender method (which sounds kind of logical to me btw) but I do not know how to call the method so the tableView will call it's methods numberOfSections, RowsPerSection etc (pseudocode btw).
Any ideas?
Cheers!
Laurens
-(IBAction)orderBy:(UIBarButtonItem *)sender {
int lv_temp = sender.tag;
choice = lv_temp;
switch (lv_temp) {
case 0:
break;
case 1: {
//This gives us all the different sections available.
typeSections = [Items valueForKeyPath:#"#distinctUnionOfObjects.type"];
//Do some more stuff and create the Dictionary so we hold an array with a key for each section
[ItemsByType2 setObject:ItemsTemp forKey:[NSString stringWithFormat:#"%#", id]];
[ItemsTemp removeAllObjects];
}
[self.tableView reloadData];
}

Multiple NSUrlRequests To Fetch Different Pages Of WebService To Load More Data In TableView

I have a simple iPhone app that is parsing data (titles, images etc.) from rss feed and showing in the tableview.
The viewDidLoad has an initial counter value to reach the first page of the feed and load in the tableview by calling the fetchEntriesNew method:
- (void)viewDidLoad
{
[super viewDidLoad];
counter = 1;
[self fetchEntriesNew:counter];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(dataSaved:)
name:#"DataSaved" object:nil];
}
- (void) fetchEntriesNew:(NSInteger )pageNumber
{
channel = [[TheFeedStore sharedStore] fetchWebService:pageNumber withCompletion:^(RSSChannel *obj, NSError *err){
if (!err) {
int currentItemCount = [[channel items] count];
channel = obj;
int newItemCount = [[channel items] count];
NSLog(#"Total Number Of Entries Are: %d", newItemCount);
counter = (newItemCount / 10) + 1;
NSLog(#"New Counter Should Be %d", counter);
int itemDelta = newItemCount - currentItemCount;
if (itemDelta > 0) {
NSMutableArray *rows = [NSMutableArray array];
for (int i = 0; i < itemDelta; i++) {
NSIndexPath *ip = [NSIndexPath indexPathForRow:i inSection:0];
[rows addObject:ip];
}
[[self tableView] insertRowsAtIndexPaths:rows withRowAnimation:UITableViewRowAnimationBottom];
[aiView stopAnimating];
}
}
}];
[[self tableView] reloadData];
}
When the user reaches the bottom of the tableview, i am using the following to reach the next page of the feed and load at the bottom of the first page that was loaded first:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height)
{
NSLog(#"Scroll End Called");
NSLog(#"New Counter NOW is %d", counter);
[self fetchEntriesNew:counter];
}
}
UPDATE2: Here is a more easy to understand description of whats wrong that i am unable to solve: For example there are 10 entries in the each page of the rss feed. The app starts, titles and other labels are loaded immediately and images starts loading lazily and finally gets finished. So far so good. The user scrolls to reach the bottom, reaching the bottom will use the scroll delegate method and the counter gets incremented from 1 to 2 telling the fetchEntriesNew method to reach the second page of the rss feed. The program will start loading the next 10 entries at the bottom of first 10 previously fetched. This can go on and the program will fetch 10 more entries every time the user scrolls and reaches bottom and the new rows will be placed below the previously fetched ones. So far so good.
Now let us say the user is on page 3 currently which has been loaded completely with the images. Since page 3 is loaded completely that means currently there are 30 entries in the tableview. The user now scrolls to the bottom, the counter gets incremented and the tableview begins populating the new rows from page 4 of the rss feed at the bottom of the first 30 entries. Titles get populated quickly thus building the rows and while the images are getting downloaded (not downloaded completely yet), the user quickly moves to the bottom again, instead of loading the 5th page at the bottom of the 4th, it will destroy the 4th ones that is currently in the middle of getting downloaded and starts loading the 4th one again.
What it should do is that it should keep on titles etc from next pages when user reaches the bottom of the tableview regardless of whether the images of the previous pages are in the middle of getting downloaded or not.
There is NO issue with the downloading and persisting data in my project and all the data is persisted between the application runs.
Can someone help to point me out to the right direction. Thanks in advance.
UPDATE 3: Based on #Sergio's answer, this is what i did:
1) Added another call to archiveRootObject [NSKeyedArchiver archiveRootObject:channelCopy toFile:cachePath]; after [channelCopy addItemsFromChannel:obj];
At this point, its not destroying and reloading the same batch again and again, exactly what i wanted. However, it doesn't persist images if i scroll multiple times to reach the next page without the images of the previous page were loaded completely.
2) I am not sure how to use Bool as he explained in the answer. This is what i did: Added #property Bool myBool; in TheFeedStore, synthesised it and set it to NO after newly added archiveRootObject:channelCopy and set it to YES in ListViewController at the very start of fetchEntries method. It didn't work.
3) I also realised the way i am dealing with the whole issue is performance vice not better. Although i don't know how to use images outside the cache and handle them as sort of cache. Are you suggesting to use a separate archiving file for images?
Thanks a lot to all people who have contributed in trying to solve my issue.
Your issue can be understood if you consider this older question of yours and the solution I proposed.
Specifically, the critical bit has to do with the way you are persisting the information (RSS info + images), which is through archiving your whole channel to a file on disk:
[channelCopy addItemsFromChannel:obj];
[NSKeyedArchiver archiveRootObject:channelCopy toFile:pathOfCache];
Now, if you look at fetchEntriesNew:, the first thing that you do there is destroying your current channel. If this happens before the channel has been persisted to disk you enter a sort of endless loop.
I understand you are currently persisting your channel (as per my original suggestion) at the very end of image download.
What you should do is persisting the channel just after the feed has been read and before starting downloading the images (you should of course also persist it at the end of image downloads).
So, if you take this snippet from my old gist:
[connection setCompletionBlock:^(RSSChannel *obj, NSError *err) {
if (!err) {
[channelCopy addItemsFromChannel:obj];
// ADDED
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_group_wait(obj.imageDownloadGroup, DISPATCH_TIME_FOREVER);
[NSKeyedArchiver archiveRootObject:channelCopy toFile:cachePath];
});
}
block(channelCopy, err);
what you should do is adding one more archiveRootObject call:
[connection setCompletionBlock:^(RSSChannel *obj, NSError *err) {
if (!err) {
[channelCopy addItemsFromChannel:obj];
[NSKeyedArchiver archiveRootObject:channelCopy toFile:cachePath];
// ADDED
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_group_wait(obj.imageDownloadGroup, DISPATCH_TIME_FOREVER);
[NSKeyedArchiver archiveRootObject:channelCopy toFile:cachePath];
});
}
block(channelCopy, err);
This will make things work as long as you do not scroll fast enough so that the channel is destroyed before the feed (without images) is ever read. To fix this you should add a bool to your TheFeedStore class that you set to YES when you call fetchWebService and reset just after executing the newly added archiveRootObject:channelCopy.
This will fix your issues.
Let me also say that from a design/architecture point of view, you have a big issue with the way you manage persistence. Indeed, you have a single file on disk that you write atomically using archiveRootObject. This architecture is intrinsically "risky" from a multi-threading point of view and you should also devise a way to avoid that concurrent accesses to the shared stored have no destructive effects (e.g.: you archive your channel to disk for page 4 at the same time as the images for page 1 have been fully downloaded, hence you try to persist them as well to the same file).
Another approach to image handling would be storing the images outside of your archive file and treat them as a sort of cache. This would fix the concurrency issues and will also get rid of the performance penalty you get from archiving the channel twice for each page (when the feed is first read and later when the images have come in).
Hope this helps.
UPDATE:
At this point, its not destroying and reloading the same batch again and again, exactly what i wanted. However, it doesn't persist images if i scroll multiple times to reach the next page without the images of the previous page were loaded completely.
This is exactly what I meant saying that your architecture (shared archive/concurrent access) would probably lead to problems.
You have several options: use Core Data/sqlite; or, more easily, store each image in its own file. In the latter case, you could do following:
on retrieval, assign to each image a filename (this could be the id of the feed entry or a sequential number or whatever) and store the image data there;
store in the archive both the URL of the image and the filename where it should be stored;
when you need accessing the image, you don't get it from the archived dictionary directly; instead, you get the filename from the it then read the file from disk (if available);
this change would not affect otherwise your current implementation of rss/image retrieval, but only the way you persist the images and you access them when required (I mean, it seems a pretty easy change).
2) I am not sure how to use Bool as he explained in the answer.
add a isDownloading bool to TheFeedStore;
set it to YES in your fetchWebService: method, just before doing [connection start];
set it to NO in the completion block you pass to the connection object (again in fetchWebService:) right after archiving the feed the first time (this you are already doing);
in your scrollViewDidEndDecelerating:, at the very beginning, do:
if ([TheFeedStore sharedStore].isDownloading)
return;
so that you do not refresh the rss feed while a refresh is ongoing.
Let me know if this helps.
NEW UPDATE:
Let me sketch how you could deal with storing images in files.
In your RSSItem class, define:
#property (nonatomic, readonly) UIImage *thumbnail;
#property (nonatomic, strong) NSString *thumbFile;
thumbFile is the the path to the local file hosting the image. Once you have got the image URL (getFirstImageUrl), you can get, e.g., and MD5 hash of it and use this as your local image filename:
NSString* imageURLString = [self getFirstImageUrl:someString];
....
self.thumbFile = [imageURLString MD5String];
(MD5String is a category you can google for).
Then, in downloadThumbnails, you would store the image file locally:
NSMutableData *tempData = [NSData dataWithContentsOfURL:finalUrl];
[tempData writeToFile:[self cachedFileURLFromFileName:self.thumbFile] atomically:YES];
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataSaved" object:nil];
Now, the trick is, when you access the thumbnail property, you read the image from file and return it:
- (UIImage *)thumbnail
{
NSData* d = [NSData dataWithContentsOfURL:[self cachedFileURLFromFileName:self.thumbFile]];
return [[UIImage alloc] initWithData:d];
}
in this snippet, cachedFileURLFromFileName: is defined as:
- (NSURL*)cachedFileURLFromFileName:(NSString*)filename {
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSArray *fileArray = [fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask];
NSURL* cacheURL = (NSURL*)[fileArray lastObject];
if(cacheURL)
{
return [cacheURL URLByAppendingPathComponent:filename];
}
return nil;
}
Of course, thumbFile should be persisted for this to work.
As you see, this approach is pretty "easy" to implement. This is not an optimized solution, just a quick way to make your app work with its current architecture.
For completeness, the MD5String category:
#interface NSString (MD5)
- (NSString *)MD5String;
#end
#implementation NSString (MD5)
- (NSString *)MD5String {
const char *cstr = [self UTF8String];
unsigned char result[16];
CC_MD5(cstr, strlen(cstr), result);
return [NSString stringWithFormat:
#"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
#end
What you are actually trying to do, is implement paging in a UITableView
Now this is very straightforward and the best idea is to implement the paging in your UITableView delegate cellForRowAtIndexPath method, instead of doing this on the UIScrollView scrollViewDidEndDecelerating delegate method.
Here is my implementation of paging and I believe it should work perfectly for you too:
First of all, I have an implementation constants related to the paging:
//paging step size (how many items we get each time)
#define kPageStep 30
//auto paging offset (this means when we reach offset autopaging kicks in, i.e. 10 items before the end of list)
#define kPageBegin 10
The reason I'm doing this is to easily change the paging parameters on my .m file.
Here is how I do paging:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
int section = indexPath.section-1;
while (section>=0) {
row+= [self.tableView numberOfRowsInSection:section];
section--;
}
if (row+kPageBegin>=currentItems && !isLoadingNewItems && currentItems+1<maxItems) {
//begin request
[self LoadMoreItems];
}
......
}
currentItems is an integer that has the number of the tableView datasource current items.
isLoadingNewItems is a boolean that marks if items are being fetched at this moment, so we don't instantiate another request while we are loading the next batch from the server.
maxItems is an integer that indicates when to stop paging, and is an value that I retrieve from our server and set it on my initial request.
You can omit the maxItems check if you don't want to have a limit.
and in my paging loading code I set the isLoadingNewItems flag to true and set it back to false after I retrieve the data from the server.
So in your situation this would look like:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
int section = indexPath.section-1;
while (section>=0) {
row+= [self.tableView numberOfRowsInSection:section];
section--;
}
if (row+kPageBegin>=counter && !isDowloading) {
//begin request
isDowloading = YES;
[self fetchEntriesNew:counter];
}
......
}
Also there is no need to reload your whole table after adding the new rows.
Just use this:
for (int i = 0; i < itemDelta; i++) {
NSIndexPath *ip = [NSIndexPath indexPathForRow:i inSection:0];
[rows addObject:ip];
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:rows withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView endUpdates];
A simple BOOL is enough to avoid repetitive calls:
BOOL isDowloading;
When the download is done, set it to NO. When it enters here:
if (endScrolling >= scrollView.contentSize.height)
{
NSLog(#"Scroll End Called");
NSLog(#"New Counter NOW is %d", counter);
[self fetchEntriesNew:counter];
}
put it to YES. Also don't forget to set it to NO when the requests fails.
Edit 1:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height)
{
if(!isDowloading)
{
isDownloading = YES;
NSLog(#"Scroll End Called");
NSLog(#"New Counter NOW is %d", counter);
[self fetchEntriesNew:counter];
}
}
}
And when you finish fetching, just set it to NO again.

Memory management using ARC on iOS

Just have a quickly question (more of a curiosity thing) based on a problem I just solved (I will post the answer to my problem in the post, which can be found here: My former question
The thing is that I have this UITableView which contains custom cell objects. Every time you enter this view, I generate new cells for the UITableView like this:
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"UploadCellView" owner:self options:nil];
cell = customCell;
}
Which happens in the standard method:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Now the problem is that my custom cell objects listens for NSNotifications about upload objects happening in the background, so they can update its model data to their labels and progress bars etc. It happens like this (this is a method from the custom cell objects):
-(void) uploadProgress: (NSNotification*)notification
{
NSDictionary *userInfo = [notification userInfo];
NSNumber *uploadID = [userInfo valueForKey:#"uploadID"];
if (uploadID.integerValue == uploadActivity.uploadID)
{
UIProgressView *theProgressBar = (UIProgressView*)[self viewWithTag:progressBarTag];
[theProgressBar setProgress:(uploadActivity.percentageDone / 100) animated:YES];
UILabel *statusText = (UILabel*)[self viewWithTag:percentageTag];
[statusText setText:[NSString stringWithFormat:#"Uploader - %.f%% (%.01fMB ud af %.01fMB)", uploadActivity.percentageDone, uploadActivity.totalMBUploaded, uploadActivity.totalMBToUpload]];
}
}
When an upload finish they simply do this:
-(void) uploadFinished: (NSNotification*)notification
{
NSDictionary *userInfo = [notification userInfo];
NSNumber *uploadID = [userInfo valueForKey:#"uploadID"];
if (uploadID.integerValue == uploadActivity.uploadID)
{
[self setUploadComplete];
[[ApplicationActivities getSharedActivities] markUploadAsFinished:uploadActivity];
NSLog(#"BEGINNING RELOAD");
[parentTable reloadData];
NSLog(#"ENDING RELOAD");
}
}
Now the problem is when they call their owning tableview. When the view which the tableview is contained within dismisses, the old custom cell objects are still alive in the background getting NSNotfications. And when that upload is then done, the old custom cell objects from the former table views still tries to call that parentTable property which was set at that time, now resulting in calling random junk memory.
The way I solved this was to keep an array of all cell objects getting created in the table and then make them stop listening when the view is dismissed like this:
-(void) viewWillDisappear:(BOOL)animated
{
for (UploadCell *aCell in lol)
{
[aCell stopListening];
}
[self.navigationController popViewControllerAnimated:YES];
}
But this seems like a bit of a hack. How would I go about making sure that the custom cell objects are deleted when the view is dismissed? Because when the view is intialized again, new cells are simply made anyways, so I have no use for the old ones.
The custom view cells have a strong property pointer to the tableview they get associated with, but I thought the ARC would make sure that TableView pointer would not get invalidated then? Obviously it is somehow. Maybe because of the containing view being deleted when popped?
Sounds like the cells have a retain property pointing back to your UITableViewDataSource class.
They should instead have an assign property, then they will be released properly when the table view is released (which it currently cannot be if your cells are retaining it).
Also, the cells should shut down notifications when they are dropped out of the tableview, by overriding the cells didMoveToSuperview method:
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
if ( [self superview] == nil )
{
[self unsubscribeFromYourNotifications];
}
}
That is so if they scroll off screen they will not be wasting resources updating things.
Have you considered a separate update model that keeps a map between uploadIDs and cells that listens for the notification? That way, the cells aren't responsible for updating the table themselves, the update model would do it. When the table goes away, you can shut down the update model.

Resources