1 前置说明
1.1 适用范围
项目类型:纯 SwiftUI 工程(
App+Scene+WindowGroup生命周期)。目标:接入阿里云 Quick Tracking(QTCommon)SDK,完成页面 PV 埋点、页面属性事件上报。
1.2 纯 SwiftUI 为什么要手动埋点?
官方setAutoPageEnabled:是通过 HookUIViewController的viewWillAppear:/viewWillDisappear:,并采集className实现的:
+ (void)setAutoPageEnabled:(BOOL)value; // 默认 NO但 SwiftUI 的 View 协议本身不是 UIViewController,其生命周期由 UIHostingController 托管,真正触发 viewWillAppear 的是 hosting controller 的 class name,而不是业务页面的语义名称。因此:
纯 SwiftUI 界面无法依赖 setAutoPageEnabled:YES 自动采集到业务页面,必须采用手动页面埋点方案。1.3 封装的类文件
已经集成了QTCommon SDK ,将封装的类文件直接导入到项目:QTPageTracker.swift
2. SDK 初始化(SwiftUI 生命周期)
SwiftUI 默认没有 AppDelegate,需通过 @UIApplicationDelegateAdaptor 注入:
import SwiftUI
import QTCommon
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
qtInit()
registerLifecycleNotifications()
return true
}
private func qtInit() {
// 1. 打开调试日志(Release 必须关闭)
QTConfigure.setLogEnabled(true)
// 2. 设置收数域名
QTConfigure.setCustomDomain("https://your-collect-domain.aliyuncs.com", standbyDomain: nil)
// 3. 初始化 SDK(务必在取得用户隐私授权后调用)
QTConfigure.initWithAppkey("your_app_key", channel: "App Store")
......
}
// MARK: - 前后台通知监听
private func registerLifecycleNotifications() {
NotificationCenter.default.addObserver(
self,
selector: #selector(didEnterBackground),
name: UIApplication.didEnterBackgroundNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(willEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil
)
}
@objc private func didEnterBackground() {
QTPageTracker.appDidEnterBackground()
}
@objc private func willEnterForeground() {
QTPageTracker.appWillEnterForeground()
}
......
}3. 自动页面埋点封装
为了将 beginLogPageView: /endLogPageView: 与 SwiftUI 的onAppear/onDisappear声明式对接,建议统一封装一个ViewModifier和扩展方法。
3.1 核心封装(QTPageTracker.swift)
import SwiftUI
import QTCommon
/// QTCommon 页面追踪器
public enum QTPageTracker {
// MARK: - 页面状态
/// 逻辑上当前正在展示的页面(用于前后台恢复计时)
private static var currentPageName: String?
/// 是否处于后台状态
private static var isInBackground = false
// MARK: - 页面 PV
/// 手动开始页面 PV 统计
public static func beginPage(_ pageName: String) {
currentPageName = pageName
QTMobClick.beginLogPageView(pageName)
#if DEBUG
print("[QTPageTracker] begin page: \(pageName)")
#endif
}
/// 手动结束页面 PV 统计
public static func endPage(_ pageName: String) {
if currentPageName == pageName {
currentPageName = nil
}
QTMobClick.endLogPageView(pageName)
#if DEBUG
print("[QTPageTracker] end page: \(pageName)")
#endif
}
/// 更新页面属性,可在任意时机调用
public static func updateProperties(
_ properties: [String: Any],
forPage pageName: String
) {
guard !properties.isEmpty else { return }
UMSpm.updatePageProperties(pageName, properties: properties)
#if DEBUG
print("[QTPageTracker] update properties for page: \(pageName), properties: \(properties)")
#endif
}
// MARK: - 前后台切换
/// App 进入后台:结束当前页面计时
public static func appDidEnterBackground() {
guard !isInBackground else { return }
isInBackground = true
if let pageName = currentPageName {
QTMobClick.endLogPageView(pageName)
#if DEBUG
print("[QTPageTracker] end page (background): \(pageName)")
#endif
}
}
/// App 返回前台:恢复当前页面计时
public static func appWillEnterForeground() {
guard isInBackground else { return }
isInBackground = false
if let pageName = currentPageName {
QTMobClick.beginLogPageView(pageName)
#if DEBUG
print("[QTPageTracker] begin page (foreground): \\(pageName)")
#endif
}
}
}
// MARK: - View Modifier
struct QTPageTrackModifier: ViewModifier {
let pageName: String
func body(content: Content) -> some View {
content
.onAppear {
QTPageTracker.beginPage(pageName)
}
.onDisappear {
QTPageTracker.endPage(pageName)
}
}
}
extension View {
/// 为当前视图绑定一个 QTCommon 页面编码
public func qtPage(_ name: String) -> some View {
modifier(QTPageTrackModifier(pageName: name))
}
}3.2 普通页面使用方式
非根页面的普通 SwiftUI View,有两个方式
方式一:直接挂 .qtPage(...)
struct HomeView: View {
var body: some View {
VStack {
Text("首页")
}
.qtPage("home_page")
}
}方式二:在view的生命周期中 onAppear 和 onDisappear 调对应的界面开始和结束的埋点
struct HomeView: View {
var body: some View {
VStack {
Text("首页")
}
.onAppear {
QTPageTracker.beginPage("AutoTrackViewB")
}
.onDisappear {
QTPageTracker.endPage("AutoTrackViewB")
}
}
}3.3 NavigationStack根控制器
SwiftUI 中 NavigationStack 的根页面不能依赖 .qtPage 的 onAppear/onDisappear,必须通过 .onChange(of: path) 监听导航路径变化,手动调用 beginPage / endPage进行界面埋点。
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
VStack(spacing: 20) {
Image(systemName: "globe")
Text("Hello, world!")
NavigationLink(value: "AutoTrackViewA") {
Text("跳转到 AutoTrackViewA")
}
}
.padding()
.navigationTitle("首页")
.navigationDestination(for: String.self) { value in
if value == "AutoTrackViewA" {
AutoTrackViewA()
}
}
}
.onChange(of: path, initial: true) { _, newPath in
if newPath.isEmpty {
QTPageTracker.beginPage("homePage")
} else {
QTPageTracker.endPage("homePage")
}
}
}
}4. 页面属性设置
4.1 接口说明
// 需要引入 <QTCommon/UMSpm.h>
+ (void)updatePageProperties:(NSString *)pageName
properties:(NSDictionary *)pProperties;4.2 SwiftUI 中使用
进入页面时设置初始属性
struct DetailView: View {
var body: some View {
VStack {
Text("详情页")
}
.qtPage("detail_page")
.onAppear {
QTPageTracker.updateProperties([
"referrer": "home_page",
"visit_type": "detail"
], forPage: "detail_page")
}
}
}任意时机更新属性
struct DetailView: View {
@State private var selectedTag: String = "none"
var body: some View {
VStack {
Button("标记为 VIP") {
selectedTag = "vip"
QTPageTracker.updateProperties(
["user_tag": "vip"],
forPage: "detail_page"
)
}
}
.qtPage("detail_page")
}
}5. 事件埋点
其他的相关的API使用可以参考:埋点API
5.1 接口说明
+ (void)event:(NSString *)eventCode;
+ (void)event:(NSString *)eventCode attributes:(NSDictionary *)attributes;
+ (void)event:(NSString *)eventId pageName:(NSString *)pageName attributes:(NSDictionary *)attributes;5.2 SwiftUI 中使用
Button("购买") {
QTMobClick.event("purchase", attributes: [
"product_id": "12345",
"category": "book",
"quantity": 3
])
}6. 完整示例
完整的示例:可以参考:swiftui-demo