iOS: Slide to display data after reloading UITableView - objective-c

Below is the sequences how my app is loading data into my UITableView:
UITableView loads from empty array.
App fetches data asynchronously.
After data is fetched and processed, app calls UITableView reloadData.
The problem I'm currently encountered is, I need to slightly slide the UITableView manually, in order to enable UITableView display the fetched data.
Currently I'm using iOS simulator to do the testing.
Is this considered normal? Is there anything I can do to display the data without a request from user to slide it?
-- EDIT --
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *tableCellIdentifier = #"TempTableViewCell";
TempTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableCellIdentifier forIndexPath:indexPath];
int row = [indexPath row];
YoutubeVideo *currentVideo = [videoArray objectAtIndex:row];
cell.TempLabel.text = currentVideo.VideoTitle;
[cell.TempImage setImageWithURL:[NSURL URLWithString:currentVideo.VideoThumbnailURL] placeholderImage:[UIImage imageNamed:#"Icon-Small"]];
cell.TempLabelDate.text = currentVideo.VideoDate;
return cell;
}

If I understand your question properly, then...
I would guess that when you call [tableView reloadData], you are not on the main thread. Try this:
dispatch_async(dispatch_get_main_queue(), ^{
[tableView reloadData];
});
Edit with clarification:
The reason for this is that your network request is probably occurring on a different thread, and, once you've finished and you reload the table, you are still on this background thread. When performing UI updates, you need to do it on the main thread, otherwise you get weird stuff happening , like you are seeing here.
The code above will perform the table update back on the main thread.

Related

UITableView fails to load images for first two cells

I have a UITableView in which each custom cell (created in storyboard) contains a UIImageView that is asynchronously populated with an image from a custom object (object.image). Also, the TableView shows different content depending on the state of a UISegmentedControl that has two states (toggling the control triggers reloadData and the cellForRowAtIndexPath method gets data from a different array).
Ordinarily the setup works as expected. However, if I scroll down in the TableView and then toggle the SegmentedControl, the first two cells don't load their images (but the labels in the cells load fine). It appears that the ImageView is occasionally nil. Scrolling down past the cells and then back up causes the image to load normally.
Relevant Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Get the cell
CustomTableViewCell *cell = (CustomTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"CustomTableViewCellIdentifier"];
if (segmentedControlPosition1) {
//Get the object containing all the data.
myObject = [firstObjectsArray objectAtIndex:indexPath.row];
//...Update cell labels...
[cell.myImageView setImage:nil];
//Asynchronously retrieve and conditionally populate the event cover image to optimize rapid scrolling in large table.
//Fetch image data on secondary, asynchronous thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *image = myObject.coverImage;
if (image) {
//Update the UI on the primary thread.
dispatch_async(dispatch_get_main_queue(), ^{
//Verify that the cell is still visible.
if ([[tableView indexPathsForVisibleRows] containsObject:indexPath]) {
//Retrieve the cell (cell may have changed during async processing).
CustomTableViewCell *correctCell = [self.tableView cellForRowAtIndexPath:indexPath];;
[correctCell.myImageView setImage:image];
NSLog(#"Loaded image successfully: %i, %#, %#, %#", indexPath.row, image, correctCell.myImageView, correctCell.myImageView.image);
NSLog(#"%#",);
[correctCell setNeedsLayout];
}
});
}
});
} else {
//...do exactly the same thing just get the data from a different array...
}
}
return cell;
}
My NSLog statement reveals that sometimes, (for instance after scrolling down a bit and then toggling the SegmentedControl), correctCell.myImageView is nil. Again, all of the labels in the same cell populate just fine.

In UITableViewCell, UIImageView content repeats after some cells using AFNetworking

The content is distinct on all the cells on the first page but as we scroll down, only the UIImageView is repeated though the UILabels are distinct.
I am using [UIImageView setImageWithURL:(NSURL *)url] from AFNetworking which was included in the RestKit. Heres the implementation
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RClubTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"RClubTableViewCell"];
RClubObject * club = _clubs[indexPath.row];
[cell.clubImageView setImageWithURL:[NSURL URLWithString:club.clubImageURL]];
cell.nameLabel.text = club.clubName;
return cell;
}
Seems like iOS is somehow using the previously created cells. Would like to have completely fresh cells while scrolling.
You need to call setImageWithURL:placeholderImage: with a non-nil placeholder image to have the old image removed from the image view.
I guess you missed reallocation in your cellForRowAtIndexPath:
Your code should be something like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RClubTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"RClubTableViewCell"];
if(cell == nil)
{
cell = [[RClubTableViewCell alloc] init];
}
RClubObject * club = _clubs[indexPath.row];
[cell.clubImageView setImageWithURL:[NSURL URLWithString:club.clubImageURL]];
cell.nameLabel.text = club.clubName;
return cell;
}
Update :
Okay. I just had the same issue. Here what my search over google tells me : Since you are downloading the image in async and also scrolling, it gets downloaded and attached to other cells which are currently visible
Solution:
add tag to each imageView using indexpath.row
Use function setImageWithURL: placeholderImage:success:
In success block reload the cell using the tag value.
Though I haven't tested this I feel this should work. PS : Let me know if this works I also would have to do the same then :)
Yes, iOS is reusing the previously created cells. That is what this line of code does, so that the app does not have to spend the time creating a new cell from scratch:
RClubTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"RClubTableViewCell"];
Clear the image from the recycled cell before adding the new image for this cell:
cell.clubImageView.image = nil;
[cell.clubImageView setImageWithURL:[NSURL URLWithString:club.clubImageURL]];
cell.nameLabel.text = club.clubName;
return cell;
Alternatively, you could create a new cell from scratch instead of dequeuing one.

Updating subviews in cells on a UITableView

I'm developing an application in iPad 6.0 using Storyboards.
Let me first explain my goal. I'm trying to achieve a Master-Detail (SplitViewController-like) View Controller using 2 UITableViewControllers.
The first UITableView("Master"), let's call this HeaderTableView, as the name implies, lists down the Headers for the...
...Second UITableView("Detail"), let's call this the EncodingTableView, which contains a programmatically changing CustomTableViewCell (subviews contained within each cell may be a UITextField, UIButton or UISwitch).
See EncodingTableView.m
- (void)updateEncodingFields:(NSArray *)uiViewList
{
// Add logic for determining the kind of UIView to display in self.tableView
// Finally, notify that a change in data has been made (not working)
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *encodingFieldsTableId = #"encodingFieldsTableId";
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:encodingFieldsTableId];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
}
// Change text in textView property of CustomTableViewCell
cell.encodingFieldTitle.text = uiViewList.title;
// added methods for determining what are to be added to [cell.contentView addSubView:]
// data used here is from the array in updateEncodingFields:
}
My HeaderTableView.m, contains the didSelectRowAtIndexPath to update the EncodingTableView
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (![selectedIndexPath isEqual:indexPath]) {
selectedIndexPath = indexPath;
[self updateDataFieldTableViewForIndexPath:indexPath];
}
}
- (void)updateDataFieldTableViewForIndexPath:(NSIndexPath *)indexPath {
[self.encodingTableView updateEncodingFields:self.uiViewList];
}
Question
- Data is all ok but why doesn't EncodingTableView "redraw"ing the fields? My
suspicion is that reusing cells has something to do with this but I just can't figure out why.
Screenshots on the result:
Initial Selection in HeaderTableView
Second Selection in HeaderTableView
What I've tried :
I kept seeing suggestions such as [UITableView setNeedsDisplay],
[UITableView reloadData] and [UITableView setNeedsLayout] but none of
them worked.
Removing the reuse of tableViewCells works fine but this causes parts of my
CustomTableView.encodingFieldTitle to disappear. Not to mention that this might cause performance issues if I were to drop reusing cells.
Restrictions:
I know that a good idea is to use a SplitViewController but this is just a subpart of my app (hence not the RootViewController).
Finally, thanks for reading such a long post. ;)
It looks like you are most likely adding subviews inside tableView:cellForRowAtIndexPath:.
The issue is that if you use cell reuse then are not always starting from a blank slate inside tableView:cellForRowAtIndexPath: instead you can possibly be given a cell back that has already been configured once. This is what you are seeing, a cell that has previously had labels added to it is handed back to you and then you add some more labels over the top.
There are a few way to deal with this:
(My preferred option) Create a subview of UITableViewCell with these extra sub views available as properties.
Ensure the cell setup is only done once
A great place to do this is when you actually create a cell when one does not already exist e.g. inside the if (cell) check
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:encodingFieldsTableId];
// add subview's here and give them some way to be referenced later
// one way of doing it is with the tag property (YUK)
UILabel *subView = [[UILabel alloc] initWithframe:someFrame];
subView.tag = 1;
[cell.contentView addSubview:subView];
}
UILabel *label = (id)[cell.contentView viewWithTag:1];
label.text = #"some value";
One problem i can see in your code is that the cell identifiers used are different in tableView cellForRowAtIndxPath function.
While dequeueing you are using this identifier - > "encodingFieldsTableId"
&
while creating a cell you are using this identifier - > "dataFieldUiGroupTableId".
Ideally these two identifiers should be same !!!
Try adding,
cell.encodingFieldTitle.text = nil;
Before if(cell == nil)
So that whenever your cellForRowAtIndexPath method is called, the string already present in the cell you are going to reuse will get deleted and the new text in uiViewList.title will be displayed.

Table View Not Properly Updating

My project is a large one, and I'm not sure which code snippet is causing the problem, so I'll just describe the problem. I have an initial view that is a UITableView. This view has a navigation bar, in which one of the buttons is an "edit" button. Pressing the "edit" button sends you into a modal view controller in which there is another UITableView. This UITableView has the attribute that it is a checklist table view (in which multiple items can be selected [checked] or deselected. Once you are finished choosing your items, it saves the array of chosen objects into an NSUserDefault. Now, you are back at the original page where the array of things you chose should be displayed on the UITableView. I change the array that is feeding the UITableView it's data to the array grabbed from the NSUserDefaults. I then call [tableView reloadData] and nothing changes. I would really appreciate any tips. If you guys think you know what part of the code is causing me grief, please respond and I'll post it. Thanks (and by the way, I know I should be making the main view controller the delegate of modal view controllers). Thanks in advance.
Call [[NSUserDefaults standardUserDefaults] synchronize]; after you make changes in the modal view controller. This will save the changes.
Be sure to update the cell content each time the table row is reloaded.
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Bleh *data = (Get your data for this row);
cell.textLabel.text = data.myValue;
cell.imageView.image = data.myImage;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
Try to reload your tableview on viewwillappear , when you back from modelviewcontroller method calls
- (void)viewWillAppear:(BOOL)animated{
[yourTableView reloadData];
}

tableview won't update after adding objects to an array

I have a tableview inside a popover in my main view which is a web browser. Whenever the user visits a web page I want it to add the page to a list of recently visited websites. I have the code set up to add the URL as a string to the array that is the data source for the table view. The table view only shows the first site visited and won't show anything past that. However I know the sites are being added to the array because the array shows the Sites using NSLog statements after being added. However they still won't show up in the table. Can anyone help me?
EDIT:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [recentlyVisitedUrls count];
}
Also make sure you are returning the correct length of your array from – tableView:numberOfRowsInSection:
Try
[tableView reloadTable];
after you add the new data.
If you wan't animated updating, you can call:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO]
If you need animation, it will be better read: Table View Programming Guide for iOS, you need Batch insert… part.
If you have a large table, [tableView reloadData] is probably not ideal. It'll also will reset your view to the beginning of the table, which may or may not be desirable. You might try something like this:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_model.recordCount - 1 inSection:0];
[_tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight];
[_tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
BTW, _model is the data delegate class for the table in the example.
Also, make sure the data delegate updates it's total count correctly before you insert, or your app will crash on the insertRowsAtIndexPaths.