Swift iOS : 程式碼分析DrawController

RecoReco發表於2019-02-23

倉庫github/DrawController(github.com/sascha/Draw…)
是一個Swift語言編寫的抽屜庫。它可以傳入一箇中心檢視控制器,一個左側檢視控制器,並由一個MaxDrawerWidth屬性。建立完畢後,中心檢視控制器的檢視內容佔據全部螢幕;當執行開啟抽屜的函式時,整個中心控制的檢視內容一起向右平移MaxDrawerWidth這麼寬,並且顯示左側檢視控制器的內容到螢幕上,內容佔滿螢幕,但是寬度不超過MaxDrawerWidth指定的值。

研究此程式碼庫的時候,發現它是一個很好的解說Container View Controller[developer.apple.com/library/con…] 概念的例子。這裡面:

  1. 類DrawerController就是一個Container View Controller
  2. 其他的檢視控制器就是Child View Controller
  3. 第一個類和其他類構成了父子關係
  4. 檢視控制器內的檢視其實都是放置到DrawerController.view之內的

我把此庫內的關於動畫和手勢的程式碼全部去掉,專門留下特定於這些控制器互動的程式碼留下。從1000多行的程式碼抽取出來247行的程式碼如下:

 import UIKit
 var drawerController : DrawerPage?
 @UIApplicationMain
 class AppDelegate: UIResponder, UIApplicationDelegate {
    var window : UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow()
        drawerController = DrawerPage()
        window!.rootViewController = drawerController
        window!.rootViewController!.view.backgroundColor = .blue
        window!.makeKeyAndVisible()
        return true
    }
 }
 class DrawerPage : DrawerBase{
    init(){
        super.init(CenterPage(),LeftPage())
    }
    // 哄編譯器開心的程式碼
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
 }
 class DrawerBase : DrawerController{
    init(_ center : UIViewController,_ left : UIViewController){
        super.init(centerViewController: center, leftDrawerViewController: left)
    }
    // 從入門到入門:
    // 1. What exactly is init coder aDecoder?
    // 2. What does the question mark means in public init?(coder aDecoder: NSCoder)?
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
 }
 class LeftPage: UIViewController {
    var count = 0
    var label : UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        label   = UILabel()
        label.frame = CGRect(x: 100, y: 100, width: 120, height: 50)
        label.text =  "Left"
        view.addSubview(label)
        let button   = UIButton(type: .system)
        button.frame = CGRect(x: 120, y: 150, width: 120, height: 50)
        button.setTitle("Close",for: .normal)
        button.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside)
        view.addSubview(button)
    }
    @objc func buttonAction(_ sender:UIButton!){
        drawerController?.toggleLeftDrawerSide(animated: true, completion: nil)
    }
 }
 class CenterPage: UIViewController {
    var label : UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        label   = UILabel()
        label.frame = CGRect(x: 100, y: 100, width: 120, height: 50)
        label.text =  "Center"
        view.addSubview(label)
        let button   = UIButton(type: .system)
        button.frame = CGRect(x: 120, y: 150, width: 120, height: 50)
        button.backgroundColor = .blue
        button.setTitle("Left Page Drawer",for: .normal)
        button.addTarget(self, action: #selector(buttonAction(_:)), for: .touchUpInside)
        view.addSubview(button)
    }
    @objc func buttonAction(_ sender:UIButton!){
        drawerController?.toggleLeftDrawerSide(animated: true, completion: nil)
    }
    @objc func buttonAction1(_ sender:UIButton!){
//        drawerController?.toggleRightDrawerSide(animated: true, completion: nil)
    }
 }
 // code imple
 open class DrawerController: UIViewController {
    fileprivate var _centerViewController: UIViewController?
    fileprivate var _leftDrawerViewController: UIViewController?
    fileprivate var _leftDrawerWidth = DrawerDefaultWidth
    open var centerViewController: UIViewController? {
        get {
            return self._centerViewController
        }
    }
    open var leftDrawerViewController: UIViewController? {
        get {
            return self._leftDrawerViewController
        }
    }
    open var leftDrawerWidth: CGFloat {
        get {
            return self._leftDrawerWidth
        }
    }
    open var shadowRadius = DrawerDefaultShadowRadius
    open var shadowOpacity = DrawerDefaultShadowOpacity
    fileprivate lazy var childControllerContainerView: UIView = {
        let a = UIView(frame: self.view.bounds)
        self.view.addSubview(a)
        return a
    }()
    fileprivate lazy var centerContainerView: DrawerCenterContainerView = {
        let centerFrame = self.childControllerContainerView.bounds
        let centerContainerView = DrawerCenterContainerView(frame: centerFrame)
        centerContainerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        centerContainerView.backgroundColor = UIColor.clear
        self.childControllerContainerView.addSubview(centerContainerView)
        return centerContainerView
    }()
    open fileprivate(set) var openSide: DrawerSide = .center {
        didSet {
            if self.openSide == .center {
                self.leftDrawerViewController?.view.isHidden = true
            }
            self.setNeedsStatusBarAppearanceUpdate()
        }
    }
    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    public init(centerViewController: UIViewController, leftDrawerViewController: UIViewController?) {
        super.init(nibName: nil, bundle: nil)
        self.setCenter(centerViewController)
        self.setDrawer(leftDrawerViewController,for:.left)
    }
    fileprivate func childViewController(for drawerSide: DrawerSide) -> UIViewController? {
        var childViewController: UIViewController?
        switch drawerSide {
        case .left:
            childViewController = self.leftDrawerViewController
        case .center:
            childViewController = self.centerViewController
        }
        return childViewController
    }
    fileprivate func sideDrawerViewController(for drawerSide: DrawerSide) -> UIViewController? {
        var sideDrawerViewController: UIViewController?
        if drawerSide != .center {
            sideDrawerViewController = self.childViewController(for: drawerSide)
        }
        return sideDrawerViewController
    }
    fileprivate func updateShadowForCenterView() {
        self.centerContainerView.layer.masksToBounds = false
        self.centerContainerView.layer.shadowRadius = shadowRadius
        self.centerContainerView.layer.shadowOpacity = shadowOpacity
        if let shadowPath = centerContainerView.layer.shadowPath {
            let currentPath = shadowPath.boundingBoxOfPath
            if currentPath.equalTo(centerContainerView.bounds) == false {
                centerContainerView.layer.shadowPath = UIBezierPath(rect: centerContainerView.bounds).cgPath
            }
        } else {
            self.centerContainerView.layer.shadowPath = UIBezierPath(rect: self.centerContainerView.bounds).cgPath
        }
    }
    open func toggleLeftDrawerSide(animated: Bool, completion: ((Bool) -> Void)?) {
        self.toggleDrawerSide(.left, animated: animated, completion: completion)
    }
    open func toggleDrawerSide(_ drawerSide: DrawerSide, animated: Bool, completion: ((Bool) -> Void)?) {
        assert({ () -> Bool in
            return drawerSide != .center
        }(), "drawerSide cannot be .None")
        if self.openSide == DrawerSide.center {
            self.openDrawerSide(drawerSide,completion: completion)
        } else {
            if (drawerSide == DrawerSide.left && self.openSide == DrawerSide.left) {
                self.closeDrawer(animated: animated, completion: completion)
            } else if completion != nil {
                completion!(false)
            }
        }
    }
    fileprivate func openDrawerSide(_ drawerSide: DrawerSide,completion: ((Bool) -> Void)?) {
        assert({ () -> Bool in
            return drawerSide != .center
        }(), "drawerSide cannot be .None")
        if let vc = self.leftDrawerViewController{
            vc.view.isHidden = false
            var newFrame: CGRect
            newFrame = view.frame
            newFrame.size.width = _leftDrawerWidth
            vc.view.frame = newFrame
            //            vc.view.frame = vc.evo_visibleDrawerFrame
        }
        func panRight(_ view : UIView,_ value : CGFloat){
            var newFrame: CGRect
            newFrame = view.frame
            newFrame.origin.x = value
            view.frame = newFrame
        }
        panRight(centerContainerView, _leftDrawerWidth)
        self.openSide = drawerSide
    }
    fileprivate func setDrawer(_ vc: UIViewController?, for drawerSide: DrawerSide) {
        assert({ () -> Bool in
            return drawerSide != .center
        }(), "drawerSide cannot be .None")
        let currentSideViewController = self.sideDrawerViewController(for: drawerSide)
        if currentSideViewController == vc {
            return
        }
        self._leftDrawerViewController = vc
        if vc != nil {
            self.addChildViewController(vc!)
            vc!.didMove(toParentViewController: self)
            self.childControllerContainerView.addSubview(vc!.view)
            self.childControllerContainerView.sendSubview(toBack: vc!.view)
            vc!.view.isHidden = true
        }
    }
    fileprivate func setCenter(_ vc: UIViewController?) {
        self._centerViewController = vc
        if vc != nil {
            self.addChildViewController(vc!)
            vc!.didMove(toParentViewController: self)
            self.centerContainerView.addSubview(vc!.view)
            self._centerViewController!.view.frame = self.centerContainerView.bounds
            self.updateShadowForCenterView()
        }
    }
    open func closeDrawer(animated: Bool, completion: ((Bool) -> Void)?) {
        let newFrame = self.childControllerContainerView.bounds
        self.setNeedsStatusBarAppearanceUpdate()
        self.centerContainerView.frame = newFrame
        self.openSide = .center
    }
    open override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.black
    }
    static let DrawerDefaultWidth: CGFloat = 210.0 //280.0
    static let DrawerDefaultShadowRadius: CGFloat = 10.0
    static let DrawerDefaultShadowOpacity: Float = 0.8
    typealias  DrawerCenterContainerView =  UIView
    public enum DrawerSide: Int {
        case center
        case left
    }
 }複製程式碼

這裡的程式碼,很關鍵的是在DrawerController內的view中,建立了一個子檢視叫做childControllerContainerView,其內在建立一個子檢視centerContainerView,它們幾個的大小都是螢幕大小,檢視層次為:

--view
----childControllerContainerView
------centerContainerView複製程式碼

並約定好:

  1. 全部的Center View Controller的檢視全部放置到centerContainerView內
  2. 全部的left View Controller和right View Controller的檢視全部放置到centerContainerView內
  3. left View Controller和right View Controller的檢視放置完畢後,需要首先隱藏起來,並且sendToBack,保證它是不可見的,也不會遮擋任何Center View Controller的內容

這樣當指定開啟抽屜函式時,centerContainerView整體平移,如果開啟左邊的抽屜,就向右移動,反之亦然。從而Center View Controller的檢視也就跟著平移。並同時把抽屜檢視(left View Controller和right View Controller的檢視)顯示出來,但是寬度不超過MaxDrawerWidth。

通過以上分析,可以加強對Container View Controller概念的理解。

相關文章