【iOS入门】UITableView加载图片

时间:2022-12-25 07:22:55

学习带图片的列表

官方 LazyTableImages demo  http://download.csdn.net/detail/jlyidianyuan/5726749

分析源码是学习的好方法。

【iOS入门】UITableView加载图片

源码结构如上,不能运行,加红框内容。

项目结构

【iOS入门】UITableView加载图片

挨个看源文件

/*
Copyright (C) 2017 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information Abstract:
Application delegate for the LazyTableImages sample.
It also downloads in the background the "Top Paid iPhone Apps" RSS feed using NSURLSession/NSURLSessionDataTask.
*/ #import "LazyTableAppDelegate.h"
#import "RootViewController.h"
#import "ParseOperation.h"
#import "AppRecord.h" // the http URL used for fetching the top iOS paid apps on the App Store
static NSString *const TopPaidAppsFeed =
@"http://phobos.apple.com/WebObjects/MZStoreServices.woa/ws/RSS/toppaidapplications/limit=75/xml"; @interface LazyTableAppDelegate () // the queue to run our "ParseOperation" NSOperationQueue解析队q列,类似java线程池
@property (nonatomic, strong) NSOperationQueue *queue; // the NSOperation driving the parsing of the RSS feed 解析类操作
@property (nonatomic, strong) ParseOperation *parser; @end #pragma mark - @implementation LazyTableAppDelegate // The app delegate must implement the window @property
// from UIApplicationDelegate @protocol to use a main storyboard file.
//
@synthesize window; // -------------------------------------------------------------------------------
// application:didFinishLaunchingWithOptions:
// -------------------------------------------------------------------------------
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//实例化联网请求
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:TopPaidAppsFeed]]; // create an session data task to obtain and the XML feed
// 使用9.0以后的联网操作类,之前可能使用NSURLConnection
NSURLSessionDataTask *sessionTask =
[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// 获取联网状态代码 200 ,300,400,500等
// in case we want to know the response status code
//NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode]; if (error != nil)//如果有错误
{
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO; if ([error code] == NSURLErrorAppTransportSecurityRequiresSecureConnection)
{
// if you get error NSURLErrorAppTransportSecurityRequiresSecureConnection (-1022),
// then your Info.plist has not been properly configured to match the target server.
//错误码含意查询:https://www.meiwen.com.cn/subject/jjjdnttx.html
//在工程的 info.plist 文件中添加 https允许
abort();
}
else
{
//其它错误交给handleError处理
[self handleError:error];
}
}];
}
else
{
//没错误往下走
// create the queue to run our ParseOperation 初始化解析队列
self.queue = [[NSOperationQueue alloc] init]; // create an ParseOperation (NSOperation subclass) to parse the RSS feed data so that the UI is not blocked 初始化解析操作类
_parser = [[ParseOperation alloc] initWithData:data];
//__weak 弱引用,这里使用弱引用,防止线程引用对象造成内存泄漏。要回收LazyTableAppDelegate?真如此,程序已经结束。没必要了。
__weak LazyTableAppDelegate *weakSelf = self;
//添加解析错误时回调的block ,block 类似java interface 或者理解为内部类。好比java OnClickLister.
self.parser.errorHandler = ^(NSError *parseError) {
//dispatch_async GCD 方式在主线程上操作的方法。相关学习:线程如何操作主线程的3种方法.
dispatch_async(dispatch_get_main_queue(), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
//扔给本类的handleError方法处理。
[weakSelf handleError:parseError];
});
}; // referencing parser from within its completionBlock would create a retain cycle
__weak ParseOperation *weakParser = self.parser; //解析完成返回处理
self.parser.completionBlock = ^(void) {
// The completion block may execute on any thread. Because operations
// involving the UI are about to be performed, make sure they execute on the main thread.
//结果需要在主线程更新
dispatch_async(dispatch_get_main_queue(), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (weakParser.appRecordList != nil)
{
//如果返回的解析集合不为空
RootViewController *rootViewController =
(RootViewController *)[(UINavigationController *)weakSelf.window.rootViewController topViewController];
//RootViewController : UITableViewController 解析数据给到tableview
rootViewController.entries = weakParser.appRecordList; // tell our table view to reload its data, now that parsing has completed 更新tableview
[rootViewController.tableView reloadData];
}
}); // we are finished with the queue and our ParseOperation
weakSelf.queue = nil;
}; [self.queue addOperation:self.parser]; // this will start the "ParseOperation"
}
}]; [sessionTask resume]; // show in the status bar that network activity is starting
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES; return YES;
} // -------------------------------------------------------------------------------
// handleError:error
// Reports any error with an alert which was received from connection or loading failures.
// -------------------------------------------------------------------------------
- (void)handleError:(NSError *)error
{
NSString *errorMessage = [error localizedDescription]; // alert user that our current record was deleted, and then we leave this view controller
//
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Cannot Show Top Paid Apps", @"")
message:errorMessage
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *OKAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
// dissmissal of alert completed
}]; [alert addAction:OKAction];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} @end
/*
Copyright (C) 2017 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information Abstract:
Controller for the main table view of the LazyTable sample.
This table view controller works off the AppDelege's data model.
produce a three-stage lazy load:
1. No data (i.e. an empty table)
2. Text-only data from the model's RSS feed
3. Images loaded over the network asynchronously This process allows for asynchronous loading of the table to keep the UI responsive.
Stage 3 is managed by the AppRecord corresponding to each row/cell. Images are scaled to the desired height.
If rapid scrolling is in progress, downloads do not begin until scrolling has ended.
*/ #import "RootViewController.h"
#import "AppRecord.h"
#import "IconDownloader.h" #define kCustomRowCount 7 static NSString *CellIdentifier = @"LazyTableCell";
static NSString *PlaceholderCellIdentifier = @"PlaceholderCell"; #pragma mark - @interface RootViewController () <UIScrollViewDelegate> // the set of IconDownloader objects for each app
@property (nonatomic, strong) NSMutableDictionary *imageDownloadsInProgress; @end #pragma mark - @implementation RootViewController // -------------------------------------------------------------------------------
// viewDidLoad
// -------------------------------------------------------------------------------
- (void)viewDidLoad
{
[super viewDidLoad]; _imageDownloadsInProgress = [NSMutableDictionary dictionary];
} // -------------------------------------------------------------------------------
// terminateAllDownloads
// -------------------------------------------------------------------------------
- (void)terminateAllDownloads
{
//停止下载
// terminate all pending download connections
NSArray *allDownloads = [self.imageDownloadsInProgress allValues];
//数组的makeObjectsPerformSelector:SEL方法来减少自己写循环代码.让数组中每个对象都执行 cancelDownload 方法
[allDownloads makeObjectsPerformSelector:@selector(cancelDownload)];
//从字典移除记录。
[self.imageDownloadsInProgress removeAllObjects];
} // -------------------------------------------------------------------------------
// dealloc
// If this view controller is going away, we need to cancel all outstanding downloads.
// -------------------------------------------------------------------------------
- (void)dealloc
{
// terminate all pending download connections
[self terminateAllDownloads];
} // -------------------------------------------------------------------------------
// didReceiveMemoryWarning
// -------------------------------------------------------------------------------
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning]; // terminate all pending download connections
[self terminateAllDownloads];
} #pragma mark - UITableViewDataSource // -------------------------------------------------------------------------------
// tableView:numberOfRowsInSection:
// Customize the number of rows in the table view.
// -------------------------------------------------------------------------------
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSUInteger count = self.entries.count; // if there's no data yet, return enough rows to fill the screen
if (count == )
{
return kCustomRowCount;
}
return count;
} // -------------------------------------------------------------------------------
// tableView:cellForRowAtIndexPath:
// -------------------------------------------------------------------------------
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil; NSUInteger nodeCount = self.entries.count; if (nodeCount == && indexPath.row == )
{
// add a placeholder cell while waiting on table data 在storyboard中定义的加载中...
cell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier forIndexPath:indexPath];
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; // Leave cells empty if there's no data yet
if (nodeCount > )
{
// Set up the cell representing the app
AppRecord *appRecord = (self.entries)[indexPath.row]; cell.textLabel.text = appRecord.appName;
cell.detailTextLabel.text = appRecord.artist; // Only load cached images; defer new downloads until scrolling ends
//这里注释得很明白,只加载cached(已缓存的)图片。
//【defer】 英[dɪˈfɜː(r)] ,美[dɪˈfɜːr] ,v.推迟; 延缓; 展期;
if (!appRecord.appIcon)//如果为空
{
if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
{
//如果没有拖动,也没在惯性滑动。开始下载。
[self startIconDownload:appRecord forIndexPath:indexPath];
}
// if a download is deferred or in progress, return a placeholder image
//如果正在下载中给一个默认图。
cell.imageView.image = [UIImage imageNamed:@"Placeholder.png"];
}
else
{
// (self.entries)[indexPath.row]; 直接使用
cell.imageView.image = appRecord.appIcon;
}
}
} return cell;
} #pragma mark - Table cell image support // -------------------------------------------------------------------------------
// startIconDownload:forIndexPath: 第N个Cell的下载图片资源
// -------------------------------------------------------------------------------
- (void)startIconDownload:(AppRecord *)appRecord forIndexPath:(NSIndexPath *)indexPath
{
IconDownloader *iconDownloader = (self.imageDownloadsInProgress)[indexPath];
if (iconDownloader == nil)
{
iconDownloader = [[IconDownloader alloc] init];
iconDownloader.appRecord = appRecord;
[iconDownloader setCompletionHandler:^{ //图片加载完成时回调本段代码 UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; // Display the newly loaded image
cell.imageView.image = appRecord.appIcon; // Remove the IconDownloader from the in progress list.
// This will result in it being deallocated. 从字典中把下载对象移除。之前是有记录要下载的。
[self.imageDownloadsInProgress removeObjectForKey:indexPath]; }];
//在开始下载后,把下载对象记录到字典管理。
(self.imageDownloadsInProgress)[indexPath] = iconDownloader;
[iconDownloader startDownload];
}
} // -------------------------------------------------------------------------------
// loadImagesForOnscreenRows
// This method is used in case the user scrolled into a set of cells that don't
// have their app icons yet.
// -------------------------------------------------------------------------------
- (void)loadImagesForOnscreenRows
{
if (self.entries.count > )
{
//获取tableview 当前可见的编号。indexPathsForVisibleRows
NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *indexPath in visiblePaths)
{
AppRecord *appRecord = (self.entries)[indexPath.row]; if (!appRecord.appIcon)
// Avoid the app icon download if the app already has an icon
// 如果没有图片 开始下载图片
{
[self startIconDownload:appRecord forIndexPath:indexPath];
}
}
}
} #pragma mark - UIScrollViewDelegate // -------------------------------------------------------------------------------
// scrollViewDidEndDragging:willDecelerate:
// Load images for all onscreen rows when scrolling is finished.
// tableview 滚动结束事件回调。停下来时,加载当前屏的图片。
// -------------------------------------------------------------------------------
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
[self loadImagesForOnscreenRows];
}
} // -------------------------------------------------------------------------------
// scrollViewDidEndDecelerating:scrollView
// When scrolling stops, proceed to load the app icons that are on screen.
// -------------------------------------------------------------------------------
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
//惯性滑动结束时,开始加载图片,同上。
[self loadImagesForOnscreenRows];
} @end
/*
Copyright (C) 2017 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information Abstract:
Helper object for managing the downloading of a particular app's icon.
It uses NSURLSession/NSURLSessionDataTask to download the app's icon in the background if it does not
yet exist and works in conjunction with the RootViewController to manage which apps need their icon.
*/ #import "IconDownloader.h"
#import "AppRecord.h" #define kAppIconSize 48 @interface IconDownloader () @property (nonatomic, strong) NSURLSessionDataTask *sessionTask; @end #pragma mark - @implementation IconDownloader // -------------------------------------------------------------------------------
// startDownload
// -------------------------------------------------------------------------------
- (void)startDownload
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.appRecord.imageURLString]]; // create an session data task to obtain and download the app icon
_sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// 这里使用NSURLSessionDataTask 进行数据加载。@NSURLSession @NSURLSessionDataTask 属一个知识体系,可以系统学习。
//_sessionTask 声明为了属性,目的是可以进行控制。 // in case we want to know the response status code
//NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode]; if (error != nil)
{
if ([error code] == NSURLErrorAppTransportSecurityRequiresSecureConnection)
{
// if you get error NSURLErrorAppTransportSecurityRequiresSecureConnection (-1022),
// then your Info.plist has not been properly configured to match the target server.
//
abort();
}
} //以上代码好像是规范格式。 [[NSOperationQueue mainQueue] addOperationWithBlock: ^{ // Set appIcon and clear temporary data/image
UIImage *image = [[UIImage alloc] initWithData:data]; if (image.size.width != kAppIconSize || image.size.height != kAppIconSize)
{
//对图片进行裁剪操作。
CGSize itemSize = CGSizeMake(kAppIconSize, kAppIconSize);
UIGraphicsBeginImageContextWithOptions(itemSize, NO, 0.0f);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
self.appRecord.appIcon = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
else
{
self.appRecord.appIcon = image;
} //不如何image给到了appRecord数据模型。 // call our completion handler to tell our client that our icon is ready for display
//通知加载完成
if (self.completionHandler != nil)
{
self.completionHandler();
}
}];
}]; //开启task
[self.sessionTask resume];
} // -------------------------------------------------------------------------------
// cancelDownload
// -------------------------------------------------------------------------------
- (void)cancelDownload
{
[self.sessionTask cancel];
_sessionTask = nil;
} @end

代码比较简单

1.appdelegate 去下载数据并解析。并更新tableview

//RootViewController : UITableViewController 解析数据给到tableview

rootViewController.entries = weakParser.appRecordList;

2.使用 model给tableview展示。

@property (nonatomic, strong) NSString *appName;

@property (nonatomic, strong) UIImage *appIcon;

@property (nonatomic, strong) NSString *artist;

@property (nonatomic, strong) NSString *imageURLString;

@property (nonatomic, strong) NSString *appURLString;

appIcon开始时为空,当展示在屏幕时,起线程去加载。并记录在

self.imageDownloadsInProgress

第一个下载对应一个线程对象。

3.这个代码  UIImage 会不数增加,数量够多,内存肯定溢出。

所以需要图片的加载策略。图片的三级缓存可系统独立学习。