側滑菜單是現在的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 // 展開
}