(翻译) 如何将视图代码从 ViewController 中抽离出来



fork2.jpg



原创作者:Paul Hudson
原文链接:How to move view code out of your view controllers


这是解决 “臃肿的ViewController” 这个问题系列教程中的第三部分:

  1. 如何在 iOS app 中使用协调模式
  2. 如何把数据源和代理从你的 ViewController 抽离出来
  3. 如何把关于视图构建的代码搬离 ViewController



想要用代码而不是使用 interface Builder 来编写你的用户界面的原因有很多: 你可能会发现使用源代码管理更容易,也可能会发现它是一个更具表现力的环境,用于编写复杂的自动布局约束,或者你可能更喜欢通过组合函数来创建界面。


但是,这样做有正确的方法也有错误的方法,太多的应用程序会做出错误的决定。你会看到,即使你将视图控制器视为视图层的一部分,而不是控制器层(MVC中的V而不是C),它们仍然应该将实际的视图代码留给 UIView 及其许多子类。


尽管应该由 UIView 或其子类来处理视图代码是无需争论的,但是你经常会在视图控制器的 viewDidLoad() 方法内部看到各种视图的创建和配置,这几乎肯定是错误的。 在视图加载完成时调用该方法,而不是在开始创建视图时调用该方法。


将此类代码移动到自定义 UIView 子类中,不仅使视图控制器更加简单,而且还允许你根据需要在不同的地方重用视图代码。 更好的是,一旦将视图控制器变得更小,更简单,您就可以开始更多地依赖于视图控制器的封闭性以使其也可重复使用,最终得到的是松散耦合的更灵活的代码。


与其抽象地讨论所有这些,不如看一个具体的例子来说明人们如何混合视图和视图控制器。 将您的视线投向这种代码:

backgroundColor = UIColor(white: 0.9, alpha: 1)

let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 10
view.addSubview(stackView)

stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
stackView.axis = .vertical

let notice = UILabel()
notice.numberOfLines = 0
notice.text = "Your child has attempted to share the following photo from the camera:"
stackView.addArrangedSubview(notice)

let imageView = UIImageView(image: shareImage)
stackView.addArrangedSubview(imageView)

let prompt = UILabel()
prompt.numberOfLines = 0
prompt.text = "What do you want to do?"
stackView.addArrangedSubview(prompt)

for option in ["Always Allow", "Allow Once", "Deny", "Manage Settings"] {
    let button = UIButton(type: .system)
    button.setTitle(option, for: .normal)
    stackView.addArrangedSubview(button)
}



那甚至不是一个复杂的用户界面,但这是你会在 viewDidLoad() 中看到的那种东西,尽管那是放置它的可怕地方。


上面的所有代码都是视图代码,因此需要这样对待。 它不是控制器代码,即使使用 Apple 的混乱定义,它也不是视图控制器代码。 它是 View 的代码,属于 UIView 的子类。


进行此更改很容易:复制所有代码,将其粘贴到 UIView 的新子类 SharePromptView 中,然后将视图控制器视图的类更改为新的子类。

最终的 SharePromptView 类应如下所示:

class SharePromptView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        createSubviews()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        createSubviews()
    }

    func createSubviews() {
        // all the layout code from above
    }
}



所有的 UIView 子类必须实现 init(coder:),但是当你在代码中创建你的 UI 时,你还需要添加 init(frame:),createSubviews()方法来构建视图。


由于有了自定义的 UIView 子类,你现在可以从视图控制器中提取大量代码:

class ViewController: UIViewController {
    var shareView = SharePromptView()

    override func loadView() {
        view = shareView
    }
}



loadView()方法是以编程方式加载视图的正确位置。

注意:拥有专用的 shareView 属性可让您访问在 SharePromptView 中声明的任何属性,而不必强制转换视图类型。

那么,视图控制器应该是什么?

我经常问这个问题。 我已经讨论过如何使用协调器完成导航,以及如何从视图控制器中分离出委托和源代码,所以视图控制器应该做什么?


在我自己的代码中,我努力使视图控制器尽可能简单,因为我亲眼目睹了失去控制会发生什么。 这意味着他们:

  • 负责视图生命周期事件,例如viewDidLoad()viewWillAppear()traitCollectionDidChange()
  • 有一些 @IBOutlets@IBActions,尽管这些动作实际上应该只是在其他地方运行一个方法。 请记住,你可以根据需要在视图中添加出口。
  • 在模型和视图之间穿梭数据。 这不会增加诸如格式化数据之类的代码,它只是将值绑定到他们的视图并将更改从用户发回。

他们还可以根据我在做什么来处理模型的获取和存储; 我觉得非常实用。

正如 Dave DeLong 所说的那样,“ 300行视图控制器或崩溃”。当你编写301行时,不会发生任何不好的事情,当然我不会为了让 SwiftLint 通过检测脱身而将视图控制器重构为扩展。 但这确确实实表示你正在使视图控制器承担过多的责任。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 229,619评论 6 539
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,155评论 3 425
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 177,635评论 0 382
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,539评论 1 316
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,255评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,646评论 1 326
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,655评论 3 444
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,838评论 0 289
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,399评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,146评论 3 356
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,338评论 1 372
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,893评论 5 363
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,565评论 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,983评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,257评论 1 292
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,059评论 3 397
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,296评论 2 376

推荐阅读更多精彩内容