需求:
指定按钮, 或者区域的镂空处理, 点击镂空区域响应底部视图的事件
效果如上gif动画
封装代码:
//
// GuideView.swift
// TestView
//
// Created by cpcoder on 24/8/2023.
//
import UIKit
enum GuideType {
case cycle // 圆
case roundedRect // 矩形
}
class GuideView: UIView {
fileprivate let rect: CGRect
fileprivate let cornerPath: UIBezierPath
/// 镂空区域的准确frame
/// - Parameter rect: CGRect
init(rect: CGRect, type: GuideType) {
self.rect = rect
switch type {
case .cycle:
// 圆心坐标
let centerPoint = CGPoint(x: rect.minX + rect.width / 2,
y: rect.minY + rect.height / 2)
// 圆半径
let radius = rect.width / 2
// 圆形镂空路径
self.cornerPath = UIBezierPath(arcCenter: centerPoint,
radius: radius,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: false)
case .roundedRect:
// 这里是一个圆角矩形镂空
self.cornerPath = UIBezierPath(roundedRect: rect, cornerRadius: 10)
}
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func show() {
let frame = UIScreen.main.bounds
self.frame = frame
self.backgroundColor = UIColor.black.withAlphaComponent(0.7)
guard
let delegate = UIApplication.shared.delegate as? AppDelegate,
let window = delegate.window
else {
return
}
// 这里也可以添加到指定的View上, 比如某个控制器的view上
window.addSubview(self)
// 也可以在这里添加一些文字说明的空间, 或者手指动画
// let uiTipsLb = UILabel(frame: CGRect(x: rect.minX, y: rect.maxY, width: 100, height: 50))
// uiTipsLb.text = "提示文案 或者 图像"
// uiTipsLb.textColor = .white
// addSubview(uiTipsLb)
// 添加圆角镂空矩形路径
let path = UIBezierPath(rect: frame)
path.append(cornerPath)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillRule = .evenOdd
layer.mask = shapeLayer
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if cornerPath.contains(point) {
// 镂空区域点击事件穿透
removeFromSuperview()
return nil
} else {
return super.hitTest(point, with: event)
}
}
}
调用:
//
// TestController.swift
// TestView
//
// Created by cpcoder on 28/8/2023.
//
import UIKit
class TestController: UIViewController {
@IBOutlet weak var uiAddBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
// 添加按钮事件
@IBAction func addAction(_ sender: Any) {
print("addAction")
}
/// 点击Cycle按钮, 展示圆形镂空区域
/// - Parameter sender:
@IBAction func cycleAction(_ sender: Any) {
// 之所以写延迟, 是因为
// 往往我们需要引导的场景是, 进入页面, 1. viewWillAppear(切换页面多次调用); 2. viewDidAppear(切换页面多次调用) 3.viewDidLoad(调用一次)
// 如果 写在 viewDidLoad 那么极有可能获取不到准确的frame
// 为了只展示一次, 且得到准确的frame, 所以写延迟
// 当然, 也可以写在 1 & 2 中, 那么 你就要额外的变量去标记是否已经展示过镂空引导, 从而解决多次展示问题
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
// 获取uiAddBtn转换到self.view之后的确切的frame
let rect = self.uiAddBtn.convert(self.uiAddBtn.bounds, to: self.view)
GuideView(rect: rect, type: .cycle).show()
})
}
/// 点击圆角矩形按钮, 展示圆角矩形镂空区域
/// - Parameter sender:
@IBAction func RoundedRectAction(_ sender: Any) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
// 获取uiAddBtn转换到self.view之后的确切的frame
let rect = self.uiAddBtn.convert(self.uiAddBtn.bounds, to: self.view)
GuideView(rect: rect, type: .roundedRect).show()
})
}
}
这些年经历过几次这样的镂空引导, 但还是忘, 记下来