Swift封装-滑出式导航栏

效果图.gif

前言:
本文将会创建以下几个主类:

DWContainerViewController:这包含了左视图,中视图和右视图控制器的视图,并处理动画和滑动等操作。
DWCenterViewController:中央面板。
DWSidePanelViewController:用于左侧和右侧面板。

创建storyboard,如图:


image.png

并且创建DWCenterViewControllerDWStarCellDWSidePanelViewController,关联上图中的storyboard

DWCenterViewController为滑出式导航的类,代码:

class DWCenterViewController: UIViewController {

    var delegate: DWCenterViewControllerDelegate?
    
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var creatorLabel: UILabel!
    
    @IBAction func actorsTapped(_ sender: Any) {
        //左边点击事件   
    }
}

DWStarCell代码:

class DWStarCell: UITableViewCell {
    
    @IBOutlet weak var animalImageView: UIView!
    @IBOutlet weak var imageNameLabel: UILabel!
    @IBOutlet weak var imageCreatorLabel: UILabel!

}

创建DWStar.swift模型,并且初始化cell显示的数据,代码如下:

//结构体
struct DWStar {
    let title: String
    let creator: String
    let image: UIImage?
   //重写init方法
    init(title: String, creator: String, image:UIImage?) {
        self.title = title
        self.creator = creator
        self.image = image
    }
    
    static func allActors() -> [DWStar] {
        return [
            DWStar(title: "林志玲", creator: "Dwyane", image: UIImage(named: "ID-100113060")),
            DWStar(title: "张歆艺", creator: "Dwyane", image: UIImage(named: "ID-10022760")),
            DWStar(title: "李连杰", creator: "Dwyane", image: UIImage(named: "ID-10091065")),
            DWStar(title: "周润发", creator: "Dwyane", image: UIImage(named: "ID-10047796")),
            DWStar(title: "舒淇", creator: "Dwyane", image: UIImage(named: "ID-10092572")),
            DWStar(title: "鹿晗", creator: "Dwyane", image: UIImage(named: "ID-10041194")),
            DWStar(title: "黄晓明", creator: "Dwyane", image: UIImage(named: "ID-10017782")),
            DWStar(title: "李赛凤", creator: "Dwyane", image: UIImage(named: "ID-10091745")),
            DWStar(title: "赵丽颖", creator: "Dwyane Ratcliff", image: UIImage(named: "ID-10056941")),
            DWStar(title: "周星驰", creator: "Dwyane", image: UIImage(named: "ID-10019208")),
            DWStar(title: "杜海涛", creator: "Dwyane", image: UIImage(named: "ID-10011404"))
        ]
    }
   

创建DWCenterViewControllerDelegate,并且创建协议方法:

//创建协议 optional:类似oc的可选
@objc
protocol DWCenterViewControllerDelegate {
    @objc optional func toggleLeftPanel()  //切换左边的容器
    @objc optional func collapseSidePanels() //折叠侧边的容器
}

DWCenterViewController.swiftactorsTapped点击方法调用协议方法toggleLeftPanel,如下:

@IBAction func actorsTapped(_ sender: Any) {
    //左边点击事件
    delegate?.toggleLeftPanel?()
}

创建DWSidePanelViewControllerDelegate.swift,并创一个协议

protocol DWSidePanelViewControllerDelegate {
    func didSelectAnimal(_ animal: DWStar)  //选择的动物
}

DWCenterViewController.swift实现DWSidePanelViewControllerDelegate的协议方法:

// MARK: - DWCenterViewController delegate
//在该类实现delegate的方法
extension DWCenterViewController: DWSidePanelViewControllerDelegate {
    func didSelectAnimal(_ animal: DWStar) { //实现协议方法
        imageView.image = animal.image
        titleLabel.text = animal.title
        creatorLabel.text = animal.creator
        
        delegate?.collapseSidePanels?() //折叠侧容器
    }  
}

创建DWContainerViewController.swift,并定义一些属性:

//枚举  滑动状态
enum SlideOutState {
    case bothCollapsed  //侧容器折叠
    case leftPanelExpanded   //左容器展开
    case rightPanelExpanded  //右容器展开
}

//定义属性
var centerNavigationController: UINavigationController!
var centerViewController: DWCenterViewController!
//当前状态
var currentState: SlideOutState = .bothCollapsed {
    didSet { //在属性值改变后触发didSet
        let shoulShowShadow = currentState != .bothCollapsed
    }
}

var leftViewController: DWSidePanelViewController?
var centerPanelExpandedOffset: CGFloat = 60 //该值是中央视图控制器在屏幕外动画显示后左侧可见的宽度(以点为单位)

扩展UIStoryboard,方便取得VC,代码如下:

private extension UIStoryboard {
    static func mainStoryboard() -> UIStoryboard {
        return UIStoryboard(name: "Main", bundle: Bundle.main)
    }
    
    static func centerViewController() -> DWCenterViewController? {
        return mainStoryboard().instantiateViewController(withIdentifier: "DWCenterViewController") as? DWCenterViewController
    }
    
    static func leftViewController() -> DWSidePanelViewController? {
        return mainStoryboard().instantiateViewController(withIdentifier: "LeftViewController") as? DWSidePanelViewController
    }    
}

viewDidLoad添加如下:

//添加中间控制器并显示
centerViewController = UIStoryboard.centerViewController()
centerViewController.delegate = self

//将centerViewController包装在导航控制器中
centerNavigationController = UINavigationController(rootViewController: centerViewController)
//加入centerViewcontroller的视图
view.addSubview(centerNavigationController.view)
//加入centerViewcontroller的视图控制器
addChildViewController(centerNavigationController)
centerNavigationController.didMove(toParentViewController: self)

实现协议方法(添加左侧容器一起动画的发生代码):

extension DWContainerViewController: DWCenterViewControllerDelegate { 
}

在协议方法中,添加

func toggleLeftPanel() {
    //如果当前状态:左边为展开
    let notAlreadyExpanded = (currentState != .leftPanelExpanded)
    
    if notAlreadyExpanded {
        addLeftPanelViewController() //添加左边容器
    }
    //左边容器展开的动画
    animateLeftPanel(shouldExpand: notAlreadyExpanded)
}

//折叠侧边容器
func collapseSidePanels() {
    switch currentState {
    case .leftPanelExpanded:
        toggleLeftPanel()
    default:
        break
    }
}

//左边的VC
func addLeftPanelViewController() {//guard语句判断其后的表达式布尔值为false时,才会执行之后代码块里的代码,如果为true,则跳过整个guard语句
    guard leftViewController == nil else { return }
    
    if let vc = UIStoryboard.leftViewController() {
        vc.animals = DWStar.allActors()
        addChildSidePanelController(vc)
        leftViewController = vc
    }
}

func addChildSidePanelController(_ sidePanelController: DWSidePanelViewController) {
    sidePanelController.delegate = centerViewController
    view.insertSubview(sidePanelController.view, at: 0)
    
    addChildViewController(sidePanelController)
    sidePanelController.didMove(toParentViewController: self)
}


//右边的VC
func addRightPanelViewController() {
    
}

func animateLeftPanel(shouldExpand: Bool) {
    if shouldExpand {
        currentState = .leftPanelExpanded
        animateCenterPanelXPosition(targetPosition: centerNavigationController.view.frame.width - centerPanelExpandedOffset)
    } else {
        animateCenterPanelXPosition(targetPosition: 0, completion: { (_) in
            self.currentState = .bothCollapsed
            self.leftViewController?.view.removeFromSuperview()
            self.leftViewController = nil
        })
    }
}

//检查是否被告知展开或折叠侧面板。如果它应该展开,那么它将设置当前状态以指示左侧面板展开,然后为中央面板设置动画,以便打开。否则,它将关闭中央面板,然后移除其视图,并设置当前状态以指示其关闭。
func animateCenterPanelXPosition(targetPosition: CGFloat, completion: ((Bool) -> Void)? = nil) {
    UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: .curveEaseOut, animations: {
        self.centerNavigationController.view.frame.origin.x = targetPosition
    }, completion: completion)
}



func showShadowForCenterViewController(_ shouldShowShadow: Bool) {
    if shouldShowShadow {
        centerNavigationController.view.layer.shadowOpacity = 0.8
    } else {
        centerNavigationController.view.layer.shadowOpacity = 0.0
    }
}

}

添加手势,更改DWCenterViewController的导航栏x坐标

// 手势
// MARK: Gesture recognizer
extension DWContainerViewController: UIGestureRecognizerDelegate {
    @objc func handlePanGesture(_ recognize: UIPanGestureRecognizer) {
        let gestureIsDraggingFromLeftToRight = (recognize.velocity(in: view).x > 0)
        
        switch recognize.state {
        case .began:
            if currentState == .bothCollapsed {
                if gestureIsDraggingFromLeftToRight {
                    //左边
                    addLeftPanelViewController()
                } else {
                    //右边
                    addRightPanelViewController()
                }
                showShadowForCenterViewController(true)
            }
        case .changed:
            if let rview = recognize.view {
                rview.center.x = rview.center.x + recognize.translation(in: view).x
                recognize.setTranslation(CGPoint.zero, in: view)
                //translationInView:方法获取View的偏移量  setTranslation:方法设置手势的偏移量
            }
        case .ended: //根据不同的方向移动左或右
            if let _ = leftViewController,
                let rview = recognize.view {
                let hasMovedGreaterThanHalfway = rview.center.x > view.bounds.size.width
                animateLeftPanel(shouldExpand: hasMovedGreaterThanHalfway)
            }
            
        default:
            break
        }
        
    }
}

代码传送门
注意:

1、自己添加tableView,需要手动添加dataSource 和 delegate
2、调节tableView的row height

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容