事件响应及传递,滚动到顶上部分
分类:www.澳门新萄京赌场

  • 明日有点不清 app 都有其一必要, 点击 statusBar, tableView/collectionview 内容滚动到最上部
  • iOS其实已经济同盟龙了这种功用, 不过它不得不在时下调节器之下唯有八个(不得不有三个) scrollView 可能其子类的时候才干有用, 假设你 tableView又有二个 scrollView 的标题栏, 那它自带的这么些职能你是用持续的
  • 本文详细索求了哪些扩充该功效, 使其能共通用
  • 代码详见自身 GitHub/quickCode/JJStatusBarExtension

@(iOS 项目实战)[项目实战]

https://github.com/RamWire/NinaPagerView澳门新萄京, *vc框架

后天以来讲iOS中的事件传递及响应。

 

  • 作者: Liwx
  • 邮箱: 1032282633@qq.com

分界面显得

先来拜候这些练习项目标app页面效果,如下:

澳门新萄京 1

QQ.png

怎么着是事件

iOS中的事件就是你做出的少数动作,举个例子触摸显示器,摇动手提式有线话机,大概在听歌的时候用线控切换了音乐,这几个工作传递到手提式无线电话机中让手提式有线电话机做出响应的正是事件。

iOS中的事件一共有二种:

  • 1.触摸事件。(比方轻触、滑动、拖动等)

  • 2.加快计事件。(比如摇一摇,使用陀螺仪等)

  • 3.远程调整事件。(比如用耳麦线控调治音量等)

那边我们就注重看一下平时最常用的触摸事件。

安装statusBar的【前景部分】

大致来讲,便是安装突显电瓶电量、时间、网络部分标示的水彩, 
此处不得不设置两种颜色:

  • 默许的深褐(UIStatusBarStyleDefault
  • 白色(UIStatusBarStyleLightContent

能够设置的地点有多少个:plist设置里面 和 程序代码里

  • 苹果是怎么得以实现该意义的, 咱们未能得知, 所以我们得自个儿达成

  • 大致效能完结步骤: 点击 statusBar--> 触发手势-->-->找到大家必要操作的内容 view-->滚动它

    • 率先大家很轻易想到, 在 statusBar 上盖上三个 view, 然后就监听点击正是了, 但小编要告诉您那是不可取的
      • 您的 view 不容许盖到 statusBar 上, 因为它也是在三个独自的 window 上的, 这么些 window 在我们 app.keyWindow之上, 所以你的 view 永久都会在 statusBar 上边(有乐趣的可以去打印看看)
      • 可能你会说把 view 加到了 statusBar 下也没涉及, 把 statusBar 的点击忽略掉就行, 笔者不得不说天真, 且不说 statusBar 档期的顺序结构复杂, 各样控件, 何况还拿不到那个控件, 固然能获得, 你要在 hitTest 里面一步步递归判别吗, 那样不现实,所以否定这种主见
      • 加 view 不行, 这就只能加 window 了, 因为 Window是能加到 statusBar 上的, 只要更动 window 的预先级--windowLevel属性就可以(补充有个别, 优先级同样的 window, 后加的在上头)
    • 光加个 window 还非常不足, 大家需求一个顶层控制器来归并保管 statusBar 的, 那样才干在调整器里成功对 view 的点击操作进行监听, 光搞个 view 是没用的
      • window 多大合适, 直接告知您, 和显示器. bounds 相等就能够, 为啥, 若是跟 statusBar.bounds 相等的话, 旋转显示屏的时候会有 bug, statusBar 会消失不见
      • 调节器 vi的尺寸就足以设置成 statusBar.bounds 大小了, 在 window 的 hitTest 方法里忽略掉 statusBar 以下的点击事件, 传递给 app.window管理
  • 第一代码:

    • 那边小编把 topWindow 设计成单例, 方便前面得到顶层的 topVc

功能效应

能够经过点击标题栏,或左右滑动内容区域,实现七个页面之间的切换。这种使用极度常见。

响应者对象(UIResponder

学学触摸事件第一件事情正是先领悟一下响应者对象UIResponder是个如何事物。

在iOS中并不是全数的东西都能响应事件,唯有承接了UIResponder的指标才有本领响应事件。

在iOS中有二种持续了UIResponder的类,他们都基本上能用并处监护人件。

  • UIApplication

  • UIViewController

  • UIView

有心人看一下UIResponder中的方法,比较轻便就会找到以下的法子:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);

- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);

- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);

- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);

这里边大家就只看触摸事件相关的主意:

//一根或多根手指触摸view

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

//一根或多根手指在view上移动

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

//一根或多根手指离开view

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

//在手指离开view之前被系统事件打断,比如来电话了

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

比如想要在UIView中管理触摸事件,只好增添二个类继承UIView,同等对待写上述方法。

如果想要在UIVIewController中管理触摸事件,在UIViewController中重写上述措施就能够。

比方那样:

- (instancetype)initWithFrame:(CGRect)frame

{

 self = [super initWithFrame:frame];

 if(self)

 {

 self.showEventLabel = [[UILabel alloc] init];

 self.showEventLabel.frame = CGRectMake(0, 10, 300, 50);

 self.showEventLabel.textAlignment = NSTextAlignmentCenter;

 [self addSubview:self.showEventLabel];

 }

 return self;

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

 self.showEventLabel.text = @"开始触摸";

}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

 self.showEventLabel.text = @"触摸结束";

}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

 self.showEventLabel.text = @"正在移动...";

}

澳门新萄京 2

UIResponderTouchView.gif

1.plist设置statusBar

在plist里扩大一行 UIStatusBarStyle(或者是“Status bar style”也能够),这里能够设置多少个值,正是上边提到那三个 
UIStatusBarStyleDefault 和 UIStatusBarStyleLightContent

那般在app运行的launch页展现的时候,statusBar的体裁正是下面plist设置的品格。

目录

技巧中央总括

以下总括一些内部的能力中央。

  • 分界面搭建
    不管做哪些类型,首先要搭建好分界面。
    界面结构解析:
    导航调节器--标题栏(能够滚动,srcollview)--内容区域(可以滚动,来回切换,呈现的视图正是导航调控器的栈顶调控器的view)
    · 导航调控器
    第一表现出来的是导航调节器的view,所以要首先合併导航调整器(在storybord中,Editor--Embed In --navigation controler)
    · 增多标题scrollview

UIScrollView *titlesrcoll = [[UIScrollView alloc] init];
titlesrcoll.backgroundColor = [UIColor grayColor];
CGFloat y = self.navigationController.navigationBarHidden? 20:64;
titlesrcoll.frame = CGRectMake(0, y, self.view.bounds.size.width, 44);
[self.view addSubview:titlesrcoll];
_titleScrollView = titlesrcoll;

· 增加内容scrollview

UIScrollView *contentsrcoll = [[UIScrollView alloc] init];
contentsrcoll.backgroundColor = [UIColor greenColor];
CGFloat y = CGRectGetMaxY(self.titleScrollView.frame); //由_titleScrollView的最底层边缘决定 y
contentsrcoll.frame = CGRectMake(0, y, self.view.bounds.size.width, self.view.bounds.size.height - y);
[self.view addSubview:contentsrcoll];
_contentScrollView = contentsrcoll; //全局

  • 标题设置
    总体布局搭建完,上边就多个叁个抬高。有多少个标题,就对应多少个view,而view又由子调节器决定,故先增多子调整器。举例:

SocietyViewController *vc4 = [[SocietyViewController alloc] init];
vc4.title = @"社会";
[self addChildViewController:vc4];

PS:子调整器的view的连锁属性设置,接纳懒加载,在其viewDidLoad方法中设。

  • 处理标题按键点击
    监听开关点击,在哪监听?增加完按键后就足以监听

[btn addTarget:self action:@selector(titleClick:) forControlEvents:UIControlEventTouchUpInside];

下一场提供监听方法,解析当点击后要做什么样动作?剖判点击标题要完结的效果与利益功能:标题颜色更改--切换分界面(先增多view,再滚动过来);其他部要求要精通点击了哪一个开关,故需求参数

-(void)titleClick:(UIButton *)button{
NSInteger i = button.tag;
//标题颜色退换
[self SelectedBtn:button];
//把相应的子控件view增添
[self setupOneViewControler:i]; //哪贰个子调整器,由所点的btn决定--角标tag
//滚动内容到可视范围(退换偏移量x
CGFloat x = i * [UIScreen mainScreen].bounds.size.width;
self.contentScrollView.contentOffset = CGPointMake(x, 0);
}

-(void)setupOneViewControler:(NSInteger)i{
//把相应的子控件view加多
UIViewController *vc = self.childViewControllers[i];
if(vc.view.superview)
return; //加载了就绝不加了
CGFloat x = i * [UIScreen mainScreen].bounds.size.width;
vc.view.frame = CGRectMake(x, 0, [UIScreen mainScreen].bounds.size.width, self.contentScrollView.bounds.size.height);
[self.contentScrollView addSubview:vc.view];
}

  • 存在bug解决
    ios7之后,导航控制器中的scrollview顶端会冒出额外64的滚动区域,解决措施是:

self.automaticallyAdjustsScrollViewInsets = NO;

  • 内容scrollview滚动监听
    三部曲:设置监听指标---遵守左券---接纳监听方法(什么日期做政工)
    self.contentScrollView.delegate = self;
    <UIScrollViewDelegate>
    几时做?滚动实现时,甘休滚动再加载view,节省财富。
    做什么样业务?——标题选中、分界面切换。

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
//获取当前角标
NSInteger i = scrollView.contentOffset.x / [UIScreen mainScreen].bounds.size.width;
//获取标题开关
// UIButton *titleBtn = self.titleScrollView.subviews[i];//不可能经过这一个艺术拿,因为通过index不可靠
UIButton *titleBtn = self.titleBtns[i]; //用三个新数组保存标题按键,可信
//1,选中题目--哪八个题名? ->通过角标 -> 角标怎么获得
[self SelectedBtn:titleBtn];
//2.把相应的子调控器的view滚动过去
[self setupOneViewControler:i];
}

  • 标题居中拍卖
    何以时候做?标题选中的时候。题目居中,其实要做的正是要调度scrollview的偏移量。偏移多少?---点了哪二个题名,就相应偏移多少。---哪个标题?---由所点击的按键决定--->方法供给参数

-(void)setupTitleCenter:(UIButton *)button{
CGFloat offsetX = button.center.x - [UIScreen mainScreen].bounds.size.width * 0.5;
if(offsetX < 0 )
offsetX =0;
CGFloat maxoffsetX = self.titleScrollView.contentSize.width - [UIScreen mainScreen].bounds.size.width;
if(offsetX > maxoffsetX)
offsetX = maxoffsetX;
[self.titleScrollView setContentOffset:CGPointMake(offsetX,0) animated:YES];
}

  • 抽取封装
    以上分界面作用十一分普遍,应用分布,由此能够当做移植代码。于是思量把地方的案例封装抽取出来,外面方便使用。

UITouch

前面讲到的不二法门中基本都富含有五个类,UITouchUIEvent

UITouch意味着触摸荧屏的手指,四个UITouch对象就对应着一根手指,在事变中得以依靠NSSetUITouch的数量来判定触摸显示器的指头个数,并且UITouch中也蕴藏部分指头的音讯,譬喻地方,触摸时间等等,大家来看一下他的天性:

//触摸的时间,以秒为单位

@property(nonatomic,readonly) NSTimeInterval timestamp;

//触摸事件的状态

@property(nonatomic,readonly) UITouchPhase phase;

//短时间内点按屏幕的次数,用来判断双击还是单击

@property(nonatomic,readonly) NSUInteger tapCount;  

//触摸事件的类型 

@property(nonatomic,readonly) UITouchType  type NS_AVAILABLE_IOS(9_0);

//触摸时的窗口

@property(nullable,nonatomic,readonly,strong) UIWindow *window;

//触摸时的view

@property(nullable,nonatomic,readonly,strong) UIView *view;

2.程序代码里安装statusBar

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];  

或者

//相对于上面的接口,这个接口可以动画的改变statusBar的前景色  
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];

不唯有如此,ios还很恩爱的在UIViewController也大增了多少个接口, 
目标是让意况栏依据当下呈现的UIViewController来定制statusBar的前景部分。

  • - (UIStatusBarStyle)preferredStatusBarStyle;

  • - (UIViewController *)childViewControllerForStatusBarStyle;

  • - (void)setNeedsStatusBarAppearanceUpdate

- (UIStatusBarStyle)preferredStatusBarStyle:

在你和谐的UIViewController里重写此办法,重临您要求的值(UIStatusBarStyleDefault 或然 UIStatusBarStyleLightContent);

事件响应及传递,滚动到顶上部分。注意:

  • 这里假设您只是轻易的return一个定点的值,那么该UIViewController展现的时候,程序就能够立马调用该格局,来更改statusBar的前景部分;
  • 假如在该UIViewController已经在展现在脚下,你或者还要在时下页面不常的改观statusBar的前景观,那么,你首先须求调用上边的setNeedsStatusBarAppearanceUpdate格局(那些方法会通知系统去调用当前UIViewController的preferredStatusBarStyle方法), 这个和UIView的setNeedsDisplay规律大概(调用UIView对象的setNeedsDisplay格局后,系统会在下一次页面刷新时,调用重绘该view,系统最快能1秒刷新57次页面,具体要看程序设置)。

- (UIViewController *)childViewControllerForStatusBarStyle:

那个接口也很关键,暗中同意再次回到值为nil。当咱们调用setNeedsStatusBarAppearanceUpdate时,系统会调用application.window的rootViewController的preferredStatusBarStyle艺术,我们的主次里一般都是用UINavigationController做root,假若是这种情景,这大家温馨的UIViewController里的preferredStatusBarStyle常有不会被调用; 
这种情况下childViewControllerForStatusBarStyle就派上用场了, 
大家要子类化三个UINavigationController,在那么些子类里面重写childViewControllerForStatusBarStyle方法,如下:

- (UIViewController *)childViewControllerForStatusBarStyle{
    return self.topViewController;
}

地点代码的情致正是,不要调用笔者要好(就是UINavigationController)的preferredStatusBarStyle措施,而是去调用navigationController.topViewControllerpreferredStatusBarStyle形式,那样写的话,就会保险当前显示的UIViewController的preferredStatusBarStyle主意能影响statusBar的前景部分。

除此以外,有的时候我们的最近展现的UIViewController恐怕有多个childViewController,重写当前UIViewController的childViewControllerForStatusBarStyle方法,让childViewController的preferredStatusBarStyle生效(当前UIViewController的preferredStatusBarStyle就不会被调用了)。

大概来讲,只要UIViewController重写的的childViewControllerForStatusBarStyle主意再次来到值不是nil,那么,UIViewController的preferredStatusBarStyle艺术就不会被系统调用,系统会调用childViewControllerForStatusBarStyle办法重回的UIViewController的preferredStatusBarStyle方法。

- (void)setNeedsStatusBarAppearanceUpdate:

让系统去调用application.window的rootViewController的preferredStatusBarStyle方法,如果rootViewController的childViewControllerForStatusBarStyle重回值不为nil,则参照他事他说加以考察上面的上书。


// JJStatusBarExtension.mstatic JJStatusBarExtension *_topWindow;  (instancetype)sharedStatusBarExtension{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _topWindow = [[self alloc] init]; }); return _topWindow;}  (instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _topWindow = [super allocWithZone:zone]; }); return _topWindow;}- copyWithZone:zone{ return _topWindow;}  showWithStatusBarClickBlock:block{ if (_topWindow) return; [JJStatusBarExtension sharedStatusBarExtension].windowLevel = UIWindowLevelAlert; [JJStatusBarExtension sharedStatusBarExtension].backgroundColor = [UIColor clearColor]; // 先显示window [JJStatusBarExtension sharedStatusBarExtension].hidden = NO; // 设置根控制器 JJTopViewController *topVc = [[JJTopViewController alloc] init]; topVc.view.backgroundColor = [UIColor clearColor]; topVc.view.frame = [UIApplication sharedApplication].statusBarFrame; topVc.view.autoresizingMask = UIViewAutoresizingFlexibleWidth; topVc.clickedBlock = block; [JJStatusBarExtension sharedStatusBarExtension].rootViewController = topVc;}- hitTest:point withEvent:(UIEvent *)event{ if (point.y > 20) { return nil; } return [super hitTest:point withEvent:event];}
  • 06.种类实战 百思不得姐 杰出子调节器view懒加载,监听状态栏点击,tabBarButton重复点击监听
  • 【相关知识点补充】
    • UIScrollView动画滚动情势
    • UIScrollView监听结束滚动
    • 坐标系调换
    • 认清是不是重叠
    • 导航条按键展现十分bug
    • 意况栏点击事件
    • UIWindow相关知识点
    • 监听开关事件
  • 1.杰出子调控器view懒加载
    • 子调控器view懒加载达成
  • 2.监听顶端处境栏区域的点击
    • 监听顶端状态栏的点击事件的兑现
  • 3.气象栏点击调节tableView滚动
    • 探索全体的scrollView
  • 4.监听tabBarButton的再次点击
    • 监听tabBarButton重复点击格局一(使用tabBarButton addTarget情势,本项目选取此措施)
    • 监听tabBarButton重复点击情势二(使用UITabBarController的代理方式)
    • 子调控器监听tabBarButton重复点击公告

UIEvent

历次爆发一个风云,就相应一个UIEvent,它记录了平地风波时有产生的大运和项目等等,我们来看一下它的习性:

//事件的类型

@property(nonatomic,readonly) UIEventType  type NS_AVAILABLE_IOS(3_0);

@property(nonatomic,readonly) UIEventSubtype subtype NS_AVAILABLE_IOS(3_0);

//事件产生的时间

@property(nonatomic,readonly) NSTimeInterval timestamp;

## 事件的发生和传递

当发生触摸事件后,系统会将事件参与一个由UIApplication管制的事件队列中。

下一场会去调用UIWindow中的响应措施,判断点击的点是不是在window范围内,如若是,则持续一遍调用subView中的响应措施,直到找到最终索要的view。

约等于说事件的传递是从父控件到子控件的。

举例下图:UIWindow有三个MainView,MainView里有三个subView,viewA,viewB,viewC,他们各有四个subview,个中viewA在最上面,viewB在当中,viewC在最上边,viewA与viewB有一部分交汇。

澳门新萄京 3

eventTouch1.png

这时大家点击viewA和viewB的重叠部分,就能够遵照上边说的递归情势推行。

澳门新萄京 4

eventTouch2.png

全体事件的传递是从UIWindow起始的,首先剖断了点击的职位是或不是在UIWindow中,然后遍历window的subview,然后依次对subview举办遍历,这里viewC根本不在点击的限量内,所以也就从没有过再走访他的subview。当事件遍历到了viewB.1时察觉整整都符合条件,那么她正是大家要找的view,所以viewA也就不曾被遍历了。

此地有几个要求注意的部分:

  • 1.只要父控件不能够承受触摸事件,那么子控件就不容许经受到触摸事件;

  • 2.暗中同意情状下UIImageView不允许交互,所以即使要UIImageView能够并行,供给设置UIImageView的userInteractionEnabled = YES。

除此以外正是UIView不允许触摸的三种景况:

  • 1.设定分歧意交互,即userInteractionEnabled = NO;

  • 2.点击地点不在view范围内;

  • 3.view被隐形或棉被服装置折射率<0.01;

终极上一张图,能够清楚的讲精晓这么些流程

澳门新萄京 5

eventTouch3.png

设置statusBar的【背景有些】

背景有个别,轻巧的话,正是背景观;改造方法有二种:

  • 因而 showWithStatusBarClickBlock:方法来创立出 topWindow, 在 APPDelegate 中调用, 这里笔者把寻找内容 view 并滚动到最终面包车型大巴主意独立出来, 并在 showWithStatusBarClickBlock:后的 block 回调, 若是你任啥地点方有其一须求, 你能够运用那个主意, 你借使传入你要找的内容view 的父view 或父父view...
  • 此地我们把 application.keyWindow传进去, 因为大家须求全局完结
  • 兑现滚动到 top 的原理, 递归查找子控件, 找到 scrollView 就把它滚到最前头

事件的响应

事先说完了平地风波的发出和传递,那么接下去改事件的响应了。

客商点击显示屏发出了贰个触摸事件,经过一名目许多的传递进度后,就能够找到适合的视图控件来管理这几个事件。

找到最合适的控件之后,就能调用控件的touches方法来坚实际的小时拍卖。

这个touches方法的默许做法是沿着响应链条向上传递的(约等于touch方法默许不处管事人件,只传递事件),将事件交给上二个响应者进行拍卖。

那就变成了响应者链条。

系统提供的格局

navigationBarsetBarTintColor接口,用此接口可转移statusBar的背景象

瞩目:一旦您设置了navigationBar- (void)setBackgroundImage:(UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics接口,那么地点的setBarTintColor接口就不能够改造statusBar的背景象,statusBar的背景象就能够产生纯浅铅灰。

【相关知识点补充】

响应者链条

响应者链条其实就是广大响应者对象(承接自UIResponder的靶子)一同构成起来的链子。

貌似暗中同意做法是控件将本着响应者链条向上传递,将事件交给上两个响应者管理。

这正是说怎么样判断当前响应者的上二个响应者是何人吧?

判别当前是不是为调控器的view,假使是,上几个响应者正是调控器,若是还是不是,上一个响应者就是父控件。

澳门新萄京 6

eventTouch4.png

传递的经过为:

  • 1.判定当前是还是不是为调控器的view,是,事件就传递给调整器,不是,事件就传递给父控件。

  • 2.在视图等级次序结构的最顶层,假使也不可能管理收到的平地风波,则将事件传递给window对象管理。

  • 3.一旦window对象也不管理,则将事件传递给UIApplication对象。

  • 4.若是UIApplication对象也不处理,则将事件舍弃。

另辟蹊径

创制一个UIView, 
安装该UIView的frame.size 和statusBar大小同样, 
设置该UIView的frame.origin 为{0,-20}, 
设置该UIView的背景观为您愿意的statusBar的水彩, 
在navigationBar上addSubView该UIView即可。

 

 

状态栏的书体为淡白紫: UIStatusBarStyleDefault  

状态栏的书体为灰白: UIStatusBarStyleLightContent 

一、在 info.plist  中,将 View controller-based status bar appearance  设为 NO

气象栏字体的颜料只由下边包车型客车性质设定,默感到白色:

// default is UIStatusBarStyleDefault

[UIApplication sharedApplication].statusBarStyle

消除个别  vc 中状态栏字体颜色各异的点子 

1、在info.plist中,将View controller-based status bar appearance设为NO.

2、在app delegate中:

[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;

3、在个别意况栏字体颜色不一致样的vc中

-(void)viewWillAppear:(BOOL)animated{

[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;

}

-(void)viewWillDisappear:(BOOL)animated

{

[super viewWillDisappear:animated];

[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;

}

二、在 info.plist  中,将 View controller-based status bar appearance  设为 YES ,可能没有安装。

View controller-based status bar appearance的私下认可值正是YES。

如果View controller-based status bar appearance为YES。

则[UIApplication sharedApplication].statusBarStyle 无效。

用上边包车型大巴法子:

1、在vc中重写vc的preferredStatusBarStyle方法。

-(UIStatusBarStyle)preferredStatusBarStyle

{

return UIStatusBarStyleDefault;

}

2、在viewDidload中调用:[self setNeedsStatusBarAppearanceUpdate];

唯独,当vc在nav中时,上边方法没用 ,vc中的preferredStatusBarStyle方法根本并不是被调用。 

原因是,[self setNeedsStatusBarAppearanceUpdate]发出后,

只会调用navigation controller中的preferredStatusBarStyle方法,

vc中的preferredStatusBarStyley方法跟本不会被调用。

解决办法有七个:

方法一:

设置navbar的barStyle 属性会影响status bar 的书体和背景观。如下。

//status bar的书体为紫褐

//导航栏的背景观是玫瑰清水蓝。

self.navigationController.navigationBar.barStyle = UIBarStyleBlack;

//status bar的书体为栗褐

//导航栏的背景象是棕黑,状态栏的背景象也是白灰。

//self.navigationController.navigationBar.barStyle = UIBarStyleDefault;

方法二:

自定义二个nav bar的子类,在那几个子类中重写preferredStatusBarStyle方法:

MyNav* nav = [[MyNav alloc] initWithRootViewController:vc];

self.window.rootViewController = nav;

@implementation MyNav

- (UIStatusBarStyle)preferredStatusBarStyle

{

UIViewController* topVC = self.topViewController;

return [topVC preferredStatusBarStyle];

}

// AppDelegate.m- application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {// NSLog(@"%@", self.window); [JJStatusBarExtension showWithStatusBarClickBlock:^{// [self test]; // 如果想要 app的所有界面都有点击 statusBar 滚到最前面, 则调用下面这个方法 [JJStatusBarExtension scrollToTopInsideView:self.window]; }]; return YES;}

  scrollToTopInsideView:view{ CGRect viewRect = [view convertRect:view.bounds toView:nil]; if (!CGRectIntersectsRect([UIApplication sharedApplication].keyWindow.frame, viewRect)) { return; } for (UIView *subview in view.subviews) { [self scrollToTopInsideView:subview]; } if (![view isKindOfClass:[UIScrollView class]]) { return; } UIScrollView *scrollView = (UIScrollView *)view; // CGPoint contentOffset = scrollView.contentOffset; // contentOffset.y = - scrollView.contentInset.top; // [scrollView setContentOffset:contentOffset animated:YES]; [scrollView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];}

UIScrollView动画滚动形式

  • 1.使用setContentOffset:animated:方法实现动画滚动.
  • 2.scrollRectToVisiable:animated:滚动一块特定的区域到scrollView显示.假使该区域早已在scrollView中可知,调用此办法没影响.
    如上多个方法animated为YES技能促成动画滚动.

hitTest:withEvent:

那是iOS事件的传递和响应中最重大的法子之一,在日前也可以有关系,今后来具体的牵线一下以此措施。

只要事件一传递给叁个控件,这几个控件就能调用自身的hitTest:withEvent:方法。

他的魔法正是寻觅并赶回最适合的view,无论这么些控件能否处总管件,也随意触摸点在不在那些控件上,事件都会先传递给那一个控件,随后就调用该办法。

事件传递给窗口或控件的后,就调用hitTest:withEvent:方法搜索更适于的view。所以是,先传递事件,再依据事件在融洽身上找更适合的view。

不管敬仲控件是还是不是最合适的view,系统暗许都要先把事件传递给子控件,经过子控件调用子控件本身的hitTest:withEvent:方法求证后才领会有没有更确切的view。就算父控件是最合适的view了,子控件的hitTest:withEvent:方法依旧会调用,不然怎么精通有未有更方便的!即,借使显明最终父控件是最合适的view,那么该父控件的子控件的hitTest:with伊芙nt:方法也是会被调用的。

假使hitTest:with伊芙nt:方法中回到nil,那么调用该措施的控件本身和其子控件都不是最合适的view,也正是在自身随身未有找到更妥善的view。那么最合适的view便是该控件的父控件。

  • 首先知道几个尺码:
    • 原先能够用 application 来修改 statusBarHidden 和 style, 但 iOS9后, application 的不二秘籍过期了
    • 在调控器中, 调控器能够决定 statusBar 的 hidden 和 style, 首先知道有个别, 系统是怎么来调整statusBar的, 有点方可印证到, 正是每一趟 view 将在展现的时候, 系统都会调用上边包车型大巴措施, 以此来调整statusBar 状态
    • 经过setNeedsStatusBarAppearanceUpdate能重复调用下边的八个措施
    • statusBar 只好由顶层调节器来决定, 所以大家加了 topVc 后, 其他调控器里对 statusBar 状态的更改是于事无补的,因为顶层调整器会覆盖它
  • 听说地方的解析, 大家也在 view 就要呈现的时候来操作, 在 viewWillAppear 方法来想艺术

UIScrollView监听甘休滚动

  • 监听UIScrollView甘休滚动的多种办法

    • 方式一: 当用户停止拖拽scrollView的时候调用(手松开)
    • 方式二: 当scrollView停止滚动的时候调用
    • 方式三: 当scrollView停止滚动的时候调用.前提:当使用setContentOffset:animated:或者scrollRectToVisible:animated:方法让scrollView产生了滚动动画
    • 方式四: 使用UIView animateWithDuration:animations:completion:方法,animations block内部修改了scrollView的contentOffset的值,在completion 的block监听scrollView滚动实现.

    • 监听UIScrollView截至滚动的两种完结参照他事他说加以考察代码
      #pragma mark - <UIScrollViewDelegate>
    
      // ----------------------------------------------------------------------------
      // 方式一
      /**
       *  当用户停止拖拽scrollView的时候调用(手松开)
       *  如果参数decelerate为YES,手松开后会继续滚动,滚动完毕后会调用scrollViewDidEndDecelerating:代理方法
       *  如果参数decelerate为NO,手松开后不再滚动,马上静止,也不会调用scrollViewDidEndDecelerating:代理方法
       */
      - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
      {
          if (decelerate) {
              NSLog(@"用户停止拖拽scrollView,scrollView会继续滚动");
          } else {
              NSLog(@"用户停止拖拽scrollView,scrollView不再滚动");
          }
      }
    
      // ----------------------------------------------------------------------------
      // 方式二
      /**
       *  当scrollView停止滚动的时候调用
       *  前提:人为手动让scrollView产生滚动
       */
      - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
      {
          NSLog(@"用户停止拖拽scrollView后滚动完毕");
      }
    
      // ----------------------------------------------------------------------------
      // 方式三
      /**
       *  当scrollView停止滚动的时候调用
       *  前提:当使用setContentOffset:animated:或者scrollRectToVisible:animated:方法让scrollView产生了滚动动画
       */
      - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
      {
          NSLog(@"通过setContentOffset:animated:或者scrollRectToVisible:animated:方法让scrollView产生滚动动画,然后停止滚动");
      }
    
      // ----------------------------------------------------------------------------
      // 方式四
      [UIView animateWithDuration:1.0 animations:^{
          self.scrollView.contentOffset = CGPointMake(150, 150);
          [self.scrollView setContentOffset:CGPointMake(150, 150)];
      } completion:^(BOOL finished) {
          NSLog(@"减速完毕----");
      }];
    

pointInside:withEvent:

本条是整整事件的传递中最重要的另三个主意,用来判确定地点击的点在不在当前view上。借使回到YES,代表点在议程调用者的坐标系上;重回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能够处总管件。

坐标系转换

  • 总计控件A在window中的x,y,width,height.
    • convertRect:toView:方法(多个艺术是可逆的)
    • convertRect:fromView:方法
    • 注意: toView和fromView如果为nil,表示window.
    • 若果措施的调用者是A本身,则传入的convertRect为A.bounds.
      CGRect rect = [A convertRect:A.bounds toView:nil];
    • 假若形式的调用者是A的父控件,则传出的convertRect为A.frame.
    // 描述控件A在window中的xywidthheight
    CGRect rect = [A.superview convertRect:A.frame toView:window];
    CGRect rect = [A.superview convertRect:A.frame toView:nil];
    CGRect rect = [A convertRect:A.bounds toView:window];
    CGRect rect = [A convertRect:A.bounds toView:nil];
    CGRect rect = [window convertRect:A.frame fromView:A.superview];
    CGRect rect = [window convertRect:A.bounds fromView:A];
  • 坐标系调换图解
![](https://upload-images.jianshu.io/upload_images/1253159-5f373e8c72e474b2.png)

坐标系转换图解.png

事件传递及响应的施用

好了,讲了那般多,那在付出的经过中有哪些时候大家会用到事件的响应和传递呢?

实际更确切的身为利用hitTest:withEvent:方法。

- prefersStatusBarHidden{ return YES;}- (UIStatusBarStyle)preferredStatusBarStyle{ return UIStatusBarStyleDefault;}- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation{ return UIStatusBarAnimationFade;}

判断是还是不是重叠

  • 使用bool CGRectIntersectsRect(CGRect rect1, CGRect rect2)函数判定rect1与rect2

    • 注意: 该函数的参数rect1和rect2必须处于同一坐标系. 设若在分歧坐标系,必得先进行坐标系转变,再用此办法推断三个控件是还是不是重叠.

    • 看清是还是不是重叠参谋代码
      - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
      {
          CGRect rect1 = [self.blueView convertRect:self.blueView.bounds toView:nil];
          CGRect rect2 = [self.redView convertRect:self.redView.bounds toView:nil];
          BOOL result = CGRectIntersectsRect(rect1, rect2);
          NSLog(@"%zd", result);
      }
    
  • 封装UIView分类,实现剖断是或不是重叠的点子

- (BOOL)wx_intersectWithView:(UIView *)view
{
    // 如果传入的参数是nil,则表示为[UIApplication sharedApplication].keyWindow
    if (view == nil) view = [UIApplication sharedApplication].keyWindow;

    // 都统一转换成window坐标系,并判断是否重叠,返回判断结果
    CGRect rect1 = [self convertRect:self.bounds toView:nil];
    CGRect rect2 = [view convertRect:view.bounds toView:nil];
    return CGRectIntersectsRect(rect1, rect2);
}

强大开关点击范围

比方日常我们的按键size相当小,不过为了便利客户点击,我们恐怕供给扩充学一年级些点击的可行区域。这时候能够透过重写hitTest:方法来扩大有效的点击范围。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

{

 //判断响应条件

 if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01)

 {

 return nil;

 }

 //扩大10个点的点击范围

 CGRect touchRect = CGRectInset(self.bounds, -10, -10);

 if(CGRectContainsPoint(touchRect, point))

 {

 for(UIView *subView in [self.subviews reverseObjectEnumerator])

 {

 CGPoint convertedPoint = [subView convertPoint:point fromView:self];

 UIView *hitTestView = [subView hitTest:convertedPoint withEvent:event];

 if(hitTestView)

 {

 return hitTestView;

 }

 }

 return self;

 }

 return nil;

}
  • 为了保障系统原有的对 statusBar 的垄断(monopoly)依旧有效, 我们就必需在 topVc 里能获得最近来得 Vc对 statusBar 的设置数据
  • 此处我们新建一个 viewController 的归类, 在这里面完毕相关操作, 这里要弄到运维时的置换方法, 因为我们要堵住 viewController 的 viewWillAppear 方法
  • 阻碍到后, 将这两天来得调控器, 通过 topWindow 传给 topVc, 那样就会在 topVc 里面得到当前显示调整器里对 statusBar 的舍子数据, 然后 topVc 在调用setNeedsStatusBarAppearanceUpdate方法, 刷新状态栏的展现
  • 此处还对假若设置了statusBar 展现动画的气象作了判别

导航条按键呈现相当bug

  • 丰裕bug现象: 导航条的开关会并发岗位有误.只要push到其他调控器再回去时,导航条开关就显得平常..
    • 导航栏按键突显出现难题,push/pop回来就好了的bug.原因是重写viewWillAppear:时,没调用super 的viewWillAppear:方法或者调用错方法导致.
// 如果super的方法名写错,会出现界面显示的一些小问题
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}

将事件传递给兄弟view

例如说前边的图样中,viewA和viewB重叠在了二只,并且viewB有一段是覆盖在viewA上的,那时候我们点击重合的一些,响应的必然是viewB,若是大家想要在点击时让viewA响应,那时候只要在viewB中重写hitTest:方法,在被点击时重回nil就可以。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

{

 UIView *hitTsetView = [super hitTest:point withEvent:event];

 if(hitTsetView == self)

 {

 hitTsetView = nil;

 }

 return hitTsetView;

}

动静栏点击事件

  • 系统暗许点击状态栏的时候,scrollView滚动到最上部

    • 前提是UIScrollView的scrollsToTop属性为YES时,并且屏幕上只有一个scrollView时本事用.
    • UIScrollView的scrollsToTop属性默认为YES.
  • 要想window里面包车型地铁剧情跟随荧屏旋转,那么必需安装window的rootViewController

  • 状态栏的体制和出示隐蔽由最顶层window的控制器决定

    • - (BOOL)prefersStatusBarHidden : 呈现和潜伏
    • - (UIStatusBarStyle)preferredStatusBarStyle :青黑和青绿
  • 旋转状态栏消失的原因是最上面window控制器没实现状态栏preferStatusBarHidden方法.


将事件传递给 subview

譬喻这种情况,青黑的框为UIScrollView,设置了pagingEnabled使得image截止滚动后会停留在荧屏的中等地方,假设在scrollView的左臂或左手活动,开掘scrollView是不只怕滚动的。

由来就是因为hitTest里未有满意pointInSide的规格,因为scrollView的bound只有紫蓝区域。那年重写hitTest:方法,重回scrollView就可以减轻难点了。

澳门新萄京 7

eventTouch5.png

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

{

 UIView *hitTsetView = [super hitTest:point withEvent:event];

 if(hitTsetView)

 {

 hitTsetView = self.scrollView;

 }

 return hitTsetView;

}

hitTest:的使用措施还会有好些个浩大,驾驭了全体育工作艺流程之后就能够协助我们很好的减轻开辟时遇见的有些困难。

// UIViewController (JJStatusBarExtension) --分类@implementation UIViewController (JJStatusBarExtension)  load{ // 交换系统 viewWillAppear 和我们自定义的 jj_viewWillAppear Method method1 = class_getInstanceMethod(self, @selector(jj_viewWillAppear:)); Method method2 = class_getInstanceMethod(self, @selector(viewWillAppear:)); method_exchangeImplementations(method1, method2);}- jj_viewWillAppear:animated{ [self jj_viewWillAppear:animated]; // 如果非当前窗口显示的控制器, 我只测试到UINavigationController下,"UIInputWindowController"会产生影响, 把它屏蔽掉// NSLog(@"%@", self.class); if ([NSStringFromClass(self.class) isEqualToString:@"UIInputWindowController"]) return; if ([self respondsToSelector:@selector(jj_ignoreStatusBar)]) { if ([self jj_ignoreStatusBar]) return; } JJTopViewController *statusBarVc = (JJTopViewController *)[JJStatusBarExtension sharedStatusBarExtension].rootViewController; if (statusBarVc == self) return; statusBarVc.showingVc = self; // 判断是否设置动画 if (statusBarVc.showingVc.preferredStatusBarUpdateAnimation == UIStatusBarAnimationNone) { [statusBarVc setNeedsStatusBarAppearanceUpdate]; }else{ [UIView animateWithDuration:[JJStatusBarExtension sharedStatusBarExtension].statusBarAnimationDuration animations:^{ [statusBarVc setNeedsStatusBarAppearanceUpdate]; }]; }}@end

UIWindow相关知识点

  • window显示固然设置hidden = NO就可以显示了,不需要像UIView要添加到父控件view上.

  • window等级越高,越显得在顶层.

    • UIWindowLevelAlert -> UIWindowLevelStatusBar ->UIWindowLevelNormal
    • 一旦等级一样,越前边显示在越顶层.
  • 干什么必定要- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions措施中给window加多根调控器?
    • 设若有设置window的根调控器,window里面的内容才会跟随旋转.window本人是不会旋转的.

参照链接

iOS 点击事件传递及响应

史上最详尽的iOS之事件的传递和响应机制-原理篇

深入显出iOS事件机制

  • topVc获得数码刷新,
    • topVc 的 touchsBegin中监听点击, 回调 scrollToTop 的方式

监听按键事件

  • 监听长时间按键数14遍点击(双击)事件
    • UIControlEventTouchDownRepeat

1.卓绝子调整器view懒加载

@interface JJTopViewController : UIViewController@property(nonatomic, strong) void(^clickedBlock)();@property(nonatomic, strong) UIViewController * showingVc;@end@implementation JJTopViewController- prefersStatusBarHidden{ return self.showingVc.prefersStatusBarHidden;}- (UIStatusBarStyle)preferredStatusBarStyle{ return self.showingVc.preferredStatusBarStyle;}- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation{ return self.showingVc.preferredStatusBarUpdateAnimation;}- touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ if (self.clickedBlock) { self.clickedBlock(); }}@end

子调整器view懒加载达成

  • 1.实现增多index地方对应的子调控器view到scrollView参照他事他说加以考察代码()

    • 1.基于目录获取子调节器(方法一)

    • 2.推断子调控器的view是还是不是早就加载过(二种形式),就算已经加载过,退出

      • 方法一: childVc.isViewLoaded 判断是否已经加载过
      • 方法二: childVc.view.superview 判断是否有父控件
      • 方法三: childVc.view.window 判断window是否有值
      • 方法四: self.scrollView.subviews containsObject:childVc.view 判别调控器的view是不是在scrollView的子控件数组中.
    • 3.安装要抬高的子调控器view的frame,并增添到scrollView

      • 3.1 设置x值
      • 3.2 需将y设置为0,因为childVc.view是UITableView,UITableView默认的y值是20
      • 3.3 暗中认可UITableView的莫大是荧屏的惊人减去它本人的y值(20),所以重复设置中度为全方位scrollView的冲天
      • 3.4 增加子调整器的view到scrollView
      // ----------------------------------------------------------------------------
      // 添加index位置对应的子控制器view到scrollView
      - (void)addChildVcViewIntoScrollView:(NSInteger)index
      {
          // 1.根据索引获取子控制器
          UIViewController *childVc = self.childViewControllers[index];
    
          // TODO: 2.判断子控制器的view是否已经加载过,如果已经加载过,退出
          // 方法一: childVc.isViewLoaded 方法二: childVc.view.superview 方法三: childVc.view.window
          if (childVc.isViewLoaded) {
              return;
          }
    
          // 3.设置要添加的子控制器view的frame,并添加到scrollView
          // 3.1 设置x值
          childVc.view.wx_x = index * self.scrollView.wx_width;
          // 3.2 需将y设置为0,因为childVc.view是UITableView,UITableView默认的y值是20
          childVc.view.wx_y = 0;
          // 3.3 默认UITableView的高度是屏幕的高度减去它本身的y值(20),所以重新设置高度为整个scrollView的高度
          childVc.view.wx_height = self.scrollView.wx_height;
          // 3.4 添加子控制器的view到scrollView
          [self.scrollView addSubview:childVc.view];
      }
    

    • 运用偏移量总括索引值(方法二)
      接纳偏移量总括索引,完毕增加子调节器view到scrollView,该方法只能通过偏移量来控制要显示那个view,灵活性非常不够(不推荐,因为其依赖偏移量)
      • 通过以下深入分析scrollView的x,y偏移量等于bounds的x,y值.可推测出childVc.view.frame刚好为scrollView.bounds;
      // ----------------------------------------------------------------------------
      // 使用偏移量计算索引,实现添加子控制器view到scrollView,该方法只能通过偏移量来控制要显示那个view,灵活性不够 (不推荐,因为其依赖偏移量)
      - (void)addChildVcViewIntoScrollView
      {
          NSInteger index = self.scrollView.contentOffset.x / self.scrollView.xmg_width;
          UIViewController *childVc = self.childViewControllers[index];
          childVc.view.frame = self.scrollView.bounds;
          [self.scrollView addSubview:childVc.view];
    
      //    childVc.view.xmg_x = self.scrollView.bounds.origin.x;
      //    childVc.view.xmg_y = self.scrollView.bounds.origin.y;
      //    childVc.view.xmg_width = self.scrollView.bounds.size.width;
      //    childVc.view.xmg_height = self.scrollView.bounds.size.height;
    
      //    childVc.view.xmg_x = self.scrollView.contentOffset.x;
      //    childVc.view.xmg_y = self.scrollView.contentOffset.y;
      //    childVc.view.xmg_width = self.scrollView.xmg_width;
      //    childVc.view.xmg_height = self.scrollView.xmg_height;
    
      //    childVc.view.xmg_x = index * self.scrollView.xmg_width;
      //    childVc.view.xmg_y = 0;
      //    childVc.view.xmg_width = self.scrollView.xmg_width;
      //    childVc.view.xmg_height = self.scrollView.xmg_height;
      }
    
  • 2.在开始化增加子调控器的方法中调用增多子调节器的view的点子设置默认显示第0个子控制器的view.

// ----------------------------------------------------------------------------
// 添加子控制器
- (void)setupAllChildViewController
{
    // 1.添加5个子控制器
    [self addChildViewController:[[WXAllViewController alloc] init]];
    [self addChildViewController:[[WXVideoViewController alloc] init]];
    [self addChildViewController:[[WXVoiceViewController alloc] init]];
    [self addChildViewController:[[WXPictureViewController alloc] init]];
    [self addChildViewController:[[WXWordViewController alloc] init]];

    // 2.获取子控制器数量
    NSInteger count = self.childViewControllers.count;
    // 设置默认显示第0个子控制器的view
    [self addChildVcViewIntoScrollView:0];
    // 3.设置scrollView的滚动范围
    self.scrollView.contentSize = CGSizeMake(count * self.scrollView.wx_width, 0);
}
  • 3.使用按键的tag值作为目录,在下划线动画实践到位修改scrollView的偏移量,呈现对应子调节器的view.
    • 要是对应子调控器的view为增多到scrollView,则增多view到scrollView,并更新偏移量突显对应view.
#pragma =======================================================================
#pragma mark - titleButton按钮点击
// ----------------------------------------------------------------------------
// 监听按钮点击
- (void)titleButtonClick:(WXTitleButton *)button
{
    // 切换中状态
    self.selectedButton.selected = NO;
    button.selected = YES;
    self.selectedButton = button;

    // 1.获取索引,按钮的tag值
    NSInteger index = button.tag;

    // 2.执行下划线动画,动画执行完成修改scrollView的偏移量,显示对应子控制器的view
    [UIView animateWithDuration:0.25 animations:^{

        // TODO: 设置下划线的宽度和中心点
        self.underLineView.wx_width = button.titleLabel.wx_width;
        self.underLineView.wx_centerX = button.wx_centerX;

        // 切换到对应的view
        self.scrollView.contentOffset = CGPointMake(self.scrollView.wx_width * index, self.scrollView.contentOffset.y);
    } completion:^(BOOL finished) {
        // 更新偏移量
        CGPoint offset = self.scrollView.contentOffset;
        offset.x = index * self.scrollView.wx_width;
        [self.scrollView setContentOffset:offset];

        // 添加对应子控制器的view
        [self addChildVcViewIntoScrollView:index];
    }];
}

  • 在此基础上, 还加了个jj_ignoreStatusBar的章程, 首假使幸免当前来得 vc上还会有多个小 vc的处境, 那时就在小 vc实现那几个办法, 屏蔽小 vc 的影响
    • 解惑:因为jj_ignoreStatusBar这么些格局自身是无需在本身分类中落到实处的, 我只是要求在急需遮盖的时候可选完毕, 所以设计成这种合同格局, 这种设计情势, 大家能够借鉴借鉴, 其实作者亦不是明亮的很深, 相互学习吧!

2.监听最上端情形栏区域的点击

  • 落到实处思路剖判

    1. 制造四个等级为UIWindowLevelAlert(最高)的且占据全屏的WXTopWindow,并设置其hidden = NO,让其出示在最顶层.

    2. WXTopWindow(继承UIWindow)中重写
      hitTest:withEvent:方法兑现响应中度( <= 20 )也便是
      蓝色区域(状态栏区域)能点击,红色区域( >20 )不可能点击

    3. 创建一个WXTopViewController(继承UIViewController),并
      WXTopViewController设置为WXTopWindow的根控制器.
      为WXTopWindow已经设置仅顶部状态栏区域能点击,所以
      重写WXTopViewController的view的touchesBegan方法就可以 仅监听状态栏区域的点击事件.

  • 监听状态栏点击完结深入分析图
![](https://upload-images.jianshu.io/upload_images/1253159-89a88ae251a2017d.png)

监听状态栏点击实现解析图.png

监听顶上部分状态栏的点击事件的落到实处

  • 监听状态栏点击事件达成

    • 1.自定义WXTopWindow(继承UIWindow)
    • 2.提供叁个参数为block的的类方法,供外界调用.block会在场所栏区域被点击的时候调用
        (void)showWithStatusBarClickBlock:(void (^)())block;
    
    • 3.在WXTopWindow(继承UIWindow)中重写hitTest:withEvent:方法达成只响应状态栏区域的点击.
    • 4.showWithStatusBarClickBlock:类措施的贯彻

      • 1.确定即使该window已经创立,不须要再次创下造,因为window整个应用程序只要求两个,不必要重新创造
      • 2.增多window到状态栏区域,window暗许填充整个显示器,所以不要设置frame
      • 3.设置window的优先级为最高,比状态栏的预先级高
        UIWindowLevelAlert(高) UIWindowLevelStatusBar(中) UIWindowLevelNormal(低,默认)
      • 4.设置window的背景象为透明色
      • 注意: 需先显示window再设置根控制器.
      • 5.开立WXTopViewController并安装背景观为clearColor
      • 6.设置旋转时只拉伸宽度,不然会产出view的境况栏区域旋转消失或变大问题.
        调控器的view默许是长宽都自动拉伸,此处只需拉伸宽度,没有须要拉伸中度.
      • 7.将block传递给调整器管理,当调整器的view的图景栏区域被点击,调用block
      • 8.设置WXTopWindow的根调整器为WXTopViewController.独有设置window的根控制器,window里面的内容才会跟随旋转.window本身不会旋转.

    • 5.在WXTopViewController调控器中贯彻touchesBegan:方法监听最上部状态栏区域的点击.
      • touchesBegan:方法监听到最上部气象栏区域的点击,调用WXTopWindow传递过来的block.
    • 注意: WXTopViewController必需旗帜明显钦定状态栏展现,不然会出现旋转后景况栏未有.

    • 采纳方法: 调用showWithStatusBarClickBlock:即可实现监听状态栏区域的点击.
    [WXTopWindow showWithStatusBarClickBlock:^{
        NSLog(@"点击了顶部状态栏区域");
    }];
    

3.状态栏点击调节tableView滚动

索求全部的scrollView

  • 1.先是落到实处UIView的归类方法: 决断方法调用者和view(本项目效率指keyWindow)是或不是重叠
// ----------------------------------------------------------------------------
// 判断方法调用者和view是否重叠
- (BOOL)wx_intersectWithView:(UIView *)view
{
    // 如果传入的参数是nil,则表示为[UIApplication sharedApplication].keyWindow
    if (view == nil) {
        view = [UIApplication sharedApplication].keyWindow;
    }

    // 都统一转换成window坐标系,并判断是否重叠,返回判断结果
    CGRect rect1 = [self convertRect:self.bounds toView:nil];
    CGRect rect2 = [view convertRect:view.bounds toView:nil];
    return CGRectIntersectsRect(rect1, rect2);
}
  • 2.查寻找window里面包车型大巴具有scrollView

    • 1.肯定是或不是在keyWindow的范围内(不跟window重叠),即便不在,直接退出
    • 2.遍历view的全数子控件和子控件的子控件,此处for循环会退出,所以递归调用会退出
    • 3.肯定假如scrollView,直接回到
    • 4.轮转scrollView到最最上部
      • 方法一: 获取scrollView,将scrollView的偏移量y值设置为负的内边距顶部值: -scrllView.contentInset.top
        UIScrollView *scrllView = (UIScrollView *)view;
        CGPoint offset = scrllView.contentOffset;
        offset.y = -scrllView.contentInset.top;
        [scrllView setContentOffset:offset animated:YES];
    
    - 方法二: 让`scrollView移动到其内容的最顶部`.
    
      ```objectivec
      [scrollView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
      ```
    
    • 使用: 在didFinishLaunchingWithOptions:格局中调用UIView的分类对象方法,searchAllScrollViewsInView:方法,传入application.keyWindow参数,判断scrollView是否在keyWindow中,如果在keyWindow中,则滚动scrollView到顶部.

    • 兑现当前来得的scrollView/tableView点击顶部状态栏区域滚动到scrollView/tableView最顶部参照代码
      - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
          // 1.创建window
          self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    
          // 2.设置window的根控制器
          self.window.rootViewController = [[WXAdViewController alloc] init];
          // init -> initWithNibName -> 1.判断有没有指定NibName 2.判断有没有跟控制器同名的xib,就会去加载 3.判断下有没有不带controller的xib 4.创建一个clearColor透明的View
    
          // 3.让window成为主窗口,并显示
          [self.window makeKeyAndVisible];
    
          // 4.添加topWindow
          [WXTopWindow showWithStatusBarClickBlock:^{
              [self searchAllScrollViewsInView:application.keyWindow];
          }];
    
          return YES;
      }
    
      // ----------------------------------------------------------------------------
      // 查找出view里面的所有scrollView
      - (void)searchAllScrollViewsInView:(UIView *)view
      {
          // 1.判断是否在keyWindow的范围内(不跟window重叠),如果不在window范围内,直接退出
          if (![view wx_intersectWithView:nil]) {
              return;
          }
    
          // 2.遍历view的所有子控件和子控件的子控件,此处for循环会退出,所以递归调用会退出
          for (UIView *subview in view.subviews) {
              [self searchAllScrollViewsInView:subview];
          }
    
          // 3.判断如果scrollView,直接返回
          if (![view isKindOfClass:[UIScrollView class]]) {
              return;
          }
    
          // 4.滚动scrollView到最顶部
          UIScrollView *scrllView = (UIScrollView *)view;
          CGPoint offset = scrllView.contentOffset;
          offset.y = -scrllView.contentInset.top;
          [scrllView setContentOffset:offset animated:YES];
      }
    

4.监听tabBarButton的双珍贵击

  • 运营效果图
![](https://upload-images.jianshu.io/upload_images/1253159-7136bdf71151ea33.gif)

监听tabBarButton的重复点击效果图.gif
  • 落到实处思路:

    • 1.想想使用UITabBarButton的addTarget形式监听(可行,轻便)
    • 2.施用UITabBar代理格局监听(经验证,不可行)
      原因: 被叁个UITabBarController管理的tabBar的代办是无法被改动的.借使一个UITabBar被UITabBarController管理,又再度设置UITabBar的代办就能够报错,运维时报错: reason: 'Changing the delegate of a tab bar managed by a tab bar controller is not allowed.)
      一经UITabBar未有被UITabBarController管理,是足以修改它的代办的.示例代码
    // UITabBar没有被UITabBarController管理,是可以修改它的代理的
    UITabBar *tabBar = [[UITabBar alloc] init];
    tabBar.delegate = self;
    [self.view addSubview:tabBar];
    
    • 3.用到UITabBarController的代办监听(可行)

监听tabBarButton重复点击格局一(使用tabBarButton addTarget情势,本项目应用此方法)

  • 1.在layoutSubviews方法中获得具有tabBarButton,使用addTarget情势监听tabBarButton的点击

    • UITabBarButton是私有类,不可能运用,所以打字与印刷其superClass,父类为UIControl,所以能够用addTarget方法监听点击事件
    • 监听tabBarButton重复点击的二种格局
      • 1.笔录上二次点击的tabBarButton的tag值.监听到tabBarButton重复点击,发送通知,通知外部tabBarButton重复点击。
          // ----------------------------------------------------------------------------
          // 重新布局tabBar子控件
          - (void)layoutSubviews
          {
              [super layoutSubviews];
    
              // 1.定义frame属性
              NSInteger count = self.items.count;
              CGFloat itemX = 0;
              CGFloat itemY = 0;
              CGFloat itemW = self.wx_width / (count   1);
              CGFloat itemH = self.wx_height;
    
              // 2.遍历子控件(过滤UITabBarButton), UITabBarButton是私有属性
              NSInteger index = 0;
              for (UIView *view in self.subviews) {
                  // 2.1 过滤UITabBarButton
                  // 可以用两张方式判断
                  // 1.[view isKindOfClass:NSClassFromString(@"UITabBarButton")]
                  // 2.[@"UITabBarButton" isEqualToString:NSStringFromClass([view class])]
                  if ([view isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
    
                      // 2.2 计算x值,并设置frame
                      itemX = index * itemW;
                      view.frame = CGRectMake(itemX, itemY, itemW, itemH);
    
                      // 2.3 监听UITabBarButton的点击,打印view的父类为UIControl
                      // TODO: 使用AddTarget监听tabBarButton的点击
                      UIControl *tabBarButton = (UIControl *)view;
                      tabBarButton.tag = index;
                      [tabBarButton addTarget:self action:@selector(tabBarButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    
                      index  ;
                      // 判断如果是是第二个batBarButton,空一格
                      if (index == 2) {
                          index  ;
                      }
                  }
              }
    
              // 3.设置加号按钮
              self.plusButton.center = CGPointMake(self.wx_width * 0.5, self.wx_height * 0.5);
          }
          // ----------------------------------------------------------------------------
          // 使用记录上一次点击的tabBarButton的tag方法监听tabBarButton的点击
          - (void)tabBarButtonClick:(UIControl *)tabBarButton
          {
              // 使用tabBarButton的tag方法监听
              if (self.selectedTabBarButton.tag == tabBarButton.tag) {
                  // 发送通知
                  [[NSNotificationCenter defaultCenter] postNotificationName:WXTabBarButtonDidRepeatClickNotification object:nil];
              }
    
              // 记录选中tabBarButton
              self.selectedTabBarButton = tabBarButton;
          }
    
    • 2.记下上贰遍点击的tabBarButton.监听到tabBarButton重复点击,发送通知,布告外部tabBarButton重复点击。

    注意: 此方式有个bug,在前后相继刚起步成功,私下认可选中第0个tabBarButton,若是再点击第0个tabBarButton时,并从未触发重复点击.原因是因为记录上一次点击的tabBarButton默认为nil.所以使用该方法需在合适的地方上一次点击的tabBarButton赋一个初始值.

      以下参考代码存在以上所提的bug,解决此bug必须给上一次点击的tabBarButton赋一个初始值.
    
      ```objectivec
      // ----------------------------------------------------------------------------
      // 使用记录上一次点击的tabBarButton方法监听tabBarButton的点击
      - (void)tabBarButtonClick:(UIControl *)tabBarButton
      {
          // TODO: 需注意程序刚启动时self.selectedTabBarButton == nil的情况
          if (self.selectedTabBarButton == tabBarButton) {
              [[NSNotificationCenter defaultCenter] postNotificationName:WXTabBarButtonDidRepeatClickNotification object:nil];
          }
          // 记录选中tabBarButton
          self.selectedTabBarButton = tabBarButton;
      }
      ```
    

监听tabBarButton重复点击形式二(使用UITabBarController的代办方式)

  • 1.在广告分界面就要跳转到TabBarController的地方,为TabBarController设置代理代理对象是·[UIApplication sharedApplication].delegate,无法用广告调控器做为代理对象,因为即使根调节器切换成TabBarController时,广告控制器就会被销毁.
// ----------------------------------------------------------------------------
// 监听点击跳过按钮
- (IBAction)jump {

    // 关闭定时器
    [self.timer invalidate];

    WXTabBarController *tabBarVc = [[WXTabBarController alloc] init];
    tabBarVc.delegate = (id<UITabBarControllerDelegate>)[UIApplication sharedApplication].delegate;
    [UIApplication sharedApplication].keyWindow.rootViewController = tabBarVc;
}
  • 2.在APPDelegate.m文件中,让AppDelegate遵守UITabBarControllerDelegate协议,并实现tabBarController:didSelectViewController:代理方法,监听TabBarController选中了哪些调节器.
// ----------------------------------------------------------------------------
// 监听tabBarController当前选中哪个控制器
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
    // selectedVc用于存放TabBarController上一次选中的控制器
    static UIViewController *selectedVc = nil;

    // 设置初始化上一次选中的控制器为tabBarController的第0个子控制器.
    if (selectedVc == nil) {
        selectedVc = tabBarController.childViewControllers[WXDefaultVcIndex];
    }

    // 如果上一次选中的控制器和当前选中控制器一样,表示重复点击,发送通知
    if (selectedVc == viewController) {
        [[NSNotificationCenter defaultCenter] postNotificationName:WXTabBarButtonDidRepeatClickNotification object:nil];
    }

    // 更新上一次选中控制器
    selectedVc = viewController;
}

子调控器监听tabBarButton重复点击通告

  • 在对应子控制器的viewDidLoad方法中监听tabBarButton重复点击文告,施行响应操作
- (void)viewDidLoad {
    [super viewDidLoad];

    WXFunc();

    self.view.backgroundColor = WXRandomColor;
    self.tableView.contentInset = UIEdgeInsetsMake(WXNavMaxY   WXTitlesViewH, 0, WXTabBarH, 0);

    // 1.监听通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tabBarButtonDidRepeatClick) name:WXTabBarButtonDidRepeatClickNotification object:nil];
}

#pragma =======================================================================
#pragma mark - 监听tabBarButton重复点击通知
- (void)tabBarButtonDidRepeatClick
{
    NSLog(@"%@: 重复点击,执行下拉刷新", [self class]);
}

- (void)dealloc
{
    // 移除通知
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

本文由澳门新萄京发布于www.澳门新萄京赌场,转载请注明出处:事件响应及传递,滚动到顶上部分

上一篇:澳门新萄京:jenkins部署及发布使用,前端开发福 下一篇:没有了
猜你喜欢
热门排行
精彩图文