《iOS UI开发捷径》之从新认识IB

11511686475_.pic.jpg
做者二亮子用写IB省出的时间出了这本书,这本书确实是一本好书,本文章根据书中的内容总结了IB开发中本身不熟悉或者是不经常使用的知识点。

一 来看一下IB开发的优势以及缺点

1.1 优势
  • 1.1.1 开发和维护效率高 IB开发与纯代码开发相比, 效率至少提升两倍 这也就是为何做者在业余时间能写出这本书的缘由😁
  • 1.1.2 减小大量的UI代码和“胶水代码” IB 开发与纯代码开发相比,代码量至少减小三分之一
  • 1.1.3 适配变得十分简单
  • 1.1.4 IB也能够作一些非UI的事情 例如能够用IB中的Object从新组织VC的业务逻辑,减小一下没必要要的代码,
  • 1.1.5 利用IB学习控件能够达到事半功倍的效果
1.2 缺点
  • 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

2.1 xib是能够不依赖于源文件而单独使用的,纯粹的“死”UI能够只用一个xib文件展现,无需使它与源文件关联
2.2 理解File's Owner 使用File'sOwner 让xib中的button事件同时响应两个文件

WechatIMG1.jpeg
File'sOwner 就是文件的全部者, 这个file就是指该Xib文件,文件的全部者就是处理这个文件所涉及的业务逻辑与交互的对象。 咱们能够经过此File'sOwner 来设置他的文件全部者
image.png
这样不只能够在toolBar.swift文件中拖UIbuttonClick事件 也能够在ViewController中去拖拽UIbuttonClick事件 这样 点击Button 两个文件下的事件都是响应
image.png

2.3 封装xib

能够把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为NormalToolBarEditToolBar 学习

image.png

而后在控制器中经过父类去初始化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

image.png

2.4 建立bundle的两种方式(一种能够包含IB,一种不能包含IB)
  • Bundle 就是一个有着固定结构的目录, 因此能够新建一个文件夹,把须要封装的资源文件复制到该目录下,而后直接给该文件夹加.bundle后缀名就能够了 而后若是须要查看bundle的资源,就点击右键显示包内容。若是咱们想加载该bundle的资源的话就能够这样添加
    image.png
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是静态的其内部的资源不参与项目编译设计

  • 建立一个基于macOSBundle的target来得到Bundle 这种方式能够把其中的XIB和SB序列化为二进制的nib和storyboardc文件。建立Bundle的Target的方式是,点击菜单栏中的file->New->Target,在弹出的菜单中选择macOS->Framework&Library->Bundle,就能够建立除一个bundle了 如图:
    image.png
    之因此选择macOS,是由于iOS不支持以target形式建立的Bundle,为了非让刚刚建立的的Bundle能在iOS上顺利工做,须要将Banner这个Target下的Build Setting里的的SupportedPlatment修改为iOS,

image.png

而后想Banner这个Target下添加xib文件,这样就能够在Xcode左上角的scheme选择Banner这个Target编译了,此Banner.Bundle 就能够直接复制到其余工程中使用了

2.5 自定义的segue

自定义的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页面已是自定义效果了

CustomSegue.gif

2.6 深刻学习:Embed Segue

咱们先来看看下图中的这种UI结构

IMG_341B14EFB937-1.jpeg

你们应该第一眼就能看出来这种结构数据父子结构,代码大体以下

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连在一块儿,以下图

image.png

删除该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,给他添加必要的约束,这也是它优点的一个重要体现

2.7 深刻学习:Unwind Segue

在开发中可能遇到这样的需求,从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方法便可

image.png

2.7 IB文件的加载过程(分为5步)

先看一下两种加载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 cacheCocoa 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) outletsactions 就是前面提到的创建@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才会发生回调,只有用.newoutlet阶段是没有回调发生的,只有初始化以后再从新赋值时,用.new才会发生回调

  • 4 调用awakeFromNib()方法

对nib中的一些对象调用awakeFromNib方法,这些对象包括IB建立的控件,例如UIVIew的子类等,可是不包括FIle's OwnerFirst Response,placeholder object

  • 5 将nib中可见的控件显示出来
2.8 用 Object 重构 “神VC”

背景: 在开发中, 你们或许遇到过业务和UI都很复杂的页面,这样的页面每每对应了一个代码量庞大,结构臃肿,可维护性差的VC, 这样的VC一般称之为“神VC”, “神VC”通常什么事情都本身作,事无巨细, 如何重构它每每都是咱们的一个“心病”,重构思路通常都是用适合的设计模式,将“神VC”的一些工做和职能拆分到其余类,化整为零,使结构更加清晰, 下面说一下如何利用IB中的Object来重构“神VC”

  • 1 使用 Object 咱们新建一个IBObjectDemo的工程,而后在Main.storyboard中的ViewController 下添加一个Object,注意,要将其 “拖”到IB左边栏或者Scene Dock中才能够添加一个Object,以下图

    image.png

    假设这个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")        
    }
复制代码

而后将 ObjectViewControllerManager进行关联 如图

image.png

而后右键点击 ViewControllerManager ,再弹出的菜单中找到刚添加的法法,而后连线到控制器的按钮

image.png
此时运行App , 点击按钮 会看到这样的输出

handle something in VC
handle Something in manager
复制代码
  • 2 用Object 重构 “神VC”的思路

掌握了Object的简单使用以后,在进一步来说上面的例子,能够只将Main.storyBoard中的ViewControllerButton事件处理放在ViewControllerManager中。下面来让ViewControllerManager作更多的事情,如今能够将IB的属性也拖到 ViewControllerManager 中,这样VIewController就不用关心该UIButton相关的逻辑了。 这是一个意义很大的事情, 对一个类来讲,属性和方法几乎是类的所有,利用Object能够将VC的属性和方法都放在manager中管理, 就很方便的解决了神VC的问题了

image.png

一般用一个类去承担VC的时候,咱们都须要给这个类的实例传参数,并且这个实例每每设置成VC的属性, 方便任何地方使用。 一样的 咱们能够把IB中的ViewControllerManager 当成一个属性拖到ViewController

image.png
连线后 咱们就能够随时使用 ViewControllerManager了,能够给他传递参数了,

image.png

在一些复杂的状况下,manager知道本身服务的VC是谁, 此时能够给ViewControllerManager一个属性指向ViewController,可是为了防止循环引用要使用weak修饰,以下

image.png

  • 3 如何用好Object IB中的Object意义很大,做用也很大,掌握了Object的用法以后,能够很灵活地运用它 在这里能够提出几个思路,但愿能起一个抛砖引玉的做用,可以对你们有所启发,从而把Object用的更好,更妙
    • ① 一般一个神VC会成为不少对象的Delegate,须要处理不少回调,此事能够用Object替VC去实现这些Delegate方法,例如,能够建立一个TableVIewObject.swift专门实现TableVIewDelegateDataSource方法
    • ② 能够将一些通用的需求或交互模块化在对应的Object里,将这些需求或交互与VC解耦,也就是说,创建各个继承于Object类的一些子类,每一个子类实现特定的需求或交互,这些类做为基本单元存在, 当要实现一个VC时,根据需求在IB中添加不一样的Object控件,这些不一样的Object控件共同完成了该VC中的大部分功能,能够把IB中的Object和它对应的NSObject子类想象成一个零散的基础的积木块,把VC想象成用这些积木块搭建起来的城堡,城堡的风格不一样(VC的做用不一样)使用积木块的数量和种类也不一样,这样就使代码的复用率很高,从而大大减小VC的代码,用IB优雅的解决了“神VC”的问题
2.9 用 External Object 重构“神VC”

External Object 是与Object相似的东西,它的功能更增强大,可是只能用于Xib xib中有一个External Object更“厉害”,它能够将 Xib源文件、Xib的Files's Owner 源文件和NSObject类的源文件三者创建关系。

新建一个项目IBExternalObjectDemo,而后建立一个SegmentView.swiftSegmentView.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 ObjectclassViewControllerManager,而后再Show The Attributes inspector 中将Identifier标签值也设置为 ViewControllerManager,如图

image.png
image.png

而后将两个按钮往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 ObjectIndentifier标签中的值,Value是Manager,而后生成另外一个字段optionDict,这个key:UINibExternalObjects是固定的,只有这一个,ValueparamDic,接下来就是实例化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 ObjectObject重构神VC时,必定要清楚每一个类的职责是什么,切记矫枉过正,把全部的逻辑都放在ObjectExternal Object中,是VC变得无足轻重,因此必定要拿捏好重构“神VC”的角度,毕竟上下文的环境大多都在改VC中。 下图展现了Object中各个对象之间的关系

Object中各个对象之间的关系.png

下图展现了External Object中各个对象之间的关系

External Object中各个对象之间的关系.png

以上就是本人详读全书以后的总结,此书中还有不少细小的知识点非常值得咱们学习的,有想对IB进一步了解学习的强烈推荐阅读此书🙂

相关文章
相关标签/搜索