探索iPhone控制中心交互实现

【项目Github地址】
先展示一张效果图:

Untitled.gif

从图片上可以看出都是弹簧效果,自然而然就会想到用系统的UIScrollView类来实现。
接下来我将仔细的一步步的教你完成这个项目。
首先咱们先创建一个工程,打开ViewController.m文件,我们先简单创建一个UIScrollView,让他的contentSize为两倍的屏幕宽度,再加入两张图片。
代码如下:

#import "ViewController.h"

#define kScreenWidth [UIScreen mainScreen].bounds.size.width
#define kScreenHeight [UIScreen mainScreen].bounds.size.height
#define kLeftRightMargin 8
#define kBottomMargin 16
#define kImageViewWidth (kScreenWidth - kLeftRightMargin*2)
#define kImageViewHeight (kImageViewWidth*392/344.5)
#define kImageViewY (kScreenHeight - kBottomMargin - kImageViewHeight)

@interface ViewController () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *leftView;
@property (nonatomic, strong) UIImageView *rightView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor lightGrayColor];
    self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    self.scrollView.delegate = self;
    self.scrollView.contentSize = CGSizeMake(kScreenWidth*2, kScreenHeight);
    self.scrollView.pagingEnabled = YES;
    self.scrollView.showsHorizontalScrollIndicator = NO;
    self.scrollView.showsVerticalScrollIndicator = NO;
    [self.view addSubview:self.scrollView];
    
    self.leftView = [[UIImageView alloc] initWithFrame:CGRectMake(kLeftRightMargin, kImageViewY, kImageViewWidth, kImageViewHeight)];
    self.leftView.backgroundColor = [UIColor yellowColor];
    self.leftView.layer.cornerRadius = 15;
    self.leftView.layer.masksToBounds = YES;
    [self.scrollView addSubview:self.leftView];
    
    self.rightView = [[UIImageView alloc] initWithFrame:CGRectMake(kLeftRightMargin+kScreenWidth, kImageViewY, kImageViewWidth, kImageViewHeight)];
    self.rightView.backgroundColor = [UIColor greenColor];
    self.rightView.layer.cornerRadius = 15;
    self.rightView.layer.masksToBounds = YES;
    [self.scrollView addSubview:self.rightView];
}
@end

运行之后,你将看到如下图的效果:


初始化工程.gif

但是你们也会发现有如下的反应,向上向下并没有弹性效果:


上下没有弹性.gif

因为此时scrollView的高度和它contentSize的高度是相同的,所以它在横向上是可以滚动的,而纵向上就不可以了。但是我们想要纵向也有弹性效果,肿么办?如果你熟悉UIScrollView,你就晓得有一个属性可以让它简单实现了。
咱们在创建scrollView的地方加入一行代码:

...
self.scrollView.alwaysBounceVertical = YES; // <--
[self.view addSubview:self.scrollView];

效果图如下:

上下弹性.gif

但此时可能你会发现另一个问题,此时的scrollView滚动的方向没有限制:

方向无限制.gif

那咱们就去UIScrollView头文件里去瞧瞧,说不定苹果爸爸已经给我们预设了解决办法了也说不定。
很快你就会看到有这样一个属性定义

@property(nonatomic,getter=isDirectionalLockEnabled) BOOL directionalLockEnabled;         // default NO. if YES, try to lock vertical or horizontal scrolling while dragging

那就用起来:

    self.scrollView.alwaysBounceVertical = YES;
    self.scrollView.directionalLockEnabled = YES; // <--
    [self.view addSubview:self.scrollView];

突然很神奇般的,好像是有效了。至少我在iOS10.3的模拟器上是没问题,但是如果你在真机上运行的话,试试你就会发现,当你滑动角度在45°左右的时候,前面设置的方向锁属性就不生效没用了。

仍然存在问题.gif

苹果官方也承认这个是个BUG了。在stackoverflow上也有在讨论这个问题。
我在上面选择了一个解决方式,实现UIScrollViewdelegate,代码如下:

@interface ViewController () <UIScrollViewDelegate>
...
@property (nonatomic, assign) CGPoint beginDragPoint;
@end
@implementation ViewController
...
#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    _beginDragPoint = scrollView.contentOffset;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView.contentOffset.x == _beginDragPoint.x) {
        self.scrollView.contentOffset = CGPointMake(_beginDragPoint.x, (self.scrollView.contentOffset.y));
    } else {
        self.scrollView.contentOffset = CGPointMake((self.scrollView.contentOffset.x), _beginDragPoint.y);
    }
}
@end

接下来说说另一个问题,手势向下移动过程中,手指移动多少视图就移动多少,而我们写的工程却还是弹性的。

系统向下移.gif

工程向下移.gif

我的思路就是,那么就让scrollViewcontentSize高度大于本身的高度吧,那么也能手移动多少,他就移动多少了。那高度设为多少合适呢?你可以先自己尝试看看,我最终的结果是两倍的本身高度,感觉非常巧妙,最后视图消失与否都通过pagingEnabled已经搞定了。
修改代码如下:

// #define kImageViewY (kScreenHeight - kBottomMargin - kImageViewHeight)
#define kImageViewY (kScreenHeight*2 - kBottomMargin - kImageViewHeight)

    self.scrollView.delegate = self;
//    self.scrollView.contentSize = CGSizeMake(kScreenWidth*2, kScreenHeight);
    self.scrollView.contentSize = CGSizeMake(kScreenWidth*2, kScreenHeight*2); // <--
    self.scrollView.contentOffset = CGPointMake(0, kScreenHeight); // <--
    self.scrollView.pagingEnabled = YES;

效果图如下:

消失出现.gif

好了,基本大功告成了。但是还有一个致命的问题,一般人找不到解决办法,我也是不断尝试才发现原来还可以这么整。
问题就是,当手指在非视图区域移动时,视图是不会动不会有反应的,一旦移动到靠近视图的时候,视图才开始移动的。如图:

非视图区.gif

先分析一下,UIScrollView的滚动时因为内部封装的pan手势,那我们可不可以拿到手势调用的方法,重写它,然后判断如果手指没有到达位置时,手势的UIGestureRecognizerStateChanged事件就不传递给父类,父类就不能处理,那么视图就不会移动了。
我们先来找找事件方法:

WechatIMG10.jpeg

打断点发现,手势属性里有个SEL方法handlePan:,嗯,应该就是它了,创建一个UIScrollView的子类重写这个方法吧。

// 创建类
@interface LYScrollView : UIScrollView
@end
@implementation LYScrollView
- (void)handlePan:(UIPanGestureRecognizer *)pan {
    NSLog(@"%s", __func__);
}
@end
.
.
#import "LYScrollView.h"
// 替换创建方法
// @property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) LYScrollView *scrollView;
...
// self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.scrollView = [[LYScrollView alloc] initWithFrame:self.view.bounds];

很是令人欣慰啊,方法回调了。


屏幕快照 2017-03-06 上午11.03.54.png

可是问题又出现了,当满足条件的时候,我要把事件还给父类的,可是编译器通过不了:

屏幕快照 2017-03-06 上午11.07.20.png

(个人想的比较挫的解决方法啊,有好的方式麻烦告知下哈。)
再创建一个中间类,声明一个handlePan:方法。。。

@interface MyScrollView : UIScrollView
- (void)handlePan:(UIPanGestureRecognizer *)pan;
@end
@interface LYScrollView : MyScrollView
@end
// 去除`.m`文件因为没有实现该方法而有的警告问题
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation MyScrollView
#pragma clang diagnostic pop
@end

最终方法实现如下:

@implementation LYScrollView {
    BOOL _canMove;
}

- (void)handlePan:(UIPanGestureRecognizer *)pan {
    CGPoint point = [pan locationInView:pan.view];
    
    if (pan.state == UIGestureRecognizerStateBegan) {
        [pan setTranslation:CGPointZero inView:pan.view];
        [super handlePan:pan];
    } else if (pan.state == UIGestureRecognizerStateChanged) {
        
        // 一旦手指位置到达视图时,则开始移动
        if (!_canMove && point.y > kImageViewY) {
            _canMove = YES;
            [pan setTranslation:CGPointZero inView:pan.view];
        }
        
        if (_canMove) {
            [super handlePan:pan];
        }
        
    } else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) {
        _canMove = NO;
        [pan setTranslation:CGPointZero inView:pan.view];
        [super handlePan:pan];
    }
}
@end

这边需要[pan setTranslation:CGPointZero inView:pan.view];调用一下,将手势的位置初始化为零后,再传给父类,不然就位置突变了。

最终效果.gif

大问题都解决完啦,剩下的背景色渐变、出现消失、再封装等实现就不在这里赘述了,可去看下工程代码。

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,617评论 4 59
  • 持续一周多,终于把自己的个人成长经历写完了。这是第四次写了吧。记得第一次写,是做咨询,写到动情处,哭的稀里哗啦。第...
    绽蕊向阳阅读 1,729评论 4 1
  • 要明白什么是跨域,先要了解什么是同源策略,它的含义是指: A网页设置的 Cookie,B网页不能打开,除非这两个网...
    书中有凉气阅读 765评论 0 50
  • 一个人的财富多少,个人的分析能力是重要决定因素之一的。一个人拥有分析能力,不管起点多低或高,都有机会获得财富。 之...
    Amanda_w阅读 281评论 0 1
  • 1.你的未来五年的职业规划是什么 我的短期职业规划是找到一份工作,并努力胜任这份工作。长期的职业规划是,希望通过在...
    mengyudezheng阅读 304评论 1 3