iOS StoryBoard 优雅创建控制器方式

StoryBoard优雅创建控制器方式

我的项目中使用到了StoryBoard, 我也是喜欢上了这种创建页面的方式, 所见即所得, 每当产品让我改页面的时候, 我都说分分钟就好. 不用在一坨代码中找一个label, 然后修改其颜色, 改颜色几秒钟, 找代码却找了两分钟.

StoryBoard的优点

  • 创建快, 容易上手
  • 页面直观
  • 页面逻辑关系清晰
  • TableViewController的Static.Cell功能

StoryBoard的缺点

  • StoryBoard内容多的时候加载慢, 这个是所有程序员所不能忍受的.
  • StoryBoard的segue乱, 页面少的时候还好, 一旦多起来,那个线会像蜘蛛网一样, 蜘蛛网还好, 还是有规律的, StoryBoard就是乱的不得了了
  • StoryBoard的segue不灵活, 使用起来比较麻烦, 需要配置segue.identifier, 属于是硬编码, 有时候很容易配置错
  • 页面间传递参数的时候麻烦, 一个方法里写跳转逻辑, 另一个方法里写参数传递, 一个控制中多个跳转的时候, 还要判断segue.identifier 这种写法我真的是受不了.
  • UIStoryBoard方式创建麻烦, 创建控制器之前先创建一个UIStoryBoard对象, 然后在根据StoryBoard.identifier去创建, 标识符的配置,也是硬编码.

优雅的使用StoryBoard

基于以上的啰嗦, 我在想是不是可以, 像纯代码创建控制器的方式那样创建StoryBoard的VC呢, 像 CustomViewController.creatVC() 这样是不是就和原来的用法一样了, 既然想到了, 那就去实现以下吧, 正好可以用Swift来实现一下. 想要的效果

let vc = CustomViewController.creatVC()
vc.model = model
navigationController?.pushViewController(vc, animated: true)

我的思路

  • 分模块拆分StoryBoard, 这样可以减少StoryBoard的体积, 避免加载慢的问题

  • segue只是做业务之间的逻辑显示, 不用它来做跳转, 或者不用它

  • StoryBoard.identifier的约定, 约定所有的StoryBoard.identifier为控制器类名, 方便统一创建VC, 也不用纠结叫什么, 主要也是怕失误的时候写错, 我一般是写上类名的时候, 直接copy一下类名放到identifier上.

      let sb = UIStoryboard(name: "storyBoardName", bundle: nil)
      let vc = sb.instantiateViewController(withIdentifier: "storyboardId") as! CustomerController
    
  • 根据以上代码分析出需要, storyBoardName, storyboardId, 两个变量, storyboardId就是类名, 可以直接通过类名获得, 但是这个storyBoardName怎么获取呢?

  • 通过属性, 好像是不行, 因为创建控制之前还没有属性, 类属性, 我也不想每个控制器都写一下, 而且也觉得不是一个好的方法, 接下来我就想到了用协议, 协议中定义一个方法, 让它返回StoryBoard name.

我的实现

/// 这里可以把StoryBoard以模块划分,
enum StoryBoardName: String {
    case none = ""
    case main = "Main"
    case project = "Project"
    case login = "Login"
    case other = "Other"
    case mine = "Mine"
}

protocol StoryBoardDelegate {
    static func storyBoardName() -> StoryBoardName
}

protocol ClassNameDelete {
    static func className() -> String
}


extension UIViewController: ClassNameDelete {
    static func className() -> String {
        if let className = classForCoder().description().components(separatedBy: ".").last {
            return className
        } else {
            return classForCoder().description().components(separatedBy: ".").first!
        }
    }
}


extension BaseViewController {

    /// 通用创建VC方式
    class func creatVC() -> Self {
        
        let type = self.storyBoardName()
        if type == .none {
            return self.init()
        } else {
            if let vc = UIStoryboard.initVC(classType: self) {
                return vc
            } else {
                debugPrint("className=\(className())创建失败")
                return self.init()
            }
        }
    }
}

extension UIStoryboard {
    /// 根据类名创建 由StoryBoard 创建的控制器.
    class func initVC(classType: T.Type) -> T? where T: ClassNameDelete, T: StoryBoardDelegate {
        let name = T.storyBoardName()
        let sb = UIStoryboard.init(name: name.rawValue, bundle: nil)
        let vc = sb.instantiateVC(classType: classType)
        return vc
    }
    
    func instantiateVC(classType: T.Type) -> T? where T: ClassNameDelete {
        let className = T.className()
        let vc = instantiateViewController(withIdentifier: className)
        return vc as? T
    }
}

具体使用

/// 在基类中实现这个协议
class BaseViewController: UIViewController, StoryBoardDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
     
    class func storyBoardName() -> StoryBoardName {
        return .none
    }
} 

/// 子类中实现, 若是纯代码方式创建则不需要实现这个方法.
class TestView1Controller: BaseViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "TestView1"
    }

    override static func storyBoardName() -> StoryBoardName {
        return .main
    }
    
    @IBAction func btnToTest2Action(_ sender: UIButton) {
        let vc = TestView2Controller.creatVC()
        navigationController?.pushViewController(vc, animated: true)
    }
}

总结

自我感觉使用起来比较方便, 若是谁有更好的方式, 或可以优化的地方, 欢迎交流.
代码Demo

你可能感兴趣的:(iOS StoryBoard 优雅创建控制器方式)