关键词:UIKit · 新手引导 · 低侵入 · 插件扩展
GitHub:**https://github.com/noodles1024/PolarisGuideKit**
可能是新手引导这个功能太小,随便实现一下也能用,导致没有人愿意来写认真写一个 iOS 下的新手引导组件,搜遍整个 github 也找不到一个在现实项目中能直接拿来用的。如果只考虑某一个具体的新手引导界面,实现起来很容易(特别是现在在 AI 的加持下,UI 仔都不需要了)。但在不同项目、不同场景下,经过和和产品经理&设计师的多次沟通中,我发现了做“新手引导/功能提示”时的一些令人头疼的问题:
reloadData 后高亮经常失效于是我做了 PolarisGuideKit:一个基于 UIKit 的轻量新手引导组件,主打低侵入 + 可扩展 + 动态高亮。
| 能力 | 说明 | 带来的价值 |
|---|---|---|
| 高亮遮罩 | 遮罩挖孔高亮 focusView | 高亮区域自动跟随,内置高亮效果,可自定义 |
| Buddy View | 说明视图可自由定制 | 文案、箭头、按钮任意组合 |
| 步骤编排 | 多步骤引导流程 | 支持下一步、跳过、完成 |
| 动态 focusView | reloadData 后自动修正 | TableView/CollectionView 场景稳定 |
| 插件化扩展 | Audio/埋点/持久化 | 可插拔、解耦 |
import UIKit
import PolarisGuideKit
final class MyViewController: UIViewController {
private var guide: GuideController?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let step = GuideStep()
step.focusView = myButton
step.buddyView = MyBuddyView()
step.forwardsTouchEventsToFocusView = true
step.completer = ControlEventCompleter(control: myButton, event: .touchUpInside)
let controller = GuideController(hostView: view, steps: [step])
controller.onDismiss = { _, context in
print("引导结束,原因 = \(context.reason)")
}
_ = controller.show()
guide = controller
}
}
内置样式包含:
DefaultFocusStyle(矩形)RoundedRectFocusStyle(圆角矩形)CircleFocusStyle(圆形)NoHighlightFocusStyle(全屏遮罩)let step = GuideStep()
step.focusView = someCard
step.focusStyle = RoundedRectFocusStyle(
focusCornerRadius: .followFocusView(delta: 2),
focusAreaInsets: UIEdgeInsets(top: -6, left: -6, bottom: -6, right: -6)
)
UITableView / UICollectionView 复用导致高亮错位?
使用 focusViewProvider 动态获取最新 cell:
let step = GuideStep()
step.focusViewProvider = { [weak self] in
guard let self else { return nil }
var cell = self.tableView.cellForRow(at: targetIndexPath)
if cell == nil {
self.tableView.layoutIfNeeded()
cell = self.tableView.cellForRow(at: targetIndexPath)
}
return cell
}
在不侵入原有业务逻辑的前提下,高亮按钮依然能触发业务逻辑,同时自动关闭引导:
let step = GuideStep()
step.focusView = myButton
step.forwardsTouchEventsToFocusView = true
step.completer = ControlEventCompleter(control: myButton, event: .touchUpInside)
✅ 设置 forwardsTouchEventsToFocusView 和 completer 保证了“引导不侵入原有业务逻辑”。
继承 GuideBuddyView,自定义 UI + 布局:
final class MyBuddyView: GuideBuddyView {
override func updateLayout(referenceLayoutGuide layoutGuide: UILayoutGuide, focusView: UIView) {
super.updateLayout(referenceLayoutGuide: layoutGuide, focusView: focusView)
// 根据 layoutGuide 布局你的文案 / 按钮 / 箭头
}
}
内置 AudioGuidePlugin,可在显示引导时播放音频文件,且可在 BuddyView 中配合显示音频播放动画(可选功能):
let step = GuideStep()
step.focusView = myCard
step.addAttachment(GuideAudioAttachment(url: audioURL, volume: 0.8))
let controller = GuideController(
hostView: view,
steps: [step],
plugins: [AudioGuidePlugin()]
)
如果想要加埋点、标记“引导是否已显示”,可通过自定义
GuidePlugin实现。
flowchart TB
subgraph Core["核心组件"]
GuideController["GuideController<br/>(流程编排器)"]
GuideStep["GuideStep<br/>(步骤配置)"]
end
subgraph ViewHierarchy["视图层级"]
GuideContainerView["GuideContainerView<br/>(透明容器)"]
GuideOverlayView["GuideOverlayView<br/>(遮罩 + 触摸转发)"]
MaskOverlayView["MaskOverlayView<br/>(遮罩基类)"]
GuideBuddyView["GuideBuddyView<br/>(说明视图)"]
GuideShadowView["GuideShadowView<br/>(焦点追踪器)"]
end
subgraph Extensions["扩展机制"]
FocusStyle["FocusStyle<br/>(高亮形状)"]
GuideAutoCompleter["GuideAutoCompleter<br/>(完成触发器)"]
GuidePlugin["GuidePlugin<br/>(生命周期钩子)"]
GuideStepAttachment["GuideStepAttachment<br/>(插件数据)"]
end
GuideController -->|"管理"| GuideStep
GuideController -->|"创建并承载"| GuideContainerView
GuideController -->|"派发事件"| GuidePlugin
GuideContainerView -->|"包含"| GuideOverlayView
GuideContainerView -->|"包含"| GuideBuddyView
GuideOverlayView -.->|"继承"| MaskOverlayView
GuideOverlayView -->|"创建"| GuideShadowView
GuideOverlayView -->|"使用"| FocusStyle
GuideStep -->|"配置"| GuideBuddyView
GuideStep -->|"使用"| FocusStyle
GuideStep -->|"通过...触发"| GuideAutoCompleter
GuideStep -->|"携带"| GuideStepAttachment
https://github.com/noodles1024/PolarisGuideKitpod 'PolarisGuideKit'
import PolarisGuideKit
focusView 必须是 hostView 的子视图hostViewGuideAutoCompleter 触发后会结束整个引导(建议用于最后一步)animatesStepTransition = false