首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >下载进度来自URL的图像查看器

下载进度来自URL的图像查看器
EN

Code Review用户
提问于 2014-09-06 18:03:17
回答 1查看 1K关注 0票数 3

我已经创建了一个imageViewer,它将使用一个String数组进行初始化,并将根据需要下载并显示图像。

在回顾的同时,你能给我一个关于在didReceiveMemoryWarning里做什么的建议吗?

PagedScrollViewController

代码语言:javascript
复制
class PagedScrollViewController:UIViewController,UIScrollViewDelegate {

    var imagesUrls:[String]?
    var imagesDownloads:[ImageDownload?]      = [ImageDownload]()
    var downloadsResponse:[DownloadResponse?] = [DownloadResponse?]()

    var pageImages:[UIImage?]          = [UIImage]()
    var pageViews:[ImageDownloadView?] = [ImageDownloadView]()

    var scrollView:UIScrollView   = UIScrollView()
    var pageControl:UIPageControl = UIPageControl()

    var viewingPage = -1

    init(images:[String])  {
        super.init(nibName: nil, bundle: nil)

        self.imagesUrls = images
        self.title = "Image viewer";

        self.view.backgroundColor = UIColor.blackColor()

        var pageCount = self.imagesUrls!.count

        self.scrollView.pagingEnabled = true
        self.scrollView.delegate = self
        self.scrollView.showsHorizontalScrollIndicator = false
        self.scrollView.showsVerticalScrollIndicator = false

        // Set up the page control
        self.pageControl.currentPage = 0;
        self.pageControl.numberOfPages = pageCount;

        //Add
        self.pageControl.setTranslatesAutoresizingMaskIntoConstraints(false)
        self.scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)

        self.view.addSubview(self.pageControl)
        self.view.addSubview(self.scrollView)

        //Set layout
        var viewsDict = Dictionary <String, UIView>()
        viewsDict["control"] = self.pageControl;
        viewsDict["scrollView"] = self.scrollView;

        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[scrollView]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[control]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
        self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[scrollView(400)]-[control]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))

        // Set up the array to hold the views for each page
        for (var i = 0; i < pageCount; ++i) {
            self.pageViews.append(nil)
            self.pageImages.append(nil)
            self.imagesDownloads.append(nil)
            self.downloadsResponse.append(nil)
        }

    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func loadVisiblePages() {
        // First, determine which page is currently visible
        var pageWidth:CGFloat = self.scrollView.frame.size.width;
        var page = Int(floor((self.scrollView.contentOffset.x * 2.0 + pageWidth) / (pageWidth * 2.0)));

        /* 
            Check that page have changed, in case that user drag left in first page, or drag right in last page
            a 'scrollViewDidEndDecelerating' is fired
        */
        if self.viewingPage != page {

            self.viewingPage = page
            // Update the page control
            self.pageControl.currentPage = page;

            // Work out which pages we want to load
            var firstPage = page - 1;
            var lastPage = page + 1;

            // Purge anything before the first page
            for (var i=0; i<firstPage; i++) { self.purgePage(i) }
            for (var i=firstPage; i<=lastPage; i++) { self.loadPage(i) }
            for (var i = lastPage+1 ; i < self.pageImages.count ; i++) { self.purgePage(i) }
        }

    }

    func loadPage(page:Int) {
        if page < 0 || page >= self.pageImages.count {
            // If it's outside the range of what we have to display, then do nothing
            return;
        }

        // Load an individual page, first seeing if we've already loaded it
        var pageView:UIView? = self.pageViews[page];
        if pageView == nil {
            var frame:CGRect = self.scrollView.bounds;
            frame.origin.x = frame.size.width * CGFloat(page)
            frame.origin.y = 0.0

            var newPageView:ImageDownloadView = ImageDownloadView()
            newPageView.frame = frame;

            if(self.imagesDownloads[page] == nil && self.pageImages[page] == nil) {
                self.downloadsResponse[page] = { (pageNumber:Int!, actionType:ImageDownload.ActionType, progress:Float, result:UIImage!) -> Void in
                    switch actionType {
                    case ImageDownload.ActionType.STARTING :
                        if self.pageViews[pageNumber] != nil {
                            //DO NOTHING FOR NOW
                        }
                    case ImageDownload.ActionType.DOWNLOADING :
                        if self.pageViews[pageNumber] != nil {
                            self.pageViews[pageNumber]?.setDownloadProgress(progress)
                        }
                    case ImageDownload.ActionType.COMPLETED :
                        self.pageImages[pageNumber] = result;
                        if self.pageViews[pageNumber] != nil {
                            self.pageViews[pageNumber]?.showImage(result)
                        }
                    }
                }
                self.imagesDownloads[page] = ImageDownload()
                self.imagesDownloads[page]?.connect(self.imagesUrls![page], pageNumber:page, downloadReponse:self.downloadsResponse[page]!)
            } else if self.pageImages[page] != nil {
                newPageView.showImage(self.pageImages[page]!)
            }

            self.scrollView.addSubview(newPageView)
            self.pageViews[page] = newPageView
        }
    }

    func purgePage(page:Int) {
        if page < 0 || page >= self.pageImages.count {
            // If it's outside the range of what we have to display, then do nothing
            return;
        }

        // Remove a page from the scroll view and reset the container array
        var pageView:UIView? = self.pageViews[page];
        if pageView != nil {
            pageView?.removeFromSuperview()
            self.pageViews[page] = nil
        }
    }

    override func viewDidAppear(animated:Bool) {
        super.viewDidAppear(animated)

        // Set up the content size of the scroll view
        var pagesScrollViewSize:CGSize = self.scrollView.frame.size;
        self.scrollView.contentSize = CGSizeMake(pagesScrollViewSize.width * CGFloat(self.pageImages.count), pagesScrollViewSize.height);

        // Load the initial set of pages that are on screen
        self.loadVisiblePages()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        //Clean not visible images ?
    }

    func scrollViewDidEndDecelerating(scrollView: UIScrollView!) {
        self.loadVisiblePages()
    }
}

ImageDownloadView

代码语言:javascript
复制
class ImageDownloadView: UIView {
    var imageViewer:UIImageView = UIImageView()
    var roundProgress:RoundProgress = RoundProgress(frame:CGRectMake(0, 0, 60, 60))

    override init() {
        super.init()

        self.imageViewer.contentMode = UIViewContentMode.ScaleAspectFit;

        self.imageViewer.setTranslatesAutoresizingMaskIntoConstraints(false)
        self.roundProgress.setTranslatesAutoresizingMaskIntoConstraints(false)
        self.addSubview(self.imageViewer)
        self.addSubview(self.roundProgress)

        //Set layout
        var viewsDict = Dictionary <String, UIView>()
        viewsDict["image"] = self.imageViewer
        viewsDict["progress"] = self.roundProgress

        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[image]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[image]-0-|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))

        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[progress(60)]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[progress(60)]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))


        self.addConstraint(NSLayoutConstraint(item:self.roundProgress,
            attribute:NSLayoutAttribute.CenterX,
            relatedBy:NSLayoutRelation.Equal,
            toItem:self,
            attribute:NSLayoutAttribute.CenterX,
            multiplier:1,
            constant:0))

        self.addConstraint(NSLayoutConstraint(item:self.roundProgress,
            attribute:NSLayoutAttribute.CenterY,
            relatedBy:NSLayoutRelation.Equal,
            toItem:self,
            attribute:NSLayoutAttribute.CenterY,
            multiplier:1,
            constant:0))
    }

    func setDownloadProgress(progress:Float) {
        self.roundProgress.setProgress(progress / 100)
    }

    func showImage(image:UIImage) {
        self.imageViewer.image = image
        self.roundProgress.removeFromSuperview()
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

}

ImageDownload

代码语言:javascript
复制
typealias DownloadResponse = (pageNumber:Int!, actionType:ImageDownload.ActionType, progress:Float, result:UIImage!) -> Void

class ImageDownload:NSObject, NSURLConnectionDataDelegate {

    var data = NSMutableData()
    var pageNumber:Int?
    var downloadReponse:DownloadResponse?
    var contentLength:Int64?

    enum ActionType:CInt {
        case STARTING = 1, DOWNLOADING, COMPLETED
    }

    func connect(query:String, pageNumber:Int, downloadReponse:DownloadResponse) {
        self.pageNumber = pageNumber
        self.downloadReponse = downloadReponse

        var url =  NSURL.URLWithString(query)
        var request = NSURLRequest(URL: url)
        var conn = NSURLConnection(request: request, delegate: self, startImmediately: false)
        conn.start()
    }

    func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
        contentLength = response.expectedContentLength
        downloadReponse!(pageNumber: self.pageNumber, actionType: ActionType.STARTING, progress:0.0, result: nil)
    }

    func connection(connection: NSURLConnection!, didReceiveData conData: NSData!) {
        self.data.appendData(conData)
        var progress:Float = Float(self.data.length * 100) / Float(self.contentLength!)
        downloadReponse!(pageNumber: self.pageNumber, actionType: ActionType.DOWNLOADING, progress:progress, result: nil)
    }

    func connectionDidFinishLoading(connection: NSURLConnection!) {
        downloadReponse!(pageNumber: self.pageNumber, actionType: ActionType.COMPLETED, progress:100.0, result: UIImage(data: self.data))
    }

}

RoundProgress

代码语言:javascript
复制
class RoundProgress:UIView {
    var progressCircle = CAShapeLayer();
    var progress:Float = 0.1;

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.bounds = frame
    }


    override func didMoveToSuperview() {
        super.didMoveToSuperview()

        let centerPoint = CGPoint (x: self.bounds.width / 2, y: self.bounds.width / 2);
        let circleRadius : CGFloat = self.bounds.width / 2 * 0.83;
        var circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true    );

        progressCircle.path = circlePath.CGPath;
        progressCircle.strokeColor = UIColor.whiteColor().CGColor;
        progressCircle.fillColor = UIColor.clearColor().CGColor;

        progressCircle.lineWidth = 2.5;
        progressCircle.strokeStart = 0;
        progressCircle.strokeEnd = 0.0;

        self.layer.addSublayer(progressCircle);

    }

    func setProgress(progress:Float) {
        self.progress = progress
        progressCircle.strokeEnd = CGFloat(self.progress)
        self.setNeedsDisplay()
    }


    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


}
EN

回答 1

Code Review用户

回答已采纳

发布于 2014-09-06 18:47:15

您的ImageDownload类有一些问题。

首先,我要指出我不喜欢这个名字。这应该更像ImageDownloader,但这是一个相当小的抱怨。

更大的问题是,我们采取一个完成块和完成块预期将响应3个不同的步骤,在下载图像的过程中。也许更大的问题是,我们根本不在乎下载可能会失败。我们还没有包括连接失败条件的委托方法。

我们也不公开允许对象的用户取消下载请求的任何方法(例如,当视图滚动到屏幕时)。因此,如果终端用户不在wi上,我们就有可能浪费他们的移动数据。

因此,我们需要添加一个方法来取消下载。我们还需要一种方法来处理下载失败或被取消的异步情况。

抑制将两个状态添加到您的enum中的冲动,并大幅增加完成块的复杂性。相反,我们需要实现一个协议-委托模式。

代码语言:javascript
复制
protocol ImageDownloaderDelegate {
    required func imageDownloaderDidConnect()
    required func imageDownloaderDidComplete(image: UIImage)
    optional func imageDownloaderDidReceiveData(progress: Float)
    optional func imageDownloaderDidFail(error: NSError)
    optional func imageDownloaderWasCanceled()
}

显然,如果您认为合适,可以向这些方法添加任何附加参数。

现在,我们向我们的ImageDownloader类添加一个值:

代码语言:javascript
复制
var delegate: ImageDownloaderDelegate?

我们在适当的时候在委托上调用这些方法。我们应该确保在调用这些方法时使用可选链接,特别是在可选方法上:

代码语言:javascript
复制
self.delegate?.imageDownloaderDidReceiveData?(0.3)

当然,这意味着无论是谁使用ImageDownloader,现在都需要实现这些方法,并准备好在调用它们时响应它们。

这使我们避免了巨大的、丑陋的开关,它被关闭而变得更加丑陋,并且只有在添加我指出的其他两种场景的情况时才会变得更丑。相反,我们只实现了这些2-5个方法。总的来说,它的代码行数并不少。差不多是一样的。但是每一种方法都是独立的。它们更小更分散。这是5个非常不同的事件,它们需要作为这样的事件来处理。

NSURLConnection使用协议委托模式有一个很好的理由,我们不应该试图将其压缩到一个完成块中。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/62144

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档