直接上代码
import Foundation
import SnapKit
import UIKit
// MARK: - GuideContainerView
/// 镂空位置
struct GuideLocation {
struct Rect {
/// 镂空尺寸
var frame: CGRect
/// 镂空区域圆角半径
var cornerRadius: CGFloat = 0
/// 镂空区域水平间距
var dx: CGFloat = -1
/// 镂空区域垂直间距
var dy: CGFloat = -1
var bPath: UIBezierPath {
return UIBezierPath(roundedRect: frame.insetBy(dx: dx, dy: dy), cornerRadius: cornerRadius)
}
}
var rects: [Rect] = []
var paths: [UIBezierPath] = []
}
/// CoverView 的 子控件布局参考点位
struct Point {
var minX: CGFloat = 0
var minY: CGFloat = 0
var maxX: CGFloat = 0
var maxY: CGFloat = 0
}
/// 引导view协议
protocol CoverViewProtocol: UIView {
func layout(make: ConstraintMaker, super: UIView)
/// 镂空位置
var location: GuideLocation { get set }
/// 供 接入方 使用
var clickAction: ((@escaping (Bool) -> Void) -> Void)? { get set }
/// 供 GuideContainerView 使用
var nextStep: (() -> Void)? { get set }
/// 子控件布局参考点位
var referPoint: Point { get set }
}
/// 引导容器
class GuideContainerView: BaseView {
public var covers: [CoverViewProtocol] = []
/// 所有 引导图 展示完毕
public var complete: (() -> Void)?
private var index: Int = 0
deinit {
print("GuideContainerView deinit")
}
override func setupUI() {
super.setupUI()
clipsToBounds = true
backgroundColor = UIColor.black.withAlphaComponent(0.7)
}
public func show(in view: UIView? = UIApplication.shared.keyWindow) {
guard let view = view else { return }
if covers.isEmpty { return }
index = 0
view.addSubview(self)
snp.makeConstraints { make in
make.edges.equalToSuperview()
}
setNeedsLayout()
layoutIfNeeded()
showCover()
}
private func showCover() {
if index < 0 || index > covers.count - 1 {
complete?()
removeFromSuperview()
return
}
let cover = covers[index]
cover.nextStep = { [weak self, weak cover] in
guard let self = self else { return }
guard let cover = cover else { return }
cover.removeFromSuperview()
self.index += 1
self.showCover()
}
addSubview(cover)
cover.snp.makeConstraints { [weak self] make in
guard let self = self else { return }
cover.layout(make: make, super: self)
}
let path = UIBezierPath(rect: bounds)
if cover.location.rects.isNotEmpty {
for rect in cover.location.rects {
path.append(rect.bPath)
}
}
if cover.location.paths.isNotEmpty {
for subPath in cover.location.paths {
path.append(subPath)
}
}
let shape = CAShapeLayer()
shape.path = path.cgPath
shape.fillRule = .evenOdd
layer.mask = shape
}
}
// MARK: - CoverView
class CoverView: BaseView, CoverViewProtocol {
var clickAction: ((@escaping (Bool) -> Void) -> Void)?
var nextStep: (() -> Void)?
var location = GuideLocation()
var referPoint = Point()
func layout(make: ConstraintMaker, super: UIView) {
make.edges.equalToSuperview()
}
}
举个
1.自定义 CoverView
class HomeFinanceCoverView: CoverView {
enum Step {
case s1
}
var step: Step = .s1
let imgStep = UIImageView().then {
$0.isUserInteractionEnabled = true
}
let imgGuide = UIImageView()
deinit {
print("HomeFinanceCoverView deinit")
}
override func setupUI() {
super.setupUI()
addSubview(imgStep)
addSubview(imgGuide)
imgStep.rx.tapGesture().when(.ended)
.subscribe(onNext: { [weak self] _ in
guard let self = self else { return }
if let clickAction = self.clickAction {
clickAction { finish in
if finish {
self.nextStep?()
}
}
} else {
self.nextStep?()
}
})
.disposed(by: rx.disposeBag)
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
switch step {
case .s1:
imgGuide.image = UIImage(named: "loan_guide_1")
imgStep.image = UIImage(named: "loan_step_1")
imgGuide.snp.makeConstraints { make in
make.leading.equalTo(16.shtScale)
make.size.equalTo(CGSize(width: 178.shtScale, height: 106.shtScale))
make.top.equalTo(self.referPoint.maxY + 8.shtScale)
}
imgStep.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.size.equalTo(CGSize(width: 106.shtScale, height: 56.shtScale))
make.bottom.equalToSuperview().offset(-40.shtScale)
}
}
}
}
2.使用
let cover = GuideContainerView()
let cell = ...
let rect = collectionView.convert(cell.frame, to: UIApplication.shared.keyWindow)
let c1 = HomeFinanceCoverView().then {
$0.step = .s1
$0.referPoint = .init(minX: rect.minX, minY: rect.minY, maxX: rect.maxX, maxY: rect.maxY)
$0.location.rects = [
.init(frame: CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height), cornerRadius: 6.shtScale)
]
}
cover.covers.append(c1)
cover.show()