iOS-如何做一个滑动侧边栏(抽屉导航)

点击下载源文件

iOS-如何做一个滑动侧边栏(抽屉导航)_第1张图片
初始页

iOS-如何做一个滑动侧边栏(抽屉导航)_第2张图片
滑开中

iOS-如何做一个滑动侧边栏(抽屉导航)_第3张图片
滑开后

mainstoryboard中的三个页面,中间页-左边栏-右边栏
核心思想是把这三个页面全部放到一个container的控制器里面,在里面做文章。

iOS-如何做一个滑动侧边栏(抽屉导航)_第4张图片
mainstoryboard

在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控制器里面的方法暂时不懂也没关系,可以先当作一个示例,以后慢慢就会懂了。

以上。

你可能感兴趣的:(iOS-如何做一个滑动侧边栏(抽屉导航))