I'm load an array of dictionary in a table view and after reload my table I called a method which change the data of dictionary from array and then refresh/reload particular index of tableview. But I'm not able to scroll my table until unless my complete data not updated again.
Code from where I call my method and reload table:
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[Hud hideAnimated:true];
[self.myTbl reloadData];
dispatch_async(dispatch_get_main_queue(),^{
[self loadTutorials];
});
}
method in which I'm trying to update my table data.
#pragma mark:GetAllData
-(void)loadTutorials{
dispatch_async(dispatch_get_main_queue(),^{
int k = 0;
for (NSMutableString*linknew in linkArr) {
//here some calculations code for parsing then next
for (TFHppleElement *elements in contributorsNodes) {
// 5
for (TFHppleElement *child in elements.children) {
if ([child.tagName isEqualToString:#"img"]) {
// 7
#try {
NSString*url = [child.attributes objectForKey:#"src"];
NSMutableDictionary*dict = [[feeds objectAtIndex:k] mutableCopy];
[dict setObject:url forKey:#"image"];
[feeds removeObjectAtIndex:k];
[feeds insertObject:dict atIndex:k];
NSIndexPath*index = [NSIndexPath indexPathForRow:k inSection:0];
[self.myTbl reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationNone];
}
#catch (NSException *e) {}
}
}
}k++;
}
});
}
cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
UILabel*lbl = (UILabel*)[cell viewWithTag:101];
UIImageView*imgView = (UIImageView*)[cell viewWithTag:103];
[imgView sd_setImageWithURL:[NSURL URLWithString:[[feeds objectAtIndex:indexPath.row] objectForKey:#"image"]] placeholderImage:[UIImage imageNamed:#"img.jpg"]];
lbl.text = [NSString stringWithFormat:#"%#",[[feeds objectAtIndex:indexPath.row] objectForKey: #"title"]];
return cell;
}
Have many things in your question make me confused.
With parserDidEndDocument method, I don't know why you need to call [self.myTbl reloadData] although after that you update self.myTbl inside loadTutorials method.
Why do you need to call 2 dispatch_async here, both outside and inside loadTutorials method? I think it's not necessary. Just call 1 time.
To replace an object in an array, you don't need to remove and insert object. Just replace by using feeds[k] = dict. Make sure feeds.count > k before using.
As i guess, you use try catch because you got crash when updating self.myTbl inside loadTutorials method. In my opinion, you should use beginUpdates and endUpdates instead of try catch.
I think the problem is because you used try catch to handle crash.
Try to rewrite you code with background queue as #Losiowaty suggested, we will have
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[Hud hideAnimated:true];
[self.myTbl reloadData];
[self loadTutorials];
}
#pragma mark:GetAllData
-(void)loadTutorials{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
int k = 0;
for (NSMutableString*linknew in linkArr) {
//here some calculations code for parsing then next
for (TFHppleElement *elements in contributorsNodes) {
// 5
for (TFHppleElement *child in elements.children) {
if ([child.tagName isEqualToString:#"img"]) {
NSString*url = [child.attributes objectForKey:#"src"];
// I assume in some case, feeds[k] doesn't exist. We need add an
// NSMutableDictionary to this index before using to avoid crash.
// If you make sure feeds[k] always exist you can ignore it
if (feeds.count == k) {
[feeds addObject:[[NSMutableDictionary alloc] init]];
}
[feeds[k] setObject:url forKey:#"image"];
NSIndexPath*index = [NSIndexPath indexPathForRow:k inSection:0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.myTbl beginUpdates];
[self.myTbl reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationNone];
[self.myTbl endUpdates];
});
}
}
}k++;
}
});
}
Let try the code first. If you still have any problem, let me know. I will help you.
Related
I started an iOS project and I'm working with UITableView to display a list of pilots with images . I did pagination on my api and I tried to load more once you scrolled the tableview. the problem that I got is that the new cells are always displayed on top of the tableview not in the bottom. Please check on my code if there is a solution I will be grateful
- (void)loadData :(NSInteger)page {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
url = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#%#%ld",NSLocalizedString(#"get_pilots",nil),mainDelegate.idAccount,#"?page=",(long)page]];
task = [restObject GET:url :mainDelegate.token completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSMutableDictionary* jsonResponse = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:nil];
NSArray *pilotKey = [jsonResponse objectForKey:#"pilot"];
for (NSDictionary *pilotItem in pilotKey ){
PilotObject *pilotObj = [PilotObject new];
[pilotObj getPilot:pilotObj :pilotItem];
[_pilotsAll addObject:pilotObj];
}
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
[self checkTableView:_pilotsDisplay :self.view];
[viewPilots.tableViewPilots reloadData];
});
}];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (currentPage == totalPages) {
return [_pilotsDisplay count];
}
return [_pilotsDisplay count] + 1;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == [_pilotsDisplay count] - 1 && currentPage<totalPages ) {
[self loadData:++currentPage];
NSLog(#"current page : = %ld",(long)currentPage);
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == [_pilotsDisplay count]) {
static NSString *identifier = #"PilotCellTableViewCell";
PilotCellTableViewCell *cell = (PilotCellTableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
cell.hidden=YES;
UIActivityIndicatorView *activityIndicator = (UIActivityIndicatorView *)[cell.contentView viewWithTag:100];
[activityIndicator startAnimating];
return cell;
} else {
PilotObject *pilotObjDisplay = nil;
pilotObjDisplay = [_pilotsDisplay objectAtIndex:[_pilotsDisplay count]-1-indexPath.row];
static NSString *identifier = #"PilotCellTableViewCell";
PilotCellTableViewCell *cell = (PilotCellTableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
cell.hidden=NO;
cell.image.image = pilotObjDisplay.imageDisplayPilot;
cell.titleLabel.text = pilotObjDisplay.firstName;
cell.subTitleLabel.text = pilotObjDisplay.lastName;
cell.backgroundColor = [UIColor colorWithHexString:NSLocalizedString(#"gray_background", nil)];
return cell;
}
return nil;
}
Why you are taking 2 array _pilotsDisplay and _pilotsAll ?
If not necessary then you can also do pagination using one NSMutableArray which you can use in both cases while fetching data from server as well as while filling data to UITableView.
Remember one thing only initialise your NSMutableArray in viewDidLoad method. And when you received new data use addObject method of NSMutableArray which you are already using. And then call reloadData method of UITableView.
And in cellForRowAtIndexPath don't use calculation like [_pilotsDisplay count]-1-indexPath.row, simply use indexPath.row.
Here, inserting rows to the tableview may help you.
[tableView beginUpdates];
NSArray *paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:[dataArray count]-1 inSection:1]];
[[self tableView] insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationTop];
[tableView endUpdates];
You shouldn't add cells to a tableview. what you should do is add data to the tableview's datasource (in your case, _pilotsDisplay) and then simply reload the table. If you want the new data to appear at bottom or in any particular order, you should do that to your datasource (the array).
I've a UITableView with custom UITableViewCell in a Master-Detail structure. The cells of the table can be documents or folder...if a cell is a document, it opens the document in the detail view, but if is a folder it opens other cells below.
This works perfectly on iOS 7, but running in iOS 8, when I tap a cell, my app freezes and it takes more and more memory...at the end it crashes.
I've tried EVERYTHING...I've searched EVERYWHERE...nothing seems to work!!!
Here is my didSelectRowAtIndexPath: method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"OBJECTS: %lu", (unsigned long)_objects.count);
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSDictionary *d=[_objects objectAtIndex:indexPath.row];
if([d valueForKey:#"Objects"])
{
NSArray *ar=[d valueForKey:#"Objects"];
BOOL isAlreadyInserted=NO;
for(NSDictionary *dInner in ar )
{
NSInteger index=[_objects indexOfObjectIdenticalTo:dInner];
isAlreadyInserted=(index>0 && index!=NSIntegerMax);
if(isAlreadyInserted) break;
}
if(isAlreadyInserted)
{
[self miniMizeThisRows:ar];
}
else
{
NSUInteger count=indexPath.row+1;
NSMutableArray *arCells=[NSMutableArray array];
for(NSDictionary *dInner in ar )
{
[arCells addObject:[NSIndexPath indexPathForRow:count inSection:0]];
[_objects insertObject:dInner atIndex:count++];
}
[self addRowsAtIndexPaths:arCells];
}
}
else
{
NSDictionary *object = [_objects objectAtIndex:indexPath.row];
self.detailViewController.detailItem = [object objectForKey:#"name"];
self.detailViewController.title = [object objectForKey:#"displayedName"];
if(![cache containsObject:object])
{
TableCustomCell *cell = (TableCustomCell*)[tableView cellForRowAtIndexPath:indexPath];
[cell.marker setHidden:NO];
[cache addObject:object];
}
}
}
And in addRowsAtIndexPaths: I just do
- (void)addRowsAtIndexPaths:(NSArray*)indexPaths
{
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationMiddle];
[self.tableView endUpdates];
}
Someone helps???
Thank you.
EDIT
I figured out that the cycle is caused by my UITableViewCell sublcass...
I used this code to manage the indentation:
- (void)layoutSubviews
{
[super layoutSubviews];
float indentPoints = self.indentationLevel * self.indentationWidth;
self.contentView.frame = CGRectMake(indentPoints,
self.contentView.frame.origin.y,
self.contentView.frame.size.width - indentPoints,
self.contentView.frame.size.height);
}
By commenting this, the app works...but the cells aren't indented!
Try to press 'Pause' button at debugger, during this freeze, and look on callStack, than, press 'Continue' button, and again 'Pause', and look for calls, witch of them is the same. It looks like call cycle.
I have a view with UITableView in it which can display 4 typles of UITableViewCell's (custom ones). Basically, two of them are with media (image, move, sound) and 2 of them have additionally users avatars.
Let's generalize that cells with media will display only images (thumbnails). Of course those images are dynamically downloaded when cell is created. It could be harmful to download those images everytime user scrolls the table view so I use EGOCache to cache those images but... This ain't helping with scrolling issues!! I thought that caching will store pionter to those images but everytime cell is recreated it takes this image from disk (so Intruments are telling me that this method is killing my performace).
My question is: How to cache UITableViewCell so it's not created everytime I scroll UITableView a little bit?
Here are samples from my code so you can imagine point of my question:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Event *event = [self.events objectAtIndex:indexPath.row];
if ([event.type isEqualToString:#"camp"]) {
if (!event.mediaType) {
CampCell *cell = [self.tableView dequeueReusableCellWithIdentifier:[CampCell identifier]];
[cell configureWithModel:event];
return cell;
} else {
CampMediaCell *cell = [self.tableView dequeueReusableCellWithIdentifier:[CampMediaCell identifier]];
[cell configureWithModel:event];
return cell;
}
} else {
if (!event.mediaType) {
StatusCell * cell = [self.tableView dequeueReusableCellWithIdentifier:[StatusCell identifier]];
[cell configureWithModel:event];
return cell;
} else {
StatusMediaCell *cell = [self.tableView dequeueReusableCellWithIdentifier:[StatusMediaCell identifier]];
[cell configureWithModel:event];
return cell;
}
}
}
And this is how configureWithModel: looks like:
- (void)configureWithModel:(id)model {
if ([model isKindOfClass:[Event class]]) {
Event *event = model;
self.titleLabel.text = event.title;
self.locationLabel.text = event.address;
self.timeLabel.text = [self hoursLeftToDate:event.expirationDate];
UIImageView *imageAttachedToStatus = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.mediaContainerView.frame.size.width, self.mediaContainerView.frame.size.height)];
imageAttachedToStatus.contentMode = UIViewContentModeScaleAspectFill;
imageAttachedToStatus.clipsToBounds = YES;
[self getMediaAttachmentForModel:model completion:^(id attachment) {
if (attachment) {
imageAttachedToStatus.image = (UIImage *)attachment;
}
}];
[self.mediaContainerView addSubview:imageAttachedToStatus];
}
}
And, of course, you may wonder how getMediaAttachmentForModel:completion: looks like...
- (void)getMediaAttachmentForModel:(Event *)model completion:(void (^)(id attachment))completion {
NSString *mediaID = [NSString stringWithFormat:#"media%li", (long)model.eventID];
if ([[EGOCache globalCache] hasCacheForKey:mediaID]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
id cachedAttachment =[[EGOCache globalCache] objectForKey:mediaID];
dispatch_async(dispatch_get_main_queue(), ^{
completion(cachedAttachment);
});
});
} else {
[[Client sharedClient] fetchMediaThumbnailForEvent:model completion:^(id attachment, NSError *error) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[EGOCache globalCache] setObject:attachment forKey:mediaID];
dispatch_async(dispatch_get_main_queue(), ^{
completion(attachment);
});
});
}];
}
}
The proper solution would be to find/implement a cache that is able to cache images in memory, without fetching them from disk every time.
Take a look at Path's FastImageCache
I have a UITableView containing list of images, each row contains 4 UITableViewCell,
the user can select multiple images (selection is by hiding and showing an overlay image on the cell)
what i want to do is when user click delete button is to remove the selected image from my table.
Here is some of the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
ThumbnailImageCell *cell = (ThumbnailImageCell *)[tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil)
{
cell = [[[ThumbnailImageCell alloc] initWithManagedImages:[self imagesForIndexPath:indexPath] reuseIdentifier:CellIdentifier] autorelease];
}
else
{
[cell setImages:[self imagesForIndexPath:indexPath]];
}
return cell;}
-(NSArray*)imagesForIndexPath:(NSIndexPath*)_indexPath {
int index = (_indexPath.row*4);
int maxIndex = (_indexPath.row*4+3);
if(maxIndex < [self.imagesArray count]) {
return [NSArray arrayWithObjects:[self.imagesArray objectAtIndex:index],
[self.imagesArray objectAtIndex:index+1],
[self.imagesArray objectAtIndex:index+2],
[self.imagesArray objectAtIndex:index+3],
nil];
}
else if(maxIndex-1 < [self.imagesArray count]) {
return [NSArray arrayWithObjects:[self.imagesArray objectAtIndex:index],
[self.imagesArray objectAtIndex:index+1],
[self.imagesArray objectAtIndex:index+2],
nil];
}
else if(maxIndex-2 < [self.imagesArray count]) {
return [NSArray arrayWithObjects:[self.imagesArray objectAtIndex:index],
[self.imagesArray objectAtIndex:index+1],
nil];
}
else if(maxIndex-3 < [self.imagesArray count]) {
return [NSArray arrayWithObject:[self.imagesArray objectAtIndex:index]];
}
return nil;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return ceil([self.imagesArray count] / 4.0);
}
What I tried to do is the following but without any success till now
-(void)finishDeleting{
int countOfDeletedThread;
[self setEditing:YES animated:YES];
[self.tableView beginUpdates];
NSMutableIndexSet *mutableIndexSet = [[NSMutableIndexSet alloc] init];
NSMutableArray *indexToDelete = [[NSMutableArray alloc] init];
NSIndexPath *indexPath ;
for(ThumbnailImage *thumbnailImage in self.imagesArray)
{
if([thumbnailImage selected])
{
countOfDeletedThread = countOfDeletedThread+1;
indexPath = [NSIndexPath indexPathForRow:countOfDeletedThread inSection:0];
[indexToDelete addObject:indexPath];
[mutableIndexSet addIndex:indexPath.row];
}
}
[self.imagesArray removeObjectsAtIndexes:mutableIndexSet];
[self.tableView deleteRowsAtIndexPaths:indexToDelete withRowAnimation:UITableViewRowAnimationFade];
[indexToDelete release];
[mutableIndexSet release];
[self.tableView endUpdates];
[self.tableView setEditing:NO animated:YES];
[self.tableView reloadData];
[CATransaction flush];}
Any help please? I am stuck for 2 days and don't know what to do.
Thank you.
If I understand it correctly, you have 4 UIImageViews per table row, not
4 UITableViewCells. That means if you delete a subset of the images, the remaining
images will "reflow" across all rows. Therefore it does not make sense to use
beginUpdates/deleteRowsAtIndexPaths/endUpdates. You probably should just
remove the selected images from the data source array self.imagesArray,
call [self.tableView reloadData].
Removing the selected images from the array can be slightly simplified to
NSIndexSet *indexSet = [self.imagesArray indexesOfObjectsPassingTest:^BOOL(ThumbnailImage *thumbnailImage, NSUInteger idx, BOOL *stop) {
return [thumbnailImage selected];
}];
[self.imagesArray removeObjectsAtIndexes:indexSet];
Note that UICollectionView (available since iOS 6) might be better suited to display
multiple images per row.
When I load remote content via a block I update the UITableView by calling reloadData. However, I am unable to scroll on this data. I think this is because I have declared that the number of rows in the table is the length of my NSArray variable that will hold the contents of the list. When this is called though, the list has a count of zero. I would have assumed that by calling reloadData on the tableView that it would have recalculated the list size again.
Perhaps I'm missing a step along the way.
Thanks
Here is my code
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:
[NSURL URLWithString: #"MYURL"]];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:YES];
});
}
-(void) fetchedData:(NSData *)responseData
{
NSError* error;
id json = [NSJSONSerialization
JSONObjectWithData:responseData //1
options:kNilOptions
error:&error];
self.dataList = json;
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self.tableView reloadData];
});
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.dataList count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"SongCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
if([self.dataList count] > 0){
NSDictionary *chartItem = [self.dataList objectAtIndex:indexPath.row];
NSDictionary *song = [chartItem objectForKey:#"song"];
cell.textLabel.text = [song objectForKey:#"title"];
}
return cell;
}
Check that your json object (and therefore self.dataList) is not nil at the end of the asynchronous call. If it is, then [self.dataList count] will return 0.
Also, make sure that you correctly set self.dataList.dataSource.
This is working now.
The cause of the problem was the I had added a pan gesture to the UITableView
[self.view addGestureRecognizer:self.slidingViewController.panGesture];
This seems to interfere with the ability to scroll on a table. Hopefully this will help anyone who comes across this in the future.