iOS Swift优雅的拆分UIViewController与View

MVC对于iOS开发的意义

对于iOS开发而言 始终没法绕开UIKit这个框架, 加之SwiftUI并不成熟, 因此你懂的, 而UIKit框架就是基于的MVC的设计模式, 因此这也是为何MVC是苹果官方推荐的设计模式.git

为何我我的比较推荐项目中用MVC.

一. 官方推荐这个标签确定是有必定份量的.github

二. 在一个基于MVC的框架上强行使用MVVM或MVP的设计模式, 不是不能够, 但总有些地方会差强人意, 当你使用各类知名框架时就会体会到这种感受, 固然你能够选择忽略不计, 但它们确实存在.swift

三. 简单, 很是的简单, 没有学习成本, 是个开发者就懂, 基本没有"交流"障碍. 越简单越利于维护(这也是我比较推崇的开发风格 一切从简).设计模式

四. 可扩展性强, 由于足够通用, 因此在MVC基础上能够根据具体业务须要转变成其余设计模式, 总的来讲 整个项目的基础设计模式仍是MVC, 根据不一样业务模块状况能够再使用最合适的设计模式.框架

老生常淡 MVC的最大弊端

没错 臃肿的C层代码. 因此不少优化方案都是围绕这点展开的, 我们也不例外.ide

UIViewController与UIView的纠葛

UIViewController在实际开发中会遇到不少问题, 这里咱们只说View相关的问题.布局

按照苹果的设计理念: UIViewController对应MVCC, UIView对应MVCV, XXXModel对应MVCM.学习

但尴尬的是 咱们使用UIViewController时 难免会有不少View的处理在其中, 纯代码的方式还好一些, 能够经过封装自定义View类来解决, StoryboardXib的方式就尤其明显了.优化

纯代码:ui

自定义View类来编写视图相关的代码, 能够将V的处理从UIViewController中分离出去, 可是难免要在UIViewController中再次编写初始化 布局等代码. 试想每一个UIViewController都要写一遍某个View的初始化 添加父视图 布局等.

Storyboard或XIB:

拖线连接的控件一般会在UIViewController中, 常常见到UIViewController中拖了一堆视图控件对象. 固然除了拖进来还要写一下其余视图相关的代码. 也有使用自定义View类来承载全部拖线连接的控件对象 和上面纯代码的方式差很少.

一样的困境:UIViewController中view的类型永远都是UIView, 上面两种方式遇到的问题同样, 须要作类型转换才能访问到自定义View类中的属性和方法, 这无疑是很麻烦的.

Swift 泛型优雅的解决类型转换问题

  • 建立一个基类 (实际项目开发中应该要有一个ViewController基类, 并保证基类的干净, 这是一个很好的习惯)
class ViewController<Container: UIView>: UIViewController {

    var container: Container { view as! Container }
    
    override func loadView() {
        super.loadView()
        if view is Container {
            return
        }
        view = Container()
    }
}
复制代码
  • 全部视图控制器都继承自该基类, 并明确声明该控制器所使用的View类型
class HomeController: ViewController<HomeView> {

    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
}
复制代码
  • 经过container属性直接访问上面声明的自定义View类型对象, 固然你也能够改成其余名字
class XXXXController: ViewController<XXXXView> {

    // CODE
  
    override func viewDidLoad() {
        super.viewDidLoad()
        // CODE
        container.xxxx()
    }
}
复制代码

使用演示:

纯代码:

class XXXXView: UIView {

    private lazy var titleLabel = UILabel()
    private lazy var iconImageView = UIImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        titleLabel.textColor = .black
        titleLabel.font = .systemFont(ofSize: 13, weight: .semibold)
        
        iconImageView.contentMode = .scaleAspectFill
        
        addSubview(titleLabel)
        addSubview(iconImageView)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        titleLabel.frame = .init(x: 100, y: 100, width: 100, height: 40)
        iconImageView.frame = .init(x: 100, y: 100, width: 100, height: 100)
    }
    
    func set(title: String) {
        titleLabel.text = title
    }
    
    func set(image: UIImage) {
        iconImageView.image = image
    }
}
复制代码
class XXXXController: ViewController<XXXXView> {

    private let model = XXXXModel()
    // CODE
  
    override func viewDidLoad() {
        super.viewDidLoad()
        // CODE
        container.set(title: model.title)
        container.set(image: model.image)
    }
}
复制代码
let controller = XXXXController()
present(controller, animated: true)
复制代码

Storyboard或XIB:

设置Controller类:

设置Controller类

设置View类:

设置View类

向View中拖线连接:

向View中拖线连接

在Controller中为视图设置数据:

为视图设置数据


总结

方法简单, 很好的解决了上面提到的这些问题, 使Controller与View的分离更加优雅.

class HomeController: ViewController<HomeView> { }
复制代码

头部的声明能够直观的看到Controller的View类型, 可读性强.

因明确了类型 调用更加顺畅天然, 省去了多余的类型转换代码.

使 Controller 能够更专一于Model与View的协调和调用, 职责更明确.

截止如今, 这个方法我本身已经使用2年了 其中也经历了几个项目的洗礼, 仍是没什么问题的, 你们若是感兴趣能够放心采纳.

Demo传送门

若是你有更好的想法 欢迎评论交流.

相关文章
相关标签/搜索