MMPopupView
MMPopupView 是我自己比较喜欢的一个库,风格简洁好看,调用和自定义都很方便。缺点是对横竖屏的支持不是很好。
在我初学的时候,也经常阅读作者的博客学习。
以下是 MMPopupView 的整体结构:
这次阅读,就到 MMPopupView 的阶段,因为之后的如 MMAlertView , MMSheetView 等都是基于 MMPopupView 自定义出来的。
MMPopupWindow
首先从作为所有 UI 的容器MMPopupWindow说起。
单例
在 MMPopupWindow 的类里,可以看到单例方法 :
1 | + (MMPopupWindow *)sharedWindow |
首先初始化一个全屏大小的 window,然后为 window 设定 rootViewController 。
初始化
初始化的方法为:
1 | - (instancetype)initWithFrame:(CGRect)frame |
windowLevel
注意到有一个 windowLevel 的设置:
1 | self.windowLevel = UIWindowLevelStatusBar + 1; |
windowLevel 为 UIWindowLevel 类型.
而 UIWindowLevel 实际的值为 CGFloat 类型 .
UIWindow 在显示时,会根据 UIWindowLevel 做排序,Level 值高的将排在前面。
系统有三个 UIWindowLevel 层级:
1 | UIKIT_EXTERN const UIWindowLevel UIWindowLevelNormal; |
UIWindow 的默认级别是 UIWindowLevelNormal .
对应的关系为:
1 | UIWindowLevelNormal < UIWindowLevelStatusBar < UIWindowLevelAlert . |
而 UIWindowLevel 的值可以是任意的,代码里的 UIWindowLevelStatusBar + 1
表示级别在 StatusBar 和 Alert 之间:
1 | UIWindowLevelStatusBar < windowLevel < UIWindowLevelAlert |
添加手势
这里添加了一个 tap 的手势:
1 | UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(actionTap:)]; |
可以看到,在指定手势响应方法之外,对 cancelsTouchesInView
设置为 NO.
cancelsTouchesInView
默认为 YES,当识别到手势的时候,会终止发送所有触摸事件。cancelsTouchesInView
为NO,则不发送终止触摸的消息,让 Tap 手势和其它响应方法,同时响应触摸事件。
Tap 手势代理
设置是否响应点击操作,返回NO代表不做操作,返回YES则会做出响应.
1 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch |
从上面的代码可以看到,从判断 touch.view 和 self.attachView.mm_dimBackgroundView是否一样,来决定做不做响应。所以只有点击背景时,才会有响应。
attachView
从下面代码看到,attachView实际就是根控制器的视图。
1 | - (UIView *)attachView |
手势响应事件
下面是响应点击的代码
1 | - (void)actionTap:(UITapGestureRecognizer*)gesture |
当 touchWildToHide 为 YES ,以及 mm_dimBackgroundAnimating 为NO,则会把 [self attachView].mm_dimBackgroundView 里的所有 MMPopupView 实例隐藏。
cacheWindow
1 | - (void)cacheWindow |
首先 MMPopupWindow 发送 makeKeyAndVisible 消息,让它成为应用的主窗口并显示。
再获取 AppDelegate 的 window 让它成为主窗口并显示。
再将背景和MMPopupWindow都给隐藏。
MMPopupView
初始化设置
setup
首先初始化调用了 setup
方法
1 | - (void)setup |
指定 type
为 MMPopupTypeAlert. animationDuration
为 0.3 秒.
设置 MMPopupView
的 attachedView 为 MMPopupWindow
的 attachedView .
设置了一个名为 MMPopupViewHideAllNotification
的通知,和处理通知消息的方法 notifyHideAll:
看一下 setType:
方法:
1 | - (void)setType:(MMPopupType)type |
以上代码可以看到,在判断 Type 后,都是针对两个 MMPopupView 的属性 showAnimation 和 hideAnimation 做设置。
它们都是 MMPopupBlock 类型的 block:
1 | @property (nonatomic, copy) MMPopupBlock showAnimation; // custom show animation block. |
通过以上两个属性来设置 弹出 和 隐藏 动画。
显示
showWithBlock
1 | - (void)showWithBlock:(MMPopupCompletionBlock)block |
判断 MMPopupCompletionBlock ,进行设置。
判断 attachView,进行设置。
显示 attachedView 背景。
用 NSAssert 检测,调用 showAnimation 的动画 block。
然后检测是否有键盘一起弹出。
隐藏
hideWithBlock
1 | - (void)hideWithBlock:(MMPopupCompletionBlock)block |
判断 MMPopupCompletionBlock ,进行设置。
判断 attachView,进行设置。
隐藏 attachedView 背景。
然后检测是否有键盘,一起隐藏。
用 NSAssert 检测,调用 hideAnimation 的动画 block。
MMPopupCategory
Associated Object
MMPopupCategory 里的很多方法都利用到了 Associated Object ,也就是 关联对象 来添加实例变量和获取实例变量。
这里是关联对象 getter/setter 方法的原型:
1 | id objc_getAssociatedObject(id object, const void *key); |
object 是要添加属性的对象, key 是变量名称,value 是你要给定的值。
而 policy 为 objc_AssociationPolicy 类型,是一个枚举:
1 | typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { |
不同的 objc_AssociationPolicy
对应了不通的属性修饰符:
objc_AssociationPolicy | modifier |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, strong |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy |
OBJC_ASSOCIATION_RETAIN | atomic, strong |
OBJC_ASSOCIATION_COPY | atomic, copy |
而一般实现的属性都是用 nonatomic
和 strong
修饰符, 可以选择OBJC_ASSOCIATION_RETAIN_NONATOMIC
。
具体的需要根据场景去确定。
mm_dimBackgroundView
这里的 mm_dimBackgroundView 位于 MMPopupCategory 里,根据代码可以看出来,是作为半透明黑色背景的。
1 | //mm_dimBackgroundView |
总结
这次阅读的源码结构很清晰,不算难懂。
除了解到整个 MMPopupView 的设计思路之外,从中也学习到一些额外的知识和技巧。
打算学习它的方式,给出常用的 AlertView , SheetView ,方便使用者直接使用。
并提供内置动画,也支持自定义动画。