美文网首页
# 03 Loading View Controller 如何封

# 03 Loading View Controller 如何封

作者: NinthDay | 来源:发表于2017-11-01 00:06 被阅读296次

    常规思路

    通常我们的类会在加载前开启动画,回调到来再停止:

    final class EpisodeDetailViewController: UIViewController {
        let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
        let titleLabel = UILabel()
        
        convenience init(resource: Resource<Episode>) {
            self.init()
            // 开始网络请求 Loading 开始加载动画
            spinner.startAnimating()
            sharedWebservice.load(resource) { [weak self] result in
                // 回调数据到来 结束 Loading 加载动画
                self?.spinner.stopAnimating()
                guard let value = result.value else { return } 
                self?.titleLabel.text = value.title
            }
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .whiteColor()
    
            spinner.hidesWhenStopped = true
            spinner.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(spinner)
            spinner.center(inView: view)
    
            titleLabel.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(titleLabel)
            titleLabel.constrainEdges(toMarginOf: view)
        }
    }
    

    Protocol

    轻量级,而且 Swift 里有 extension,这一点比oc实现简单很多。
    主要在定义时候注意

    1. 相同的可重用的放到extension里面
    2. 需要每个具体类做特殊处理的就声明在协议里,当然也可以有默认实现。

    我们需要规范Resource资源,加载视图 spinner 以及 load 方法,这里加载视图默认是 UIActivityIndicatorView,协议定义如下:

    protocol Loading {
        associatedtype ResourceType
        var spinner: UIActivityIndicatorView { get }
        func load(resource: Resource<ResourceType>)
    }
    
    extension Loading where Self: UIViewController {
        func load(resource: Resource<ResourceType>) {
            spinner.startAnimating()
            sharedWebservice.load(resource) { [weak self] result in
                // 这里是回调处理 到这里的时候 result 就是解析好的数据格式
                self?.spinner.stopAnimating()
                guard let value = result.value else { return } 
                // TODO 这里和具体业务相关
            }
        }
    }
    

    ps: 加载视图可以不使用 UIActivityIndicatorView ,只需要小小修改下协议就能使用自定义。

    现在 TODO 和具体业务相关,我们必须想办法规范一下,为此在协议中增加一个configure 方法 用于处理业务逻辑。

    protocol Loading {
        // ...
        func configure(value: ResourceType)
    }
    
    extension Loading where Self: UIViewController {
        func load(resource: Resource<ResourceType>) {
            spinner.startAnimating()
            sharedWebservice.load(resource) { [weak self] result in
                self?.spinner.stopAnimating()
                guard let value = result.value else { return } // TODO loading error
                self?.configure(value)
            }
        }
    }
    

    现在用协议方法来实现:

    final class EpisodeDetailViewController: UIViewController, Loading {
        let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
        let titleLabel = UILabel()
        
        convenience init(episode: Episode) {
            self.init()
            configure(episode)
        }
        
        convenience init(resource: Resource<Episode>) {
            self.init()
            load(resource)
        }
        
        func configure(value: Episode) {
            titleLabel.text = value.title
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .whiteColor()
    
            spinner.hidesWhenStopped = true
            spinner.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(spinner)
            spinner.center(inView: view)
    
            titleLabel.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(titleLabel)
            titleLabel.constrainEdges(toMarginOf: view)
        }
    }
    

    至于 associatedtype ResourceType 中的 ResourceType 会在 load(resource) 推断出来。

    Container 容器封装

    有pros也有contra,首先不利的话,是每次都要addchildViewController 不是很好,在一些情况下可能出错,比如navigation栈下貌似会出错。

    我们要实现的 LoadingViewController 容器类职责:1. 数据请求load(),具体由外部实现; 2. 加载视图的显示和隐藏。

    核心代码是两个闭包:loadbuild

    final class LoadingViewController: UIViewController {
        let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
    
        init<A>(load: ((Result<A>) -> ()) -> (), build: (A) -> UIViewController) {
            super.init(nibName: nil, bundle: nil)
            spinner.startAnimating()
            load() { [weak self] result in
                self?.spinner.stopAnimating()
                guard let value = result.value else { return } // TODO loading error
                let viewController = build(value)
                self?.add(content: viewController)
            }
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .whiteColor()
            spinner.hidesWhenStopped = true
            spinner.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(spinner)
            spinner.center(inView: view)
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func add(content content: UIViewController) {
            addChildViewController(content)
            view.addSubview(content.view)
            content.view.translatesAutoresizingMaskIntoConstraints = false
            content.view.constrainEdges(toMarginOf: view)
            content.didMoveToParentViewController(self)
        }
    }
    

    这是我们Loading视图控制器,注意 build 闭包会EpisodeDetailViewController实例,具体处理业务。
    EpisodeDetailViewController 和上面定义的一样:

    final class EpisodeDetailViewController: UIViewController {
        let titleLabel = UILabel()
        
        convenience init(episode: Episode) {
            self.init()
            titleLabel.text = episode.title
        }
        
        override func viewDidLoad() {
            view.backgroundColor = .whiteColor()
            
            view.addSubview(titleLabel)
            titleLabel.translatesAutoresizingMaskIntoConstraints = false
            titleLabel.constrainEdges(toMarginOf: view)
        }
    }
    

    现在的关键是如何把两者关联组合起来:

    let episodesVC = LoadingViewController(load: { callback in
        sharedWebservice.load(episodeResource, completion: callback)
    }, build: EpisodeDetailViewController.init)
    

    category

    主要是今天看到高峰的文章 认为是一个不错的选择
    链接:iOS weak 关键字漫谈

    欢迎关注我的微博:@Ninth_Day

    相关文章

      网友评论

          本文标题:# 03 Loading View Controller 如何封

          本文链接:https://www.haomeiwen.com/subject/hetwpxtx.html