zoukankan      html  css  js  c++  java
  • 【iOS入门】UITableView加载图片

    学习带图片的列表

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

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

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

    项目结构

    挨个看源文件

    /*
     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 == 0)
        {
            return kCustomRowCount;
        }
        return count;
    }
    
    // -------------------------------------------------------------------------------
    //    tableView:cellForRowAtIndexPath:
    // -------------------------------------------------------------------------------
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = nil;
        
        NSUInteger nodeCount = self.entries.count;
        
        if (nodeCount == 0 && indexPath.row == 0)
        {
            // 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 > 0)
            {
                // 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 > 0)
        {
            //获取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 会不数增加,数量够多,内存肯定溢出。

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

  • 相关阅读:
    MYSQL 使用DBI
    mysql 更改数据目录
    Error Code: 1360
    org.hibernate.exception.GenericJDBCException: Could not open connection
    Error: Dynamic is undefined
    Unhandled event loop exception No more handles
    Count:858org.apache.jasper.JasperException: Unable to compile class for JSP
    Mysql --skip-grant-table
    第24章-启动 停止 和配置mysql
    Linux 6.2 x86_64 安装ipvs
  • 原文地址:https://www.cnblogs.com/mamamia/p/12273766.html
Copyright © 2011-2022 走看看