UITableViewCell change Image without calling Reload (LazyLoading) - objective-c

I have looked at some sample LazyLoading to load the image whenever the cells are visible and the loading is complete. I noticed they do not call ReloadData How do I change the images without having to call ReloadData or reloading a specific row?
I figured that changing the image data at the address would do it, but it doesn't work.
I tried
for (NSIndexPath *indexPath in visiblePaths)
{
UIBubbleTableViewCell *aCell = (UIBubbleTableViewCell *)[self.bubbleTableView cellForRowAtIndexPath:indexPath];
NSBubbleData *cellData = [aCell data];
cellData.avatar = [self.profileImages objectForKey:[NSString stringWithFormat:#"%#", [cellData posterID]]];
}
However, this does not update the image. I have to call ReloadData after the for loop for it to load the image. If I do not call ReloadData it will not update until the cell goes off the screen again and comes back into view.

Looking at the source code for UIBubbleTableViewCell, it does not appear to handle updates to the data. However, it looks like the the data is re-applied anytime you set the cell's frame. So doing something like this might be a workaround (I don't use this library so I can't test it):
aCell.frame = aCell.frame;

Related

Asynchronously loading image into autolayout UIImageView

I've searched SO and have not seen an answer specifically for Autolayout.
I have a UITableViewCell using Autolayout. The UIImageView is constrained to have its edges 12 pt from Cell edges and the trailing edge of the Label.
Images are retrieved asynchronously and assigned to the UIImageView's image property.
[self downloadImageWithURL:[NSURL URLWithString:imageUrlString] completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded) {
weakSelf.newsImageView.image = image;
// [weakSelf.contentView layoutIfNeeded];
}
}];
If there is a blank placeholder image assigned to the image of the cell in cellForRowAtIndexPath, then the asynchronously retrieved image is loaded and visible. If the blank placeholder assignment is commented out, the asynchronously retrieved image never appears.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
SCTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableCellIdentifier];
// cell.newsImageView.image = _blankImage;
return cell;
}
I tried layoutIfNeeded (commented out above) and updateConstraintsIfNeeded to see if the image would appear but it did not work. So for now I've been putting a blank image there first, which seems like a hack, and wouldn't be flexible if relying on intrinsic content size of images who's sizes could be different. Are the constraints being optimized out of existence when the first layout occurs? Anyone know what's going on and how to resolve it?
Try calling: invalidateIntrinsicContentSize on your UIImageView in the completion handler. This should force the auto layout system to recalculate the view intrinsic size. Possibly, you could also need calling updateConstraintsIfNeeded after that (but try first without).
If this does not work and your UIImageViews have a known size, you could try subclassing UIImageView and override the -intrinsicContentSize method. Return the known size from it.
Turns out the nib somehow had two outlets referring to the same UIImageView element. One outlet didn't exist in code, yet still linked in the nib UI. Must have been a mishap in refactoring, but removing it fixes it.

Resizing UICollectionViewCell After Data Is Loaded Using invalidateLayout

This question has been asked a few times but none of the answers are detailed enough for me to understand why/how things work. For reference the other SO questions are:
How to update size of cells in UICollectionView after cell data is set?
Resize UICollectionView cells after their data has been set
Where to determine the height of a dynamically sized UICollectionViewCell?
I'm using MVC but to keep things simple lets say that I have a ViewController that in ViewWillAppear calls a web service to load some data. When the data has been loaded it calls
[self.collectionView reloadData]
The self.collectionView contains 1 UICollectionViewCell (let's call it DetailsCollectionViewCell).
When self.collectionView is being created it first calls sizeForItemAtIndexPath and then cellForItemAtIndexPath. This causes a problem for me because it's only during cellForItemAtIndexPath that I set the result of the web service to DetailsCollectionViewCell via:
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"detailsCell" forIndexPath:indexPath];
((DetailsCollectionViewCell*)cell).details = result;
DetailsCollectionViewCell has a setter for the property details that does some work that I need to happen first to know what the correct cell size should be.
Based on the linked questions above it seems like the only way to fire sizeForItemAtIndexPath after cellForItemAtIndexPath is to call
[self.collectionView.collectionViewLayout invalidateLayout];
But this where the other questions don't work for me because although it calls sizeForItemAtIndexPath and allows me to grab enough information from DetailsCollectionViewCell to set the correct height it doesn't update the UI until after the user scrolls the UICollectionView and my guess is that it has something to do with this line from the documentation
The actual layout update occurs during the next view layout update cycle.
However, i'm stumped on how to get around this. It almost feels like I need to create a static method on DetailsCollectionViewCell that I can pass the web service result to during the first sizeForItemAtIndexPath pass and then just cache that result. But i'm hoping there is a simple solution to having the UI automatically update instead.
Thanks,
p.s. - First SO question so hope i followed all the rules correctly.
Actually, from what I found, calling to invalidateLayout will cause calling sizeForItemAtIndexPath for all cells when dequeuing next cell (this is for iOS < 8.0, since 8.0 it will recalculate layout in next view layout update).
So the solution i came up with, is subclassing UICollectionView, and overriding layoutSubviews with something like this:
- (void)layoutSubviews
{
if ( self.shouldInvalidateCollectionViewLayout ) {
[self.collectionViewLayout invalidateLayout];
self.shouldInvalidateCollectionViewLayout = NO;
} else {
[super layoutSubviews];
}
}
and then calling setNeedsLayout in cellForItemAtIndexPath and setting shouldInvalidateCollectionViewLayout to YES. This worked for me in iOS >= 7.0. I also implemented estimated items size this way. Thx.
Here my case and solution.
My collectionView is in a scrollView and I want my collectionView and her cells to resize as I'm scrolling my scrollView.
So in my UIScrollView delegate method : scrollViewDidScroll :
[super scrollViewDidScroll:scrollView];
if(scrollView.contentOffset.y>0){
CGRect lc_frame = picturesCollectionView.frame;
lc_frame.origin.y=scrollView.contentOffset.y/2;
picturesCollectionView.frame = lc_frame;
}
else{
CGRect lc_frame = picturesCollectionView.frame;
lc_frame.origin.y=scrollView.contentOffset.y;
lc_frame.size.height=(3*(contentScrollView.frame.size.width/4))-scrollView.contentOffset.y;
picturesCollectionView.frame = lc_frame;
picturesCollectionViewFlowLayout.itemSize = CGSizeMake(picturesCollectionView.frame.size.width, picturesCollectionView.frame.size.height);
[picturesCollectionViewFlowLayout invalidateLayout];
}
I had to re set the collectionViewFlowLayout cell size then invalidate his layout.
Hope it helps !

AVPlayerLayer inside UITableViewCell reload issue

I've got custom UITableViewCell class which keeps AVPlayerLayer inside one of its view (called videoView). Everytime I have to load a new cell (inside cellForRow... method) I'm checking if there's AVPlayerLayer and creating new one in case it wasn't made in the past:
_playerLayer = nil ;
for(CALayer * layer in _videoView.layer.sublayers)
if ([layer isKindOfClass:[AVPlayerLayer class]]) {
_playerLayer = (AVPlayerLayer*) layer;
}
if(!_playerLayer)
{
_playerLayer.player = nil ;
_playerLayer = [AVPlayerLayer layer];
[_videoView.layer addSublayer:_playerLayer];
}
Sometimes, I want to reload whole cell while video is playing (for example in order to update height or subtitle). cellForRow... method is called again, but that time I am getting new cell (is there way to force using old cell instead of getting new instance?). I want to move state of player/player layer from one view to another. I am doing it that way:
// create AVPlayerLayer
if(iAmPlayingButCellIsReloaded)
{
_playerLayer.player = playerWhichIsPlaying;
// time resets to 0 here
}
Mostly, it's working correctly, but there are cases when video restarts (moving to the beginning) after replacing cells. It happens every first reload and rarely during next iterations.
Is there fix for that? Should I wait to finish video, and then reload cell? Can I somehow force to use old cell while reloading row? Is there better way to copy AVPlayerLayer/AVPlayer from one place to another?

ProgressView wont update in tableview

I am trying to update a custom tableview cell's progressview but the progress view wont update.
I can set the labels on the tableview cell but accessing things like progress bars or activity indicators doesnt work.
I do the following on the custom cell
cell.progressView.hidden = NO;
cell.progressView.progress = 0.5;
[cell.progressView setNeedsDisplay];
This doesnt work even though in the debugger I see the object is of the correct cell type and the progressView is allocated.
I have tried setNeedsDisplay on the cell itself as well, but no luck. What am I missing?
Calling [self tableView:tableView cellForRowAtIndexPath:indexPath]; is most likely the problem because this likely results in another cell being dequeued from the table. So you end up setting up a new cell, not the one currently being display.
Instead, do this:
CustomTableViewCell *cell = (CustomTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
This directly asks the table for the cell. This should return the currently displayed cell if the row is visible.

UITableView: load all cells

Is it possible to load all cells of an UITableView when the view is loaded so that they are not loaded when I'm scrolling?
(I would show a loading screen while doing this)
Please, it's the only way at my project (sorry too complicate to explain why ^^)
EDIT:
Okay let me explain you, what I'm definite doing:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = [NSString stringWithFormat:#"Identifier %i/%i", indexPath.row, indexPath.section];
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSDictionary *currentReading;
if (cell == nil)
{
cell = [[[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
UILabel *label;
UIView *separator;
if(indexPath.row == 0)
{
// Here I'm creating the title bar of my "table" for each section
}
else
{
int iPr = 1;
do
{
currentReading = [listData objectAtIndex:iPr-1];
iPr++;
} while (![[currentReading valueForKey:#"DeviceNo"] isEqualToString:[devicesArr objectAtIndex:indexPath.section]] ||
[readingresultsArr containsObject:[currentReading valueForKey:#"ReadingResultId"]]);
[readingresultsArr addObject:[currentReading valueForKey:#"ReadingResultId"]];
//
// ...
//
}
}
return cell;
}
My error happens in the do-while-loop:
"listData" is an array with multiple dictionaries in it.
My problem ist that when I’m scrolling my table slowly down, all is fine, but when I’m scrolling quickly to the end of the view and then I’m scrolling to the middle, I get the error that iPr is out of the array’s range. So the problem is, that the last row of the first section has already been added to the "readingresultsArr", but has not been loaded or wants to be loaded again.
That’s the reason why I want to load all cells at once.
You can cause all of the cells to be pre-allocated simply by calling:
[self tableView: self.tableView cellForRowAtIndexPath: indexPath];
for every row in your table. Put the above line in an appropriate for-loop and execute this code in viewDidAppear.
The problem however is that the tableView will not retain all of these cells. It will discard them when they are not needed.
You can get around that problem by adding an NSMutableArray to your UIViewController and then cache all the cells as they are created in cellForRowAtIndexPath. If there are dynamic updates (insertions/deletions) to your table over its lifetime, you will have to update the cache array as well.
put a uitableview on a uiscrollview
for example , you expect the height of the full list uitableview is 1000
then set the uiscrollview contentsize is 320X1000
and set the uitableview height is 1000
then all cell load their content even not visible in screen
In my case it was that I used automaticDimension for cells height and put estimatedRowHeight to small that is why tableview loaded all cells.
Some of the answers here and here suggest using automaticDimension for cells height and put mytable.estimatedRowHeight to a very low value (such as 1).
Starting with iOS 15 this approach seems not to work anymore. Hence, another way to achieve the table to "load" all cells could be by automatically scrolling to the last cell. Depending on the tables height and how many rows it can show some cells are discarded but each cell would be loaded and shown at least once.
mytable.scrollEnabled = YES;
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:cellCount - 1 inSection:0];
[mytable scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
mytable.scrollEnabled = NO;
If you want to scroll up again just scroll to the top as outlined here.
Following the comment that was made by juancazalla, I found that if you have a tableView that is using automaticDimension, loading all the cells at once can be best achieved by setting estimatedRowHeight to a low value (such as 1).