倉庫github/DrawController(github.com/sascha/Draw…)
是一個Swift語言編寫的抽屜庫。它可以傳入一箇中心檢視控制器,一個左側檢視控制器,並由一個MaxDrawerWidth屬性。建立完畢後,中心檢視控制器的檢視內容佔據全部螢幕;當執行開啟抽屜的函式時,整個中心控制的檢視內容一起向右平移MaxDrawerWidth這麼寬,並且顯示左側檢視控制器的內容到螢幕上,內容佔滿螢幕,但是寬度不超過MaxDrawerWidth指定的值。
研究此程式碼庫的時候,發現它是一個很好的解說Container View Controller[developer.apple.com/library/con…] 概念的例子。這裡面:
- 類DrawerController就是一個Container View Controller
- 其他的檢視控制器就是Child View Controller
- 第一個類和其他類構成了父子關係
- 檢視控制器內的檢視其實都是放置到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複製程式碼
並約定好:
- 全部的Center View Controller的檢視全部放置到centerContainerView內
- 全部的left View Controller和right View Controller的檢視全部放置到centerContainerView內
- left View Controller和right View Controller的檢視放置完畢後,需要首先隱藏起來,並且sendToBack,保證它是不可見的,也不會遮擋任何Center View Controller的內容
這樣當指定開啟抽屜函式時,centerContainerView整體平移,如果開啟左邊的抽屜,就向右移動,反之亦然。從而Center View Controller的檢視也就跟著平移。並同時把抽屜檢視(left View Controller和right View Controller的檢視)顯示出來,但是寬度不超過MaxDrawerWidth。
通過以上分析,可以加強對Container View Controller概念的理解。