UICollectionView scrolling is slow

2019-02-01 14:37发布

I have just created a UICollectionView in which The user can add images from their phone to the photo album feature in the app. I have the images save to the a subdirectory in the documents directory so more can be added and removed. However, when I scroll up and down the collection view, it is very laggy.

How can I make the scroll nice and smooth?

My Code: The first 16 images are preset images everything after that are from a subdirectory in documents Directory

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Custom" forIndexPath:indexPath];
    //Current index number
    int index=indexPath.section * noOfSection + indexPath.row;
    //Check if its the preset photos
    if(index<16){
        NSString *name=[recipePhotos objectAtIndex:indexPath.section * noOfSection + indexPath.row];
        cell.imageView.image=[UIImage imageNamed:name];
    }

//not preset photos, so retrieve the photos the user added
    else {
        NSData *data= [NSData dataWithContentsOfFile:[recipePhotos objectAtIndex:index]];
        UIImage *theImage=[UIImage imageWithData:data];

        cell.imageView.image=theImage;
        data=nil;
    }

    return cell;
}

Time Profiler gave me this

Running Time    Self        Symbol Name
568.0ms   63.1% 0.0     Main Thread  0x4048
320.0ms   35.5% 0.0     _pthread_start  0x405e
320.0ms   35.5% 0.0      thread_start
320.0ms   35.5% 0.0       _pthread_start
320.0ms   35.5% 0.0        0x1084be960
310.0ms   34.4% 1.0         0x1084be6f0
7.0ms    0.7%   0.0         mach_msg
2.0ms    0.2%   2.0         objc_msgSend
1.0ms    0.1%   1.0         -[NSAutoreleasePool release]
4.0ms    0.4%   0.0     _dispatch_mgr_thread  0x4052
4.0ms    0.4%   0.0      _dispatch_mgr_thread
4.0ms    0.4%   0.0       _dispatch_mgr_invoke
4.0ms    0.4%   4.0        kevent
3.0ms    0.3%   0.0     _dispatch_worker_thread2  0x62b24
3.0ms    0.3%   1.0      start_wqthread
3.0ms    0.3%   0.0     _dispatch_worker_thread2  0x62a84
3.0ms    0.3%   0.0      start_wqthread
3.0ms    0.3%   0.0       _pthread_wqthread
3.0ms    0.3%   0.0        _dispatch_worker_thread2
3.0ms    0.3%   0.0         _dispatch_queue_invoke
3.0ms    0.3%   0.0          _dispatch_queue_drain
3.0ms    0.3%   0.0           _dispatch_client_callout
2.0ms    0.2%   0.0            my_io_execute_passive_block
1.0ms    0.1%   0.0             __86-[NSPersistentUIManager writePublicPlistWithOpenWindowIDs:optionallyWaitingUntilDone:]_block_invoke_0835
1.0ms    0.1%   0.0              -[NSPersistentUIManager writePublicPlistData:]
1.0ms    0.1%   0.0               -[NSURL(NSURLPathUtilities) URLByAppendingPathComponent:]
1.0ms    0.1%   0.0                -[NSURL getResourceValue:forKey:error:]
1.0ms    0.1%   0.0                 CFURLCopyResourcePropertyForKey
1.0ms    0.1%   0.0             __block_global_2
1.0ms    0.1%   0.0              -[NSPersistentUIManager writeRecords:withWindowInfos:flushingStaleData:]
1.0ms    0.1%   0.0            _dispatch_call_block_and_release
1.0ms    0.1%   0.0             0x1084b8580
1.0ms    0.1%   0.0              mach_msg_send
1.0ms    0.1%   0.0               mach_msg
1.0ms    0.1%   1.0                mach_msg_trap
1.0ms    0.1%   0.0     _pthread_struct_init  0x62a83
1.0ms    0.1%   0.0      start_wqthread
1.0ms    0.1%   0.0       _pthread_wqthread
1.0ms    0.1%   1.0        _pthread_struct_init
1.0ms    0.1%   0.0     start_wqthread  0x62a7f

3条回答
甜甜的少女心
2楼-- · 2019-02-01 15:15

You will need to make a approach like the one you need to do in the tableviews, you will need to reuse the views, like you reuse yours cells in table view.

A really good tutorial is this one from Ray Wenderlich:

In the first part you have the basic, in the second one they talk about the reusable views, you, take a look at the link:

http://www.raywenderlich.com/22417/beginning-uicollectionview-in-ios-6-part-22

Edit

Example to load images async:

Create at your cell a method loadImageFromFile for example, that receives the path you will cal it this way:

[cell loadImageFromFile:[recipePhotos objectAtIndex:index]];

And then will look like (maybe you need to adapt something ...):

- (void) loadImageFromFile:(NSString*)path{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
        NSData *data= [NSData dataWithContentsOfFile:path];
        UIImage *theImage=[UIImage imageWithData:data];

        dispatch_async(dispatch_get_main_queue(), ^{
            cell.imageView.image=theImage;
    });
});
查看更多
冷血范
3楼-- · 2019-02-01 15:20

@ggrana has the right idea. Loading async will definitely help. However, you're still doing redundant work if you load from file every time. One thing to consider would be augmenting the async loading with an NSCache. It's basically an NSDictionary but does memory management itself and dumps data when there is memory pressure.

So if you have the memory budget, you can actually make your thumbnails on the fly (so you don't have to hard code a size) and store them in the cache. That way your the images only pop in the first time. Every time after that, they load instantly.

You can use it like so:

@implementation ...
{
    NSCache * _johnny;  // get it?
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    [cell setImage:nil]; // since they are reused, prevents showing an old image
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         UIImage * sticker = [self thumbnailOfSize:CGSizeMake(desiredImageWidth, desiredImageHeight) 
                                          forIndex:[indexPath row]];
         dispatch_async(dispatch_get_main_queue(), ^{
             [cell setImage:sticker];
         });
    });
}

// load image from disk and cache thumbnail version
- (UIImage*) thumbnailOfSize:(CGSize)size forIndex:(NSInteger)index
{
    NSString * cacheKey = [NSString stringWithFormat:@"%@ %d", NSStringFromCGSize(size), index];
    UIImage * image = [_johnny objectForKey:cacheKey];

    if (!image) {
        image = [UIImage imageWithContentsOfFile:_imagePaths[index]];

        float desiredWidth = size.width;
        float desiredHeight = size.height;
        float actualHeight = image.size.height;
        float actualWidth = image.size.width;
        float imgRatio = actualWidth / actualHeight;
        float maxRatio = desiredWidth / desiredHeight;

        if(imgRatio != maxRatio) {
            if(imgRatio < maxRatio) {
                imgRatio = desiredHeight / actualHeight;
                actualWidth = imgRatio * actualWidth;
                actualHeight = desiredHeight;
            } else {
                imgRatio = desiredWidth / actualWidth;
                actualHeight = imgRatio * actualHeight;
                actualWidth = desiredWidth;
            }
        }

        CGRect rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight);
        UIGraphicsBeginImageContextWithOptions(rect.size, FALSE, 0); // do right thing for retina
        [image drawInRect:rect];
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        // here's johnny
        [_johnny setObject:image forKey:cacheKey];
    }

    return image;
}
查看更多
可以哭但决不认输i
4楼-- · 2019-02-01 15:33

So After some messing around, I've figured out that the problem was based on a few factors.

One- The images for the thumbnails were too big, so what I did was made a seperate array of images with smaller image sizes that would fit the cell. Two- With the help from @ggrana, opening a seperate thread sped up the process to and made it less laggy. Three- I also found that having an array of images rather than image locations was faster-- only problem is it takes up more memory.

查看更多
登录 后发表回答