再造 “手机QQ” 侧滑菜单(一)——实现侧滑效果
本系列文章中,我们将尝试再造手机QQ的侧滑菜单,力争最大限度接近手Q的实际效果,并使用 Auto Layout 仿造左侧菜单,实现和主视图的联动。
代码示例:https://github.com/johnlui/SwiftSideslipLikeQQ
最终效果:
开发环境
本系列文章的开发环境为:
* OS X 10.10.3
* Xcode Version 6.3 (6D570)
基本数据采集
初步体验,手Q采用的应该是线性动画,即缩放比例等随着手指滑动的距离以一次方程的形式变化。动画达到最大幅度时截图如下(4.7 寸):

提取基本数据:
- 右侧主视图左边界距离屏幕左边界的距离占屏幕宽度的比例为:78%
- 右侧主视图的高度占屏幕高度的比例为:77%
找出线性关系
1. 比例与手指移动距离的关系
字比较丑 o(╯□╰)o。注意:式(1)中的 x 表示“手指移动距离”这个变量,和上面图中表示屏幕宽度的 x 意义不同。

2. 矩形中心向右移动距离和手指移动距离相等
实现侧滑
1. 新建项目,在 StoryBoard 中新增一个 View Controller,并新增一个名为 HomeViewController 的 UIViewController 类,并在 StoryBoard 中完成绑定。
2. 给 HomeViewController 设置背景颜色以示区分。也可以像我一样设一个大 Label 作为更明显的区分。

3. 给 HomeViewController 拖放一个 UIPanGestureRecognizer 并绑定到代码。
从右下角拖一个 Pan Gesture Recognizer 到主窗体上,这一步会让它与 HomeViewController.view 自动绑定。下图为第二步,绑定到代码。

3. 编写代码实现效果:
新建 Common.swift,存储屏幕宽度、高度:
import UIKit
struct Common {
static let screenWidth = UIScreen.mainScreen().applicationFrame.maxX
static let screenHeight = UIScreen.mainScreen().applicationFrame.maxY
}
修改 ViewController:
import UIKit
class ViewController: UIViewController {
var homeViewController: HomeViewController!
var distance: CGFloat = 0
let FullDistance: CGFloat = 0.78
let Proportion: CGFloat = 0.77
override func viewDidLoad() {
super.viewDidLoad()
// 给主视图设置背景
let imageView = UIImageView(image: UIImage(named: "back"))
imageView.frame = UIScreen.mainScreen().bounds
self.view.addSubview(imageView)
// 通过 StoryBoard 取出 HomeViewController 的 view,放在背景视图上面
homeViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
self.view.addSubview(homeViewController.view)
// 绑定 UIPanGestureRecognizer
homeViewController.panGesture.addTarget(self, action: Selector("pan:"))
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// 响应 UIPanGestureRecognizer 事件
func pan(recongnizer: UIPanGestureRecognizer) {
let x = recongnizer.translationInView(self.view).x
let trueDistance = distance + x // 实时距离
// 如果 UIPanGestureRecognizer 结束,则激活自动停靠
if recongnizer.state == UIGestureRecognizerState.Ended {
if trueDistance > Common.screenWidth * (Proportion / 3) {
showLeft()
} else if trueDistance < Common.screenWidth * -(Proportion / 3) {
showRight()
} else {
showHome()
}
return
}
// 计算缩放比例
var proportion: CGFloat = recongnizer.view!.frame.origin.x >= 0 ? -1 : 1
proportion *= trueDistance / Common.screenWidth
proportion *= 1 - Proportion
proportion /= 0.6
proportion += 1
if proportion <= Proportion { // 若比例已经达到最小,则不再继续动画
return
}
// 执行平移和缩放动画
recongnizer.view!.center = CGPointMake(self.view.center.x + trueDistance, self.view.center.y)
recongnizer.view!.transform = CGAffineTransformScale(CGAffineTransformIdentity, proportion, proportion)
}
// 封装三个方法,便于后期调用
// 展示左视图
func showLeft() {
distance = self.view.center.x * (FullDistance + Proportion / 2)
doTheAnimate(self.Proportion)
}
// 展示主视图
func showHome() {
distance = 0
doTheAnimate(1)
}
// 展示右视图
func showRight() {
distance = self.view.center.x * -(FullDistance + Proportion / 2)
doTheAnimate(self.Proportion)
}
// 执行三种试图展示
func doTheAnimate(proportion: CGFloat) {
UIView.animateWithDuration(0.3, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
self.homeViewController.view.center = CGPointMake(self.view.center.x + self.distance, self.view.center.y)
self.homeViewController.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, proportion, proportion)
}, completion: nil)
}
}
4. 查看效果

评论:
过路人
2017-03-09 14:26
2017-03-09 14:26
展示左右视图的 计算结果并不符合0.77的定义啊 distance = WIDTH * (FullDistance + Proportion / 2) - self.view.center.x
zero思念的力量
2016-08-02 11:23
强烈点赞还有
panGesture添加事件后油加了这句话才响应的事件
homeViewController.view.addGestureRecognizer(homeViewController.panGesture)
,效果是好好好 学到好多东西
2016-08-02 11:23
强烈点赞还有panGesture添加事件后油加了这句话才响应的事件
homeViewController.view.addGestureRecognizer(homeViewController.panGesture)
,效果是好好好 学到好多东西
逗霸帝
2016-03-07 23:04
2016-03-07 23:04
setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key panGesture.' 出现这样的错误。。。怎么破?
小班
2015-12-21 20:24
2015-12-21 20:24
还有, 我 将 if trueDistance > Common.screenWidth * (Proportion / 3) { 这句的Common.screenWidth输出,为什么等于零啊!
IOS潘
2015-12-02 00:30
2015-12-02 00:30
我是新手,在看github上的这个qq项目,但是我一运行就报‘Index' does not have a member named ‘advanceBy‘错误。用的xcode7.0。
黑豆腐
2015-07-17 16:58
2015-07-17 16:58
弱弱的问一下,为什么滑动最大距离时,水平移动距离distance = self.view.center.x * (FullDistance + Proportion / 2)而不是由你画的图得到distance = common.screenWidth * (FullDistance + Proportion / 2) - self.view.center.x,求解?
lastcc
2015-07-16 22:53
2015-07-16 22:53
static let screenWidth = UIScreen.mainScreen().applicationFrame.maxX
这里如果稍后旋转屏幕会怎样?这个值不会变了
是否应该用observer?
这里如果稍后旋转屏幕会怎样?这个值不会变了
是否应该用observer?
lastcc
2015-07-16 21:23
很好!
就是想看这样有水平的文章!
楼主你敢回复我一个吗。
顺便我想问下怎么看起来好像有点hack的意味
homeViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
2015-07-16 21:23

很好!
就是想看这样有水平的文章!
楼主你敢回复我一个吗。
顺便我想问下怎么看起来好像有点hack的意味
homeViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
呵呵哒
2015-06-12 14:39
2015-06-12 14:39
少这一句 // 绑定 UIPanGestureRecognizer
homeViewController.myPan.addTarget(self, action: Selector("pan:"))
homeViewController.view.addGestureRecognizer(homeViewController.myPan);
homeViewController.myPan.addTarget(self, action: Selector("pan:"))
homeViewController.view.addGestureRecognizer(homeViewController.myPan);
machine9
2015-05-03 15:52
2015-05-03 15:52
楼主厉害!!!
p.s. 似乎这段代码里少了一句addGestureRecognizer,可能会导致panGestureRecognizer无法使用
p.s. 似乎这段代码里少了一句addGestureRecognizer,可能会导致panGestureRecognizer无法使用
wuou
2015-04-17 16:31
2015-04-17 16:31
重点是找出线性关系! 这里给了我很大启发
,然后联动可以这样做 :
1、这是leftVC.view的缩放比例:
找出这两点 (0.77 , 0) (1 , screenwidth*0.78) ==> (left.view的缩放比例, main.view.or.x坐标),线性关系
2、这是leftVC.view的移动:
找出这两点(self.view.center.x , screenwidth * 0.78) (center - 80 , 0) ==> (屏幕中心点x的坐标 ,main.view.or.x坐标),线性关系
,然后联动可以这样做 : 1、这是leftVC.view的缩放比例:
找出这两点 (0.77 , 0) (1 , screenwidth*0.78) ==> (left.view的缩放比例, main.view.or.x坐标),线性关系
2、这是leftVC.view的移动:
找出这两点(self.view.center.x , screenwidth * 0.78) (center - 80 , 0) ==> (屏幕中心点x的坐标 ,main.view.or.x坐标),线性关系
wuou
2015-04-15 20:29
2015-04-15 20:29
用UITouch更方便
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event








?
2017-06-25 22:45