iOS开发- 自定义遮罩视图(引导, 功能说明)源码+解析
我们平时使用App的时候, 经常在第一次使用的时候, 会有类似”新手教程”之类的东西, 来引导我们应该如何使用这个App。
但是这个”新手教程”不同于常规的引导页(引导页指第一次打开App时候, 弹出的那种介绍视图。 他是静态的, 不需要与用户交互, 可以直接一页页翻, 或者直接跳过。)所谓的”新手教程”, 就是按照App的提示, 一步步跟着完成。
那这个“新手教程”有什么好处呢?
- 指导用户了解操作。 比如, 如何调节音量, 如何设置焦距…
- 限制用户填充必要信息, 完成必要操作。
现在大家应该都明白这到底是一个什么东西了。为实现这样的功能, 一般是采用遮罩层效果, 今天花了点时间, 封装好了这样一个功能。 同时把源码分享到我的Github, 需要的可以自己下载。
下载链接: YLZHoledView
同时, 还写了一个demo, 效果如下:
我封装好的这个文件(YLZHoledView), 主要实现了如下功能:
1. 添加多种样式聚光灯效果。(圆形, 矩形, 圆角矩形, 自定义视图)
2. 点击该区域支持delegate回调
3. UIView 中的控件事件穿透。(demo中的按钮, 是在遮罩层下方, 但是正确响应点击事件, 这就是事件穿透)
好了, 大体介绍就到这里, 下面简单解释下核心代码:
delegate回调
YLZHoledView.h
@class YLZHoledView;
@protocol YLZHoledViewDelegate <NSObject>
- (void)holedView:(YLZHoledView *)holedView didSelectHoleAtIndex:(NSUInteger)index;
@end
YLZHoledView.m
#pragma mark - Tap Gesture
- (void)tapGestureDetectedForGesture:(UITapGestureRecognizer *)gesture
{
if ([self.holeViewDelegate respondsToSelector:@selector(holedView:didSelectHoleAtIndex:)]) {
CGPoint touchLocation = [gesture locationInView:self];
[self.holeViewDelegate holedView:self didSelectHoleAtIndex:[self holeViewIndexForAtPoint:touchLocation]];
}
}
这里先在自定义的View中添加手势, 然后在
- (NSUInteger)holeViewIndexForAtPoint:(CGPoint)touchLocation
中判断点击范围是否属于遮罩层中添加的视图, 再发送委托。
然后在我们的实现类中, 也就是实现这个委托的地方, 就可以通过index来做相关的操作。 我demo里面只是简单的打印。
聚光灯效果
我这里封装了几种常见的类型。(圆形, 矩形, 圆角矩形, 自定义视图)
typedef NS_ENUM(NSInteger, YLZHoleType)
{
YLZHoleTypeCirle,
YLZHoleTypeRect,
YLZHoleTypeRoundedRect,
YLZHoleTypeCustomRect
};
然后重写
- (void)drawRect:(CGRect)rect
通过类型判断, 来重写对应的效果。
比如, 圆形的代码:
if (hole.holeType == YLZHoleTypeRoundedRect) {
YLZRoundedRectHole *rectHole = (YLZRoundedRectHole *)hole;
CGRect holeRectIntersection = CGRectIntersection( rectHole.holeRect, self.frame);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:holeRectIntersection
cornerRadius:rectHole.holeCornerRadius];
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor clearColor] CGColor]);
CGContextAddPath(UIGraphicsGetCurrentContext(), bezierPath.CGPath);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
CGContextFillPath(UIGraphicsGetCurrentContext());
}
事件穿透
在这之前, 提一个方法。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
// recursively calls -pointInside:withEvent:. point is in the receiver’s coordinate system
只要实现了最顶层的 UIView 的 hitTest 方法,在某些情况返回下层的某个按钮实例,即相当于把那个按钮的事件透出来了,比如在点击落在该按钮上时,不管这个按钮在 UIView 下多少层都可以把它挖出来。
所以, 重写hitTest方法即可。
#pragma mark - UIView Overrides
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self)
{
for (UIView *focus in self.focusView) {
if (CGRectContainsPoint(focus.frame, point))
{
return focus;
}
}
}
return hitView;
}