0

我正在创建一个房地产应用程序。我有一个屏幕,它显示所有条目的列表,旁边有一个缩略图和一个小文本。这些是我在应用程序启动时从服务器加载的。每个条目最多可以有 5 张照片,出于明显的原因,我没有预先加载这些照片。我的问题是……当用户选择一个条目时,应用程序会从服务器下载较大的照片。根据具体情况,这可能需要几秒钟。现在,该应用程序只挂起那几秒钟。我不知道在列表中使用活动指示器的任何实用方法。标题空间似乎只是用于显示“正在加载...”的浪费空间。任何人都知道我可以做些什么来让用户知道加载正在进行中?

澄清:一旦从列表中选择了一个条目,我就会加载另一个 Table View Controller,它的选择列表中有照片。我目前使用 ViewDidLoad 加载照片

NSData *myPhoto = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
4

2 回答 2

1

你可以:

  1. 用于UIActivityIndicatorView在最终加载图像的精确位置显示旋转活动指示器。

  2. 在单独的队列中下载图像。虽然下面的代码使用GCD,但实际上使用起来要好得多,NSOperationQueue因为在慢速网络上,使用 GCD 会消耗所有可用的工作线程,从而对应用程序的性能产生不利影响。ANSOperationQueue带一个合理的maxConcurrentOperationCount(比如 4 或 5)要好得多。

  3. 下载完成后,将 UI 的更新分派回主队列(例如关闭活动指示器并设置图像)。

这是来自画廊应用程序的示例代码,展示了您可以如何做到这一点。这可能比您需要的更复杂,并且可能很难通过剪切和粘贴来重新调整用途,但该loadImage方法显示了解决方案的基本元素。

@interface MyImage : NSObject

@property (nonatomic, strong) NSString *urlString;
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
@property (nonatomic, strong) UIView *view;
@property BOOL loading;
@property BOOL loaded;

@end

@implementation MyImage

// I find that I generally can get away with loading images in main queue using Documents
// cache, too, but if your images are not optimized (e.g. are large), or if you're supporting
// older, slower devices, you might not want to use the Documents cache in the main queue if
// you want a smooth UI. If this is the case, change kUseDocumentsCacheInMainQueue to NO and
// then use the Documents cache only in the background thread.

#define kUseDocumentsCacheInMainQueue NO

- (id)init
{
    self = [super init];
    if (self)
    {
        _view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, IMAGE_WIDTH, IMAGE_HEIGHT)];
        _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, IMAGE_WIDTH, IMAGE_HEIGHT)];
        _imageView.contentMode = UIViewContentModeScaleAspectFill;
        _imageView.clipsToBounds = YES;
        [_view addSubview:_imageView];
        _loading = NO;
        _loaded = NO;
    }
    return self;
}

- (void)loadImage:(dispatch_queue_t)queue
{
    if (self.loading)
        return;

    self.loading = YES;

    ThumbnailCache *cache = [ThumbnailCache sharedManager];

    if (self.imageView.image == nil)
    {
        // I've implemented a caching system that stores images in my Documents folder
        // as well as, for optimal performance, a NSCache subclass. Whether you go through
        // this extra work is up to you

        UIImage *imageFromCache = [cache objectForKey:self.urlString useDocumentsCache:kUseDocumentsCacheInMainQueue];
        if (imageFromCache)
        {
            if (self.activityIndicator)
            {
                [self.activityIndicator stopAnimating];
                self.activityIndicator = nil;
            }

            self.imageView.image = imageFromCache;
            self.loading = NO;
            self.loaded = YES;
            return;
        }

        // assuming we haven't found it in my cache, then let's see if we need to fire
        // up the spinning UIActivityIndicatorView

        if (self.activityIndicator == nil)
        {
            self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
            self.activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
            [self.view addSubview:self.activityIndicator];
        }
        [self.activityIndicator startAnimating];

        // now, in the background queue, let's retrieve the image

        dispatch_async(queue, ^{
            if (self.loading)
            {
                UIImage *image = nil;

                // only requery cache for Documents cache if we didn't do so in the main 
                // queue for small images, doing it in the main queue is fine, but apps 
                // with larger images, you might do this in this background queue.

                if (!kUseDocumentsCacheInMainQueue)
                    image = [cache objectForKey:self.urlString useDocumentsCache:YES];

                // if we haven't gotten the image yet, retrieve it from the remote server

                if (!image)
                {
                    NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:self.urlString]];

                    if (data)
                    {
                        image = [UIImage imageWithData:data];

                        // personally, I cache my image to optimize future access ... you might just store in the Documents folder, or whatever

                        [cache setObject:image forKey:self.urlString data:data]; 
                    }
                }

                // now update the UI in the main queue

                dispatch_async(dispatch_get_main_queue(), ^{
                    if (self.loading)
                    {
                        [self.activityIndicator stopAnimating];
                        self.activityIndicator = nil;
                        self.imageView.image = image;
                        self.loading = NO;
                        self.loaded = YES;
                    }
                });
            }
        });
    }
}

// In my gallery view controller, I make sure to unload images that have scrolled off
// the screen. And because I've cached the images, I can re-retrieve them fairly quickly.
// This sort of logic is critical if you're dealing with *lots* of images and you want 
// to be responsible with your memory.

- (void)unloadImage
{
    // remove from imageview, but not cache

    self.imageView.image = nil;

    self.loaded = NO;
    self.loading = NO;
}

@end

顺便说一句,如果您正在下载的图像在UIImageView最后UITableViewCell更新回表中,则可能需要检查单元格是否仍在屏幕上(以确保它没有出队,因为UITableViewCell滚出屏幕)。在这种情况下,成功下载图像后的最终 UI 更新可能会执行以下操作:

dispatch_async(dispatch_get_main_queue(), ^{

    // if the cell is visible, then set the image

    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
    if (cell)
    {
        cell.imageView.image = image;
    }
});

请注意,这是使用UITableView方法cellForRowAtIndexPath,不应与UITableViewController方法混淆tableView:cellForRowAtIndexPath

于 2012-09-18T20:01:07.123 回答
1

对于我的一个项目,我将这个自定义类用于 UIImageView: https ://github.com/nicklockwood/AsyncImageView

小教程位于:http: //www.markj.net/iphone-asynchronous-table-image/

只需几行代码,我就成功实现了图像的异步加载、缓存等。看看吧。

于 2012-09-18T20:40:46.173 回答