preface
We usually use UITableView a lot, so optimization of lists is also a concern. A lot of times, we set UIImageView to scale the width and height of the image, and then use scaleAspectFill and clipsToBounds to keep the image in shape. This is very efficient development, after all, we have fixed the image width and height.
What if the product requires images to be displayed to true scale? If the server has a return width and height, that’s fine, but if not, how should we do it?
Let’s explore it.
Image adaptive proportions
Our approach is commonly used UITableViewAutomaticDimension.
In the past, we used to add images directly with sd_setImageWithURL, and that’s how we get the width and height of the image.
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
completed:(nullable SDExternalCompletionBlock)completedBlock
Copy the code
And when we’re done, we can get UIImage, so we know the size of the image. We can then scale the height of the image and change the height by updating constraints.
The general approach is as follows:
[_imageView sd_setImageWithURL:[NSURL URLWithString:model.urlStr] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL _Nullable imageURL) {if (image) {CGSize imageSize = image.size; CGFloat maxWidth = kscreen_width-32; CGFloat height = imagesize.height * maxWidth/imagesize.width; [self.imageView mas_updateConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(height); }]; } }];Copy the code
Then when we run the code, we find that the height of the image doesn’t show the height we want, or even the height we want.
Obviously the height of the image doesn’t work. When you think about it, we made an asynchronous call to load the image, and waiting for the asynchronous result to come back and call mas_updateConstraints does not trigger the heightForRowAtIndexPath agent, so why update height?
In that case, let’s reload the list.
[cell setHeightBlock:^(CGFloat imageHeight){ [tableView beginUpdates]; [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; [tableView endUpdates]; }];Copy the code
Once we’ve updated mas_updateConstraints, at this point we can simply refresh the list and resolve the problem.
At this time we re-run, the effect is ok, but in the back and forth scrolling, found a bit of a card. Obviously, we need to refresh the cell every time we scroll.
If we have a cache and we know the height of the image, then we don’t need to reloadRows satIndexPaths.
So I optimized it again
/ / is there a cache BOOL hasCache = [[SDImageCache sharedImageCache] diskImageDataExistsWithKey: model, urlStr]; [_ImageView sd_setImageWithURL:[NSURL URLWithString:model.urlStr] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { if (image) { CGSize imageSize = image.size; CGFloat maxWidth = kSCREEN_WIDTH - 32; CGFloat height = imageSize.height * maxWidth / imageSize.width; [self.imageView mas_updateConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(height); }]; If (! HasCache && self.heightBlock) {self.heightBlock(height);}}}];Copy the code
If we find that there are cached images, we don’t refresh the list. Sd_setImageWithURL also reads the image from the cache, so it is not loaded asynchronously, so there is no need to refresh the current cell again.
XHWebImageAutoSize
Some people say, well, it seems like a lot of trouble to write it like that, but there is no way to encapsulate it, and there is.
That is the third-party library XHWebImageAutoSize, which is actually written using SDWebImage to optimize the operation.
[_imageView sd_setImageWithURL:[NSURL URLWithString:model.urlStr] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {if (image) {/** Cache image size */ [XHWebImageAutoSize storeImageSize:image forURL:imageURL completed:^(BOOL result) { /** reload */ if(result && self.heightBlock) { self.heightBlock(0) } ]; } }];Copy the code
After caching the image, refresh the cell again.
[cell setHeightBlock:^(CGFloat imageHeight) {
[tableView xh_reloadDataForURL:[NSURL URLWithString:model.urlStr]];
}];
Copy the code
Then there is the reload height.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
ImageModel *model = _dataArray[indexPath.row];
return [XHWebImageAutoSize imageHeightForURL:[NSURL URLWithString:model.urlStr] layoutWidth:[UIScreen mainScreen].bounds.size.width-32 estimateHeight:200] + 50;
}
Copy the code
For example, there is a title in the cell, which is the picture + other heights.
This can also achieve image adaptive height.
Only the contents of the current screen are loaded
The list of images is so large that it keeps slipping and sliding, and the loading speed of images can’t keep pace with the hand speed. It feels a bit stuck, so we can only load the content of the current screen. When we swipe, we don’t load, and when the list stops, we load the current screen again.
So we’re going to add an isLoad parameter to our model, if it’s true, we’re going to load it.
Start by adding a method that loads the cell on the current screen.
-(void)loadCurrentCells{ NSArray * array = [self.tableView indexPathsForVisibleRows]; for (NSIndexPath *indexPath in array) { JJTableViewCell * cell = (JJTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]; ImageModel *model = _dataArray[indexPath.row]; // Set model.isLoad = YES; / / configuration cell [cell configCellWithImageModel: model]; }}Copy the code
The cell determines the isLoad of the Model.
- (void)configCellWithImageModel:(ImageModel *)model { if (model.isLoad) { [_imageView sd_setImageWithURL:[NSURL URLWithString:model.urlStr]]; }else { _imageView.image = [UIImage imageNamed:@"default_images_icon"]; }}Copy the code
If not loading, we can set the image occupation first.
This way, we just listen when the slide stops, and we set the current page to load.
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (! decelerate) { [self loadCurrentCells]; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self loadCurrentCells]; }Copy the code
Of course, when we first reloaData, all isloads are false, so we need to load the current cell once.
[self.tableView reloadData];
[self loadCurrentCells];
Copy the code
In this way, we can keep swiping without asynchronously loading images, and wait until the slide stops loading images on the current screen.
Of course, instead of writing it this way, we can also do it with RunLoop.
preload
The so-called preload, is always sliding, when we turn the page, preload the data out, so that the user feels like there is always data. There is no more pull-up loading.
The general idea is that preloading is triggered when the sliding-distance ratio reaches %90 of the total sliding-distance (not fixed).
I’m not going to write it here, but I’m going to get the link from the big guy: iOS development TableView Web request/display preloading
The latter
Of course there are a lot more to UITableView optimization. If you have any different ideas about the code here, please leave a comment.