iOS进阶iOS开发技术

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

2017-12-03  本文已影响208人  奥卡姆剃须刀
11511686475_.pic.jpg
作者二亮子用写IB省出的时间出了这本书,这本书确实是一本好书,本文章根据书中的内容总结了IB开发中自己不熟悉或者是不常用的知识点。

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

1.1 优点
1.2 缺点

二 IB开发中的技巧

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

然后在控制器中通过父类去初始化

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!)                
    }

运行后就可以看到两个视图了 如下

image.png
2.4 创建bundle的两种方式(一种可以包含IB,一种不能包含IB)
        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是静态的其内部的资源不参与项目编译

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个教程

@IBAction weak var testView : UIView!

那就就可以注册该属性,通过KVO的回调得知outlet建立关系的时刻:

self.addObserver(self, forKeyPath: "testView", options: .initial, context: nil)

这里注意,因为是初始化阶段,所以options必须有.initial才会发生回调,只有用.newoutlet阶段是没有回调发生的,只有初始化之后再重新赋值时,用.new才会发生回调

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

2.8 用 Object 重构 “神VC”

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

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

掌握了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
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进一步了解学习的强烈推荐阅读此书🙂

上一篇下一篇

猜你喜欢

热点阅读