点击下载源文件
mainstoryboard中的三个页面,中间页-左边栏-右边栏
核心思想是把这三个页面全部放到一个container
的控制器里面,在里面做文章。
在AppDelegate.swift里注册
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//创建containerViewController并添加到window去
window = UIWindow(frame: UIScreen.main.bounds)
let containerViewController = ContainerViewController()
window!.rootViewController = containerViewController
window!.makeKeyAndVisible()
return true
}
CenterViewControllerDelegate.swift
import UIKit
//单独一个文件写代理,增加扩展性
@objc
protocol CenterViewControllerDelegate {
@objc optional func toggleLeftPanel()
@objc optional func toggleRightPanel()
@objc optional func collapseSidePanels()
}
CenterViewController.swift
import UIKit
class CenterViewController: UIViewController {
@IBOutlet weak fileprivate var imageView: UIImageView!
@IBOutlet weak fileprivate var titleLabel: UILabel!
@IBOutlet weak fileprivate var creatorLabel: UILabel!
//定义一个变量指向代理(这个代理里面的方法在别的控制器实现,我在本控制器只需要调用其中的方法即可)
//在别的控制器里面的viewdidload里面把centerviewcontroller.delegate = self
//并在别的控制器里实现这个协议,并实现里面的方法
var delegate: CenterViewControllerDelegate?
// MARK: Button actions
@IBAction func kittiesTapped(_ sender: Any) {
delegate?.toggleLeftPanel?()
}
@IBAction func puppiesTapped(_ sender: Any) {
delegate?.toggleRightPanel?()
}
}
center控制器什么都不做,把事情(协议里规定的方法)委托给container
控制器做。所以定一个delegate属性,指向这个代理。
ContainerViewController.swift(核心代码)
import UIKit
import QuartzCore
class ContainerViewController: UIViewController {
//当前滑出框的状态
enum SlideOutState {
case bothCollapsed
case leftPanelExpanded
case rightPanelExpanded
}
//中心视图和其导航(在didload里面赋值)
var centerNaviController: UINavigationController!
var centerVC: CenterViewController!
//当前状态,并做观察者以便处理阴影效果
var currentState: SlideOutState = .bothCollapsed{
didSet{
let shouldShow = currentState != .bothCollapsed
showShadow(shouldShow: shouldShow)
}
}
//左边栏
var leftViewController: SidePanelViewController?
var rightViewController: SidePanelViewController?
let centerPanelExpandedOffset: CGFloat = 60//在展开边栏后中心视图还剩多少可见
override func viewDidLoad() {
super.viewDidLoad()
centerVC = UIStoryboard.centerViewController()//获取到center控制器实例
centerVC.delegate = self
//实力化一个导航控制器-->把center控制器放入导航控制器中,以便往center控制器里面push边栏以及展示导航按钮
centerNaviController = UINavigationController(rootViewController: centerVC)
//为本容器控制器加上视图及导航控制器
view.addSubview(centerNaviController.view)
self.addChildViewController(centerNaviController)
centerNaviController.didMove(toParentViewController: self)//加了子控制器之后必须这一步
//手势--实例化一个手指平移,交由handlePan函数处理(在下方的extension中)
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(pan:)))
centerNaviController.view.addGestureRecognizer(pan)
}
}
//实现代理里面的方法,以便委托者在自己的controller里面直接调用
//里面有些方法并不是协议规定的
extension ContainerViewController: CenterViewControllerDelegate {
//弹出左边框
func toggleLeftPanel() {
let isCollapse = (currentState != .leftPanelExpanded)
//如果左边没展开,则添加左边控制器
if isCollapse {
addLeftPanelViewController()
}
//无论有没有展开,都需要弹出或弹进动画
animateLeftPanel(shouldExpand: isCollapse)
}
//弹出右边框--基本同上
func toggleRightPanel() {
let isCollapse = (currentState != .rightPanelExpanded)
if isCollapse{
addRightPanelViewController()
}
animateRightPanel(shouldExpand: isCollapse)
}
//上面两个函数要到的共有方法
func addLeftPanelViewController() {
//如果左控制器已有值,则返回。没值则继续
guard leftViewController == nil else { return }
//获取到左边控制器实例,并获取数据--并调用下一个函数
if let vc = UIStoryboard.leftViewController() {
vc.animals = Animal.allCats()
addChildSidePanelController(sidePanelController:vc)
leftViewController = vc
}
}
//基本同上
func addRightPanelViewController() {
guard rightViewController == nil else{ return }
if let vc = UIStoryboard.rightViewController() {
vc.animals = Animal.allDogs()
addChildSidePanelController(sidePanelController: vc)
rightViewController = vc
}
}
//上面两个函数中用到的共有方法---此方法和上面didload中增加导航视图及控制器相似
func addChildSidePanelController(sidePanelController:UIViewController){
view.insertSubview(sidePanelController.view, at: 0)//0代理子视图是位于center视图的下方
self.addChildViewController(sidePanelController)
sidePanelController.didMove(toParentViewController: self)
}
//动画部分--两个toggle函数要到的共有方法
func animateLeftPanel(shouldExpand: Bool) {
if shouldExpand{
currentState = .leftPanelExpanded
//展示x轴拉伸至什么位置
animateX(targetPosition: centerNaviController.view.frame.width - centerPanelExpandedOffset)
}else{
animateX(targetPosition: 0){ finished in
//做一些移除处理
self.currentState = .bothCollapsed
self.leftViewController?.view.removeFromSuperview()
self.leftViewController = nil
}
}
}
func animateRightPanel(shouldExpand: Bool) {
if shouldExpand{
currentState = .rightPanelExpanded
animateX(targetPosition: centerPanelExpandedOffset - centerNaviController.view.frame.width)
}else{
animateX(targetPosition: 0){ finished in
self.currentState = .bothCollapsed
self.rightViewController?.view.removeFromSuperview()
self.rightViewController = nil
}
}
}
//上面两个函数中用到的共有方法
func animateX(targetPosition: CGFloat,completion: ((Bool)->Void)? = nil){
UIView.animate(withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0,
options: .curveEaseInOut, animations: {
self.centerNaviController.view.frame.origin.x = targetPosition
}, completion: completion)
}
//阴影
func showShadow(shouldShow: Bool){
centerNaviController.view.layer.shadowOpacity = shouldShow ? 0.8 : 0
}
}
extension ContainerViewController:UIGestureRecognizerDelegate{
@objc func handlePan(pan:UIPanGestureRecognizer){
//用手势速度(x轴)来判断用户是否在向右平移
let LeftToRight = (pan.velocity(in: view).x > 0)
//平移的三种状态要分别处理
switch pan.state {
case .began:
if currentState == .bothCollapsed {
if LeftToRight {
addLeftPanelViewController()
} else {
addRightPanelViewController()
}
showShadow(shouldShow: true)
}
case .changed:
if let rview = pan.view {
rview.center.x = rview.center.x + pan.translation(in: view).x
pan.setTranslation(CGPoint.zero, in: view)
}
case .ended:
if let _ = leftViewController,
let rview = pan.view {
//看边栏平移是否超过一半来决定-->手指松开之后是收起边栏还是展开边栏
let hasHalfway = rview.center.x > view.bounds.size.width
animateLeftPanel(shouldExpand: hasHalfway)
} else if let _ = rightViewController,
let rview = pan.view {
let hasHalfway = rview.center.x < 0
animateRightPanel(shouldExpand: hasHalfway)
}
default:
break
}
}
}
private extension UIStoryboard {
static func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
static func leftViewController() -> SidePanelViewController? {
return mainStoryboard().instantiateViewController(withIdentifier: "LeftViewController") as? SidePanelViewController
}
static func rightViewController() -> SidePanelViewController? {
return mainStoryboard().instantiateViewController(withIdentifier: "RightViewController") as? SidePanelViewController
}
static func centerViewController() -> CenterViewController? {
return mainStoryboard().instantiateViewController(withIdentifier: "CenterViewController") as? CenterViewController
}
}
container
控制器实现了这个代理(extension中),并在didload
里面指定center
控制器的代理人是自己,故center
控制器才可以什么都不干,直接调用toggleLeftPanel
等方法,让container
控制器去做。
关于其他几个文件
Animal.swift
---数据
SidePanelViewController.swift
---边栏控制器(tableview的知识点)
AnimalCell.swift
---边栏单元格(tableview的cell)
SidePanelViewControllerDelegate.swift
---与本文无关,可以做一些扩展功能用
如果container
控制器里面的方法暂时不懂也没关系,可以先当作一个示例,以后慢慢就会懂了。
以上。