1.2.1 IB的执行效率没有纯代码高 这是一个不争的事实,IB加载UI能够简单理解为两个过程:首先要把xib或sb文件对应的nib或storyboardc文件加载到内存中, 而后利用这些数据去生成UI页面,加以显示。而纯代码只须要一个过程, 这个过程相似于IB加载UI的第二个过程,直接在内存中生成UI页面加以显示swift
1.2.2 使用IB开发过程当中容易出现一些小的问题 用IB开发确实是会碰见一些小问题,可能这些小问题用代码开发就不会出现,因此若是遇到由于IB开发的问题的话,就把IB的这些坑记录下来,这是一个很好的学习习惯设计模式
1.2.3 文件容易冲突bash
1.2.4 没有代码表达清晰架构
1.2.5 不利于代码的封装和工程架构的组织 ###二 IB开发中的技巧ide
能够把loadNibNamed(_:owner:option:)
方法封装到源文件的一个类中,源文件派生出几个子类,根据不一样状况加载并返回不一样的子类。可使用工厂设计模式模块化
// 咱们建立一个父类
class ToolBar: UIView {
class func toolBar(type:ToolBarType)-> ToolBar? {
if type == .normal {
return Bundle.main.loadNibNamed("ToolBar", owner: nil, options: nil)?[0] as? ToolBar
}else if type == .edit {
return Bundle.main.loadNibNamed("ToolBar", owner: nil, options: nil)?[1] as? ToolBar
}else {
return nil;
}
}
override func awakeFromNib() {
super.awakeFromNib()
handleEvent()
}
func handleEvent() {
// 子类重写
}
}
// 实例化两个子类并设置颜色
class NormalToolBar: ToolBar {
override func handleEvent() {
backgroundColor = UIColor.red
}
}
class EditToolBar: ToolBar {
override func handleEvent() {
backgroundColor = UIColor.yellow
}
}
复制代码
而后在ToolBar.xib中添加两个VIew 分别更改他们的class为NormalToolBar
和EditToolBar
学习
而后在控制器中经过父类去初始化ui
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let normalToolBar = ToolBar.toolBar(type: ToolBarType.normal)
normalToolBar?.frame.origin = CGPoint(x: 0, y: 100)
view.addSubview(normalToolBar!)
let editToolBar = ToolBar.toolBar(type: ToolBarType.edit)
editToolBar?.frame.origin = CGPoint(x: 0, y: 200);
view.addSubview(editToolBar!)
}
复制代码
运行后就能够看到两个视图了 以下spa
.bundle
后缀名就能够了 而后若是须要查看bundle的资源,就点击右键显示包内容。若是咱们想加载该bundle的资源的话就能够这样添加
let imageBundle = Bundle.main.path(forResource: "image", ofType: "bundle")
let imagePath = imageBundle! + "/icon.png"
let image = UIImage.init(contentsOfFile: imagePath)
复制代码
用上述的方式建立的bundle能够放几乎全部的资源, 除了IB文件,由于IB文件在工程编译后会被序列化为二进制的nib和storyboardc文件,而修改文件夹后缀名的方式建立的Bundle是静态的其内部的资源不参与项目编译设计
而后想Banner这个Target下添加xib文件,这样就能够在Xcode左上角的scheme选择Banner这个Target编译了,此Banner.Bundle 就能够直接复制到其余工程中使用了
自定义的segue须要在segue菜单中选择custom选项, 而后再Class标签里指定一个UIStoryboardSegue
子类的类名,这个子类必须实现perform方法, 本身完成segue跳转的过程,若是选择了segue,可是并无在class标签中指定任何 UIStoryboardSegue
的子类,那么App运行该segue是会crash,仍是以A页面跳转到B页面为例, 来讲明一下自定义的segue,要自定义segue,就要继承于UIStoryboardSegue
,写一个子类,这个暂且叫作CustomSegue
,而后重写perform方法
class CustomSegue: UIStoryboardSegue {
override func perform() {
let svc = source
let dvc = destination
dvc.view.frame = svc.view.frame
dvc.view.alpha = 0.0
svc.view.addSubview(dvc.view)
UIView.animate(withDuration: 0.3, animations: {
dvc.view.alpha = 1.0
}) { (flag:Bool) in
svc.navigationController?.pushViewController(dvc, animated: false)
}
}
}
复制代码
在perform里简单的实现了一个渐显的效果来显示B页面。准备好CustomSegue
以后,“拖”一个从A页面的Button到B页面的segue,在弹出的segue菜单中选择一个Custom选项,而后把Class标签设置成CustomSegue
,运行App就会发现,从A页码跳转到B页面已是自定义效果了
咱们先来看看下图中的这种UI结构
你们应该第一眼就能看出来这种结构数据父子结构,代码大体以下
let testVC = TestViewController.init()
self.view.addSubview(testVC.view)
self.addChildViewController(testVC)
复制代码
而IB中的Embed Segue就是专门解决这种VC嵌套的。在右边栏下面的Show the Object Library
中找到Container View
,拖 到 View Contwoller
的控件显示区域,会看到Container View
与另外一个 View Contwoller
经过Segue连在一块儿,以下图
删除该Segue箭头指向的 View Contwoller
,选中Container View
,将segue拖到但愿添加的子VC上,在弹出的菜单中选择Embed
,注意这里只能选择Embed
,当选择了Embed
后,你会发现子VC的大小和Container View
的大小同样了, 此时改变Container View
的大小, 子VC的大小也随之改变, 将Container View
调整到合适的尺寸运行App,会发现Container View
所在的区域已经变成了子VC,点击子VC的上的按钮,能够正常处理事件,这说明了Embed Segue
执行了容器VC的 addChildVIewController
,将子VC自动添加到容器VC的ChildVIewController
中,整个嵌套过程操做十分简单,Embed Segue
的优点不只体如今不用实例化子VC,不用本身添加到ChildVIewController
中,并且能够在IB中调整Container View
的frame,给他添加必要的约束,这也是它优点的一个重要体现
在开发中可能遇到这样的需求,从A页面跳转到B页面,在B页面选择或者填写一些数据后,在回到A页面获取刚刚在B页面选择或填写的数据加以显示;这中需求相信你们都作过无数遍了吧,代理,block,通知等什么方式均可以作到的,如今来学习一下用Unwind Segue
的方式 Unwind Segue
提供了一种从一个页面返回到上一个页面时的回调, 能够利用这个特性,简单优雅的实现页面间的反向传值。这个回调能够由系统自动触发,也能够手动触发,只要在回到的页面里添加一个相似于下边的代码
@IBAction func handleUnWindSegue(unwindSegue: UIStoryboardSegue) {
if unwindSegue.identifier == "unwindB" {
if let svc = unwindSegue.source as? BViewController {
print("data fromB : \(svc.textF.text)")
}
}
}
复制代码
而后再SB中选择要返回到上一个页面的Button,按住control将其拖动到Exit的位置(以下图),在弹出的菜单中选择 handleUnWindSegue方法便可
先看一下两种加载IB的方式
// 第一种
let testView = Bundle.main.loadNibNamed("LLTestView", owner: nil, options: nil)?[0] as! UIView
// 第二种
let testViewNib = UINib.init(nibName: "LLTestView", bundle: Bundle.main)
let testView = testViewNib.instantiate(withOwner: nil, options: nil)[0] as! UIView
复制代码
以上两种方式都包括了这5个过程,下边详细介绍这5个教程
1 将nib加载到内存 该过程会将nib中的对象和对象所引用的资源加载到内存,例如,在nib中引用了图片, 声音等资源文件,该过程会把这些资源加载到相应的Cocoa Image cache
或 Cocoa sound cache
中, 前面说过, 从xib到nib的过程叫作序列化,是将XML格式的plist文件序列化为二进制格式的plist,该过程虽然将nib种的对象加载到了内存,可是没有进行反序列化
2 解雇化 并实例化nib文件里对应的对象 该过程会将上面加载到内存中的对象进行反序列化,该过程会调用初始化,这里注意,虽然这些对象大多数都是UIVIew
类,UIViewController
类,或者是它们的子类,可是这些对象经过IB进行初始化,并不会调用init(frame:)
或者普通的init
方法。UIVIew
及其子类会调用Init(coder:)
的方法,UIVIewController
及其子类会调用Init(nibName:bundle:)
的方法,而若是nib
中存在Object
或者External Object
对象,那么会调用这些对象所在类的init
方法,通过这一步后,才真正把“数据”变成了“对象”
3 创建 connections
(outlets
, actions
) outlets
与 actions
就是前面提到的创建@IBOutlet
就与@IBAction
的链接。创建Connections
的顺序为,先创建outlets
链接,而后创建actions
链接。创建 outlet
链接到过程用到了setValue:forKey:
方法,同时创建outlet
过程支持KVO,若有有一个属性:
@IBAction weak var testView : UIView!
复制代码
那就就能够注册该属性,经过KVO的回调得知outlet创建关系的时刻:
self.addObserver(self, forKeyPath: "testView", options: .initial, context: nil)
复制代码
这里注意,由于是初始化阶段,因此options
必须有.initial
才会发生回调,只有用.new
在outlet
阶段是没有回调发生的,只有初始化以后再从新赋值时,用.new
才会发生回调
awakeFromNib()
方法对nib中的一些对象调用awakeFromNib
方法,这些对象包括IB
建立的控件,例如UIVIew
的子类等,可是不包括FIle's Owner
, First Response
,placeholder object
nib
中可见的控件显示出来Object
重构 “神VC”背景: 在开发中, 你们或许遇到过业务和UI都很复杂的页面,这样的页面每每对应了一个代码量庞大,结构臃肿,可维护性差的VC, 这样的VC一般称之为“神VC”, “神VC”通常什么事情都本身作,事无巨细, 如何重构它每每都是咱们的一个“心病”,重构思路通常都是用适合的设计模式,将“神VC”的一些工做和职能拆分到其余类,化整为零,使结构更加清晰, 下面说一下如何利用IB中的Object
来重构“神VC”
1 使用 Object 咱们新建一个IBObjectDemo
的工程,而后在Main.storyboard
中的ViewController
下添加一个Object
,注意,要将其 “拖”到IB左边栏或者Scene Dock中才能够添加一个Object,以下图
假设这个ViewController
是一个神VC,为了重构这个神VC,咱们新建一个VIewControllerManage.swift
类,该类负责处理ViewController.swift
中的某一类业务逻辑或交互。 如今ViewControllerManager.swift
中添加以下方法:
class ViewControllerManager: NSObject {
@IBAction func handleSomethingForViewController() {
print("handle Something in manager")
}
}
复制代码
想Main.storyboard
中的Viewcontroller
中放一个按钮,而后再文件中添加对应的点击事件
@IBAction func handleSomething(_ sender: UIButton) {
print("handle something in VC")
}
复制代码
而后将 Object
和 ViewControllerManager
进行关联 如图
而后右键点击 ViewControllerManager
,再弹出的菜单中找到刚添加的法法,而后连线到控制器的按钮
handle something in VC
handle Something in manager
复制代码
掌握了Object
的简单使用以后,在进一步来说上面的例子,能够只将Main.storyBoard
中的ViewController
中Button
事件处理放在ViewControllerManager
中。下面来让ViewControllerManager
作更多的事情,如今能够将IB的属性也拖到 ViewControllerManager
中,这样VIewController
就不用关心该UIButton
相关的逻辑了。 这是一个意义很大的事情, 对一个类来讲,属性和方法几乎是类的所有,利用Object
能够将VC的属性和方法都放在manager
中管理, 就很方便的解决了神VC的问题了
一般用一个类去承担VC的时候,咱们都须要给这个类的实例传参数,并且这个实例每每设置成VC的属性, 方便任何地方使用。 一样的 咱们能够把IB中的ViewControllerManager
当成一个属性拖到ViewController
中
ViewControllerManager
了,能够给他传递参数了,
在一些复杂的状况下,manager
知道本身服务的VC是谁, 此时能够给ViewControllerManager
一个属性指向ViewController
,可是为了防止循环引用要使用weak
修饰,以下
Object
意义很大,做用也很大,掌握了Object
的用法以后,能够很灵活地运用它 在这里能够提出几个思路,但愿能起一个抛砖引玉的做用,可以对你们有所启发,从而把Object
用的更好,更妙
Delegate
,须要处理不少回调,此事能够用Object
替VC去实现这些Delegate
方法,例如,能够建立一个TableVIewObject.swift
专门实现TableVIew
的Delegate
和DataSource
方法Object
里,将这些需求或交互与VC解耦,也就是说,创建各个继承于Object
类的一些子类,每一个子类实现特定的需求或交互,这些类做为基本单元存在, 当要实现一个VC时,根据需求在IB中添加不一样的Object
控件,这些不一样的Object
控件共同完成了该VC中的大部分功能,能够把IB中的Object
和它对应的NSObject
子类想象成一个零散的基础的积木块,把VC想象成用这些积木块搭建起来的城堡,城堡的风格不一样(VC的做用不一样)使用积木块的数量和种类也不一样,这样就使代码的复用率很高,从而大大减小VC的代码,用IB优雅的解决了“神VC”的问题External Object
重构“神VC”External Object
是与Object
相似的东西,它的功能更增强大,可是只能用于Xib xib中有一个External Object
更“厉害”,它能够将 Xib源文件、Xib的Files's Owner
源文件和NSObject
类的源文件三者创建关系。
新建一个项目IBExternalObjectDemo
,而后建立一个SegmentView.swift
、SegmentView.xib
和一个ViewControllerManager.swift
文件,而后再SegmentView.xib
中添加两个按钮,往SegmentView.swift
文件中连线回调方法
@IBAction func handleSelect(_ sender: UIButton) {
print("handle select in SegmentView")
}
复制代码
而后再SegmentView.Xib
中选中File's Owner
,再Show the Identify inspector
中将class
改成 ViewController
,而后再讲两个按钮像控制器中连线回调方法
@IBAction func handleSegmentChanges(_ sender: UIButton) {
print("handle select In VC")
}
复制代码
重点是是 External Object
,向SegmentView
中拖入External Object
,而后更改External Object
的class
为 ViewControllerManager
,而后再Show The Attributes inspector
中将Identifier
标签值也设置为 ViewControllerManager
,如图
而后将两个按钮往ViewControllerManager
中脱线回调方法
@IBAction func managerSegmentView(_ sender: UIButton) {
print("handle select In manager")
}
复制代码
而后再控制器中初始化SegmentView
并添加再控制器上
let manager = VIewControllerManager()
override func viewDidLoad() {
super.viewDidLoad()
let paramDic = ["VIewControllerManager" : manager]
let optionDict = [UINibExternalObjects : paramDic]
let segmentVIew = Bundle.main.loadNibNamed("SegmentView", owner: self, options: optionDict)?[0] as! SegmentView
segmentVIew.center = view.center
view.addSubview(segmentVIew)
}
复制代码
首先初始化一个VIewControllerManager
实例做为VC的Manager
属性, 而后生成一个字典,这个字典的key 是 VIewControllerManager
,就是segmentVIew.xib
中的External Object
的Indentifier
标签中的值,Value是Manager
,而后生成另外一个字段optionDict
,这个key:UINibExternalObjects
是固定的,只有这一个,Value
是paramDic
,接下来就是实例化xib,将optionDict
传入options
这个参数中,而这个参数就是制定External Object
,运行代码 点击按钮, 输出一下结果
handle select In VC
handle select in SegmentView
handle select In manager
复制代码
咱们就能够用上述的思路将“神VC”中的功能分在三个模块中完成,重构“神VC”的主要思路就是将该类的代码清晰,合理的分散在其余类中,让每一个类仅仅处理本身的职责,各司其职。
Object 和 External Object总结 Object
能够用于xib 和 sb,而External Object
只能用于sb,二者的类似之处是都提供了一种能够将VC中的代码放到其余类中的途径,这里的其余类必须是NSObject
的子类,当用Swift开发时要注意到这一点,当用External Object
和Object
重构神VC时,必定要清楚每一个类的职责是什么,切记矫枉过正,把全部的逻辑都放在Object
和External Object
中,是VC变得无足轻重,因此必定要拿捏好重构“神VC”的角度,毕竟上下文的环境大多都在改VC中。 下图展现了Object
中各个对象之间的关系
下图展现了External Object
中各个对象之间的关系
以上就是本人详读全书以后的总结,此书中还有不少细小的知识点非常值得咱们学习的,有想对IB进一步了解学习的强烈推荐阅读此书🙂