我正在创建一个房地产应用程序。我有一个屏幕,显示所有条目的列表,旁边有缩略图和一些文字。这些是我在应用程序启动时从服务器加载的。每个条目最多可以有5张照片,由于显而易见的原因我不会预先加载。我的问题是这个......当用户选择一个条目时,应用程序会从服务器下载较大的照片。根据具体情况,这可能需要几秒钟。现在该应用程序只挂了几秒钟。我不知道在列表中使用活动指示器的任何实用方法。标题空间似乎只是浪费空间,仅用于显示“正在加载...”。任何人都有任何想法,我可以做什么让用户知道加载正在进行中?
澄清:从列表中选择一个条目后,我加载另一个表视图控制器,其中包含选择列表中的照片。我目前正在使用
在ViewDidLoad中加载照片NSData *myPhoto = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURL]];
答案 0 :(得分:1)
你可以:
使用UIActivityIndicatorView
在最终加载图片的精确位置显示旋转活动指示器。
在单独的队列中下载图像。虽然以下代码使用GCD,但使用NSOperationQueue
实际上要好得多,因为在慢速网络中,使用GCD会占用所有可用的工作线程,从而对应用程序的性能产生不利影响。具有合理NSOperationQueue
(例如4或5)的maxConcurrentOperationCount
要好得多。
下载完成后,将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
混淆。
答案 1 :(得分:1)
对于我的一个项目,我为UIImageView使用了这个自定义类: https://github.com/nicklockwood/AsyncImageView
小教程位于:http://www.markj.net/iphone-asynchronous-table-image/
只需几行代码,我就设法实现图像的异步加载,缓存等等。只需看一下。