You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中多PopoverTip修饰符的持续显示故障问题

问题分析与解决方案

问题根源

你的代码中存在高频视图重绘:定时器每0.001秒更新@State变量counter,导致ContentView的body每秒重绘1000次。每次重绘都会重新创建PopoverTip1和PopoverTip2的实例,TipKit会持续重新评估提示的显示状态,最终导致提示框反复出现消失。这是TipKit在视图频繁更新场景下的典型行为,并非框架bug,而是使用方式不当导致的。

核心疑问解答

1. 是否为已知问题?

是的,TipKit的提示绑定依赖视图节点的稳定性。当视图频繁重建时,Tip的生命周期会被频繁打断,引发提示闪烁或重复触发的问题,这是社区中常见的使用误区。

2. 如何在复杂视图中依次显示多个提示弹窗?

可以通过以下两种方式实现有序提示:

  • 使用Tip规则控制显示顺序:给每个Tip添加依赖规则,确保只有前一个提示被关闭后,下一个才会显示。
  • 手动通过TipManager管理:直接调用TipKit的管理类,精准控制提示的显示时机。

3. popoverTip是否仅适用于无状态更新的视图?

不是,但需要保证绑定提示的视图节点稳定,避免因状态频繁更新导致视图反复重建。如果视图需要动态更新状态,可通过复用Tip实例、抽分子视图等方式减少不必要的重绘。

修复代码示例

方案1:添加规则控制顺序+减少重绘频率

import SwiftUI
import TipKit

@main
struct testbedApp: App {
    var body: some Scene {
        WindowGroup {
          ContentView()
        }
    }
  
    init() {
        try? Tips.configure()
    }
}

struct PopoverTip1: Tip {
    var title: Text {
        Text("Test title 1").foregroundStyle(.indigo)
    }

    var message: Text? {
        Text("Test message 1")
    }

    // 规则:仅在未被关闭过的情况下显示
    var rules: [Rule] {
        [Tip.Rule.showNeverAfterDismissed(false)]
    }
}

struct PopoverTip2: Tip {
    var title: Text {
        Text("Test title 2").foregroundStyle(.indigo)
    }

    var message: Text? {
        Text("Test message 2")
    }

    // 规则:仅在PopoverTip1被关闭后显示
    var rules: [Rule] {
        [Tip.Rule.dependency(PopoverTip1.self, isDismissed: true)]
    }
}

struct ContentView: View {
    // 降低定时器频率,减少不必要的重绘
    private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var counter = 1
    
    // 提前初始化Tip实例,避免每次重绘都创建新对象
    private let tip1 = PopoverTip1()
    private let tip2 = PopoverTip2()
    
    var body: some View {
        VStack(spacing: 20) {
            Spacer()
            Text("Counter value: \(counter)")
                .popoverTip(tip1)
            Spacer()
            Text("Counter value multiplied by 2: \(counter * 2)")
                .foregroundStyle(.tertiary)
                .popoverTip(tip2)
            Spacer()
        }
        .padding()
        .onReceive(timer) { _ in
            counter += 1
        }
    }
}

#Preview {
    ContentView()
}

方案2:手动通过TipManager管理提示顺序

import SwiftUI
import TipKit

@main
struct testbedApp: App {
    var body: some Scene {
        WindowGroup {
          ContentView()
        }
    }
  
    init() {
        try? Tips.configure()
    }
}

struct PopoverTip1: Tip {
    var title: Text {
        Text("Test title 1").foregroundStyle(.indigo)
    }

    var message: Text? {
        Text("Test message 1")
    }
}

struct PopoverTip2: Tip {
    var title: Text {
        Text("Test title 2").foregroundStyle(.indigo)
    }

    var message: Text? {
        Text("Test message 2")
    }
}

struct ContentView: View {
    private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var counter = 1
    @StateObject private var tipManager = TipManager.shared
    
    private let tip1 = PopoverTip1()
    private let tip2 = PopoverTip2()
    
    var body: some View {
        VStack(spacing: 20) {
            Spacer()
            Text("Counter value: \(counter)")
                .popoverTip(tip1)
            Spacer()
            Text("Counter value multiplied by 2: \(counter * 2)")
                .foregroundStyle(.tertiary)
                .popoverTip(tip2)
            Spacer()
        }
        .padding()
        .onReceive(timer) { _ in
            counter += 1
        }
        .onAppear {
            // 视图出现时显示第一个提示
            tipManager.showTip(tip1)
        }
        .onChange(of: tipManager.isDismissed(tip1)) { isDismissed in
            // 第一个提示关闭后,显示第二个
            if isDismissed {
                tipManager.showTip(tip2)
            }
        }
    }
}

#Preview {
    ContentView()
}

内容的提问来源于stack exchange,提问作者Arietis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 07:25:53