0

不動産アプリを作成しています。サムネイルと横に小さなテキストを含むすべてのエントリのリストを表示する画面があります。これらは、アプリの起動時にサーバーからロードしました。各エントリには最大 5 枚の写真を含めることができますが、明らかな理由で事前にロードしていません。私の問題はこれです... ユーザーがエントリを選択すると、アプリはサーバーから大きな写真をダウンロードします。状況によっては、これに数秒かかることがあります。現在、アプリはその数秒間だけハングします。リストでアクティビティ インジケーターを使用する実用的な方法を知りません。ヘッダースペースは、「読み込み中…」を表示するためだけに使用する無駄なスペースのように思えます。ロードが進行中であることをユーザーに知らせるために私ができることについて、何かアイデアはありますか?

明確化: リストからエントリが選択されると、選択リストに写真がある別の Table View Controller をロードします。私は現在、ViewDidLoadに写真をロードしています

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

2 に答える 2

1

私のプロジェクトの 1 つで、UIImageView にこのカスタム クラスを使用しました: https://github.com/nicklockwood/AsyncImageView

小さなチュートリアルはここにあります: http://www.markj.net/iphone-asynchronous-table-image/

ほんの数行のコードで、画像の非同期読み込み、キャッシュなどを実装することができました。

于 2012-09-18T20:40:46.173 に答える
1

あなたはできる:

  1. UIActivityIndicatorView画像が最終的に読み込まれる正確な場所に回転アクティビティ インジケーターを表示するために使用します。

  2. 別のキューでイメージをダウンロードします。以下のコードではGCDNSOperationQueueを使用していますが、低速ネットワークでは GCD を使用すると使用可能なすべてのワーカー スレッドが消費され、アプリのパフォーマンスに悪影響を及ぼす可能性があるため、実際には使用する方がはるかに優れています。NSOperationQueue妥当な値(4 または 5 など)の A のmaxConcurrentOperationCount方がはるかに優れています。

  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;
    }
});

これは method を使用しているため、UITableViewmethodとcellForRowAtIndexPath混同しないでください。UITableViewControllertableView:cellForRowAtIndexPath

于 2012-09-18T20:01:07.123 に答える