侧滑菜单是现在的APP上很常见的功能,其效果是在主界面用手指向右滑动,就可以将菜单展示出来,而主界面会被隐藏大部分,但是仍有左侧的一小部分同菜单一起展示。
虽然网上也有很多实现这种slide view效果的第三方库,但如果想自己写代码实现也是很简单的,效果图如下:
1,程序页面结构
MainViewController:主页视图
MenuViewController:菜单视图,当主视图侧滑后显示
ViewController:页面容器视图,将上面两个视图加入到这里面
2,StoryBoard配置
在StoryBoard中添加两个新的View Controller(Storyoard ID分别是mainView、menuView),同时分别绑定MainViewController和MenuViewController这两个类。
3,代码讲解
(1)页面初始化完毕后我们会先把主页视图(MainViewController)添加进来,同时对其设置个拖动手势(UIPanGestureRecognizer)。
(2)当手指在屏幕从左向右滑动时,创建菜单视图(MenuViewController)并添加到页面最底部。同时主页视图会随着手指的移动做线性移动。
(3)手指离开后,根据主页视图的位置(是否滑动超过一半),程序自动将主页视图完全展开或收起。
(4)菜单完全展开时,手指点击主页突出的部分也会自动收起菜单。
(5)menuViewExpandedOffset属性是设置菜单展示出来后,主页面在左侧露出部分的宽度。
(6)currentState属性保存菜单的状态,同时监听它的didSet事件来设置主页面阴影(当菜单显示出来的时候,主页边框会添加阴影,这样有层次感,效果更好些。)
4,ViewController.swift代码如下
import UIKit
class ViewController: UIViewController {
// 主页面控制器
var mainViewController:MainViewController!
// 菜单页控制器
var menuViewController:MenuViewController?
// 菜单页当前状态
var currentState = MenuState.Collapsed {
didSet {
//菜单展开的时候,给主页面边缘添加阴影
let shouldShowShadow = currentState != .Collapsed
showShadowForMainViewController(shouldShowShadow)
}
}
// 菜单打开后主页在屏幕右侧露出部分的宽度
let menuViewExpandedOffset: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
//添加主页面
mainViewController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewControllerWithIdentifier("mainView") as! MainViewController
view.addSubview(mainViewController.view)
//建立父子关系
addChildViewController(mainViewController)
mainViewController.didMoveToParentViewController(self)
//添加拖动手势
let panGestureRecognizer = UIPanGestureRecognizer(target: self,
action: "handlePanGesture:")
mainViewController.view.addGestureRecognizer(panGestureRecognizer)
//单击收起菜单手势
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: "handlePanGesture")
mainViewController.view.addGestureRecognizer(tapGestureRecognizer)
}
//拖动手势响应
func handlePanGesture(recognizer: UIPanGestureRecognizer) {
switch(recognizer.state) {
// 刚刚开始滑动
case .Began:
// 判断拖动方向
let dragFromLeftToRight = (recognizer.velocityInView(view).x > 0)
// 如果刚刚开始滑动的时候还处于主页面,从左向右滑动加入侧面菜单
if (currentState == .Collapsed && dragFromLeftToRight) {
currentState = .Expanding
addMenuViewController()
}
// 如果是正在滑动,则偏移主视图的坐标实现跟随手指位置移动
case .Changed:
let positionX = recognizer.view!.frame.origin.x +
recognizer.translationInView(view).x
//页面滑到最左侧的话就不许要继续往左移动
recognizer.view!.frame.origin.x = positionX < 0 ? 0 : positionX
recognizer.setTranslation(CGPointZero, inView: view)
// 如果滑动结束
case .Ended:
//根据页面滑动是否过半,判断后面是自动展开还是收缩
let hasMovedhanHalfway = recognizer.view!.center.x > view.bounds.size.width
animateMainView(hasMovedhanHalfway)
default:
break
}
}
//单击手势响应
func handlePanGesture() {
//如果菜单是展开的点击主页部分则会收起
if currentState == .Expanded {
animateMainView(false)
}
}
// 添加菜单页
func addMenuViewController() {
if (menuViewController == nil) {
menuViewController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewControllerWithIdentifier("menuView") as? MenuViewController
// 插入当前视图并置顶
view.insertSubview(menuViewController!.view, atIndex: 0)
// 建立父子关系
addChildViewController(menuViewController!)
menuViewController!.didMoveToParentViewController(self)
}
}
//主页自动展开、收起动画
func animateMainView(shouldExpand: Bool) {
// 如果是用来展开
if (shouldExpand) {
// 更新当前状态
currentState = .Expanded
// 动画
animateMainViewXPosition(CGRectGetWidth(mainViewController.view.frame) -
menuViewExpandedOffset)
}
// 如果是用于隐藏
else {
// 动画
animateMainViewXPosition(0) { finished in
// 动画结束之后s更新状态
self.currentState = .Collapsed
// 移除左侧视图
self.menuViewController?.view.removeFromSuperview()
// 释放内存
self.menuViewController = nil;
}
}
}
//主页移动动画(在x轴移动)
func animateMainViewXPosition(targetPosition: CGFloat,
completion: ((Bool) -> Void)! = nil) {
//usingSpringWithDamping:1.0表示没有弹簧震动动画
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 1.0,
initialSpringVelocity: 0, options: .CurveEaseInOut, animations: {
self.mainViewController.view.frame.origin.x = targetPosition
}, completion: completion)
}
//给主页面边缘添加、取消阴影
func showShadowForMainViewController(shouldShowShadow: Bool) {
if (shouldShowShadow) {
mainViewController.view.layer.shadowOpacity = 0.8
} else {
mainViewController.view.layer.shadowOpacity = 0.0
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
// 菜单状态枚举
enum MenuState {
case Collapsed // 未显示(收起)
case Expanding // 展开中
case Expanded // 展开
}
源码下载: hangge_1028.zip
5,功能改进
如果只有左右滑动能调出菜单的话,会显得把菜单功能隐藏太深,可能用户使用半天还不知道有这个菜单。
所以通常除了滑动调出菜单,页面上也会提供个菜单按钮,一般放置在导航栏上。点击按钮同样可以打开,收起菜单。效果图如下:
(1)在StoryBoard中,点击首页面(Main)的Scene,选择Editor -> Embed In -> Navigation Controller 添加导航控制器
(2)设置导航控制器的StoryBoard ID为 mainNavigaiton,同时给主页面的导航栏左侧添加一个菜单按钮。
(3)ViewController.swift 代码如下(高亮处为修改过的地方):
import UIKit
class ViewController: UIViewController {
// 主页导航控制器
var mainNavigationController:UINavigationController!
// 主页面控制器
var mainViewController:MainViewController!
// 菜单页控制器
var menuViewController:MenuViewController?
// 菜单页当前状态
var currentState = MenuState.Collapsed {
didSet {
//菜单展开的时候,给主页面边缘添加阴影
let shouldShowShadow = currentState != .Collapsed
showShadowForMainViewController(shouldShowShadow)
}
}
// 菜单打开后主页在屏幕右侧露出部分的宽度
let menuViewExpandedOffset: CGFloat = 60
override func viewDidLoad() {
super.viewDidLoad()
//初始化主视图
mainNavigationController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewControllerWithIdentifier("mainNavigaiton")
as! UINavigationController
view.addSubview(mainNavigationController.view)
//指定Navigation Bar左侧按钮的事件
mainViewController = mainNavigationController.viewControllers.first
as! MainViewController
mainViewController.navigationItem.leftBarButtonItem?.action = Selector("showMenu")
//添加拖动手势
let panGestureRecognizer = UIPanGestureRecognizer(target: self,
action: "handlePanGesture:")
mainNavigationController.view.addGestureRecognizer(panGestureRecognizer)
//单击收起菜单手势
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: "handlePanGesture")
mainNavigationController.view.addGestureRecognizer(tapGestureRecognizer)
}
//导航栏左侧按钮事件响应
func showMenu() {
//如果菜单是展开的则会收起,否则就展开
if currentState == .Expanded {
animateMainView(false)
}else {
addMenuViewController()
animateMainView(true)
}
}
//拖动手势响应
func handlePanGesture(recognizer: UIPanGestureRecognizer) {
switch(recognizer.state) {
// 刚刚开始滑动
case .Began:
// 判断拖动方向
let dragFromLeftToRight = (recognizer.velocityInView(view).x > 0)
// 如果刚刚开始滑动的时候还处于主页面,从左向右滑动加入侧面菜单
if (currentState == .Collapsed && dragFromLeftToRight) {
currentState = .Expanding
addMenuViewController()
}
// 如果是正在滑动,则偏移主视图的坐标实现跟随手指位置移动
case .Changed:
let positionX = recognizer.view!.frame.origin.x +
recognizer.translationInView(view).x
//页面滑到最左侧的话就不许要继续往左移动
recognizer.view!.frame.origin.x = positionX < 0 ? 0 : positionX
recognizer.setTranslation(CGPointZero, inView: view)
// 如果滑动结束
case .Ended:
//根据页面滑动是否过半,判断后面是自动展开还是收缩
let hasMovedhanHalfway = recognizer.view!.center.x > view.bounds.size.width
animateMainView(hasMovedhanHalfway)
default:
break
}
}
//单击手势响应
func handlePanGesture() {
//如果菜单是展开的点击主页部分则会收起
if currentState == .Expanded {
animateMainView(false)
}
}
// 添加菜单页
func addMenuViewController() {
if (menuViewController == nil) {
menuViewController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewControllerWithIdentifier("menuView") as? MenuViewController
// 插入当前视图并置顶
view.insertSubview(menuViewController!.view, atIndex: 0)
// 建立父子关系
addChildViewController(menuViewController!)
menuViewController!.didMoveToParentViewController(self)
}
}
//主页自动展开、收起动画
func animateMainView(shouldExpand: Bool) {
// 如果是用来展开
if (shouldExpand) {
// 更新当前状态
currentState = .Expanded
// 动画
animateMainViewXPosition(CGRectGetWidth(mainNavigationController.view.frame) -
menuViewExpandedOffset)
}
// 如果是用于隐藏
else {
// 动画
animateMainViewXPosition(0) { finished in
// 动画结束之后s更新状态
self.currentState = .Collapsed
// 移除左侧视图
self.menuViewController?.view.removeFromSuperview()
// 释放内存
self.menuViewController = nil;
}
}
}
//主页移动动画(在x轴移动)
func animateMainViewXPosition(targetPosition: CGFloat,
completion: ((Bool) -> Void)! = nil) {
//usingSpringWithDamping:1.0表示没有弹簧震动动画
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 1.0,
initialSpringVelocity: 0, options: .CurveEaseInOut, animations: {
self.mainNavigationController.view.frame.origin.x = targetPosition
}, completion: completion)
}
//给主页面边缘添加、取消阴影
func showShadowForMainViewController(shouldShowShadow: Bool) {
if (shouldShowShadow) {
mainNavigationController.view.layer.shadowOpacity = 0.8
} else {
mainNavigationController.view.layer.shadowOpacity = 0.0
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
// 菜单状态枚举
enum MenuState {
case Collapsed // 未显示(收起)
case Expanding // 展开中
case Expanded // 展开
}