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

Apple Watch上SwiftUI Sheet关闭后重复弹出问题求助

Apple Watch健身应用Sheet重复弹出问题

我正在开发一款Apple Watch健身应用,用户可在开始运动前选择强度。尝试在导航至实际运动视图前,以Sheet形式展示强度选择器,但遇到问题:关闭Sheet后它会立即重新弹出。使用环境为Xcode 14.2和watchOS 9.1。

相关代码

主视图(Sheet控制视图)

主视图通过workoutManager.showingZonePickerView控制Sheet的展示:

import SwiftUI

@main
struct Wise_Watch_AppApp: App {
    @StateObject private var workoutManager = WorkoutManager()

    @SceneBuilder var body: some Scene {
        WindowGroup {
            NavigationView {
                WorkoutView()
            }
            .sheet(isPresented: $workoutManager.showingZonePickerView, onDismiss: {
                workoutManager.showingZonePickerView = false
            }) {
                WorkoutLevelPickerView(total: 5, completed: 1)
                    .toolbar(.hidden)
            }
            .sheet(isPresented: $workoutManager.showingSummaryView) {
                SummaryView()
            }
            .environmentObject(workoutManager)
        }
    }
}

运动类型选择视图

用户选择偏好运动类型的视图,通过NavigationLink跳转到运动会话视图:

import SwiftUI
import HealthKit

struct WorkoutView: View {
    @EnvironmentObject var workoutManager: WorkoutManager
    
    @State var linkActive: Bool = false

    var workoutTypes: [HKWorkoutActivityType] = [.cycling, .running, .walking]
    var workoutDictionary: Dictionary<String, HKWorkoutActivityType> = [
        "figure.outdoor.cycle" : .cycling,
        "figure.run" : .running,
        "figure.walk" : .walking
    ]

    var body: some View {
        List(Array(workoutDictionary.keys), id: \.self) { workoutType in
            NavigationLink(
                destination: SessionPagingView(),
                tag: workoutDictionary[workoutType]!,
                selection: $workoutManager.selectedWorkout
            ) {
                Label("\(workoutDictionary[workoutType]!.name)", systemImage: workoutType)
                    .font(.subheadline)
                    .fontWeight(.semibold)
                    .padding()
            }
            .padding(EdgeInsets(top: 15, leading: 5, bottom: 15, trailing: 5))
        }
        .listStyle(.carousel)
        .navigationBarTitle("Workouts")
        .onAppear {
            workoutManager.requestAuthorization()
        }
    }
}

运动会话视图

点击NavigationLink后进入的视图,在onAppear中触发Sheet展示:

import SwiftUI
import WatchKit
import ConfettiSwiftUI

struct SessionPagingView: View {
    @EnvironmentObject var workoutManager: WorkoutManager
    @State private var selection: Tab = .metrics
    @State private var counter: Int = 0
    @State private var isViewHidden: Bool = true

    enum Tab {
        case metrics, nowPlaying, milestone
    }

    var body: some View {
        if(isViewHidden) {
            sessionView.hidden()
        } else {
            sessionView
        }
    }
    
    var sessionView: some View {
        TabView(selection: $selection) {
            ForEach(workoutManager.tabItems) { item in
                VStack {
                    Text("Congrats!")
                        .font(.title2)
                        .fontWeight(.bold)
                        .confettiCannon(counter: $counter, num: 40, radius: 200)
                    Text("You just reached")
                        .font(.subheadline)
                        .foregroundColor(.cyan)
                        .tag(Tab.milestone)
                    Text("\(item.value) km")
                        .font(.subheadline)
                        .foregroundColor(.cyan)
                        .tag(Tab.milestone)
                }
                
            }
            MetricsView().tag(Tab.metrics)
            NowPlayingView().tag(Tab.nowPlaying)
        }
        .navigationBarHidden(true)
        .navigationBarBackButtonHidden(true)
        .onAppear {
            workoutManager.showingZonePickerView = true
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                self.isViewHidden = false
            }
        }
        .onChange(of: workoutManager.running) { _ in
            displayMetricsView()
        }
        .onChange(of: workoutManager.tabItems) { _ in
            if(workoutManager.tabItems.count > 0) {
                displayMilestoneView()
                DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5), execute: {
                    displayMetricsView()
                })
            }
        }
        .ignoresSafeArea()
        .animation(.easeInOut, value: self.selection)
    }

    private func displayMetricsView() {
        withAnimation {
            selection = .metrics
        }
    }
    
    private func displayMilestoneView() {
        withAnimation {
            selection = .milestone
            WKInterfaceDevice.current().play(.notification)
            DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1), execute: {
                counter += 1
            })
        }
    }
}

强度选择Sheet视图

Sheet中展示的强度选择器视图,通过dismiss()关闭Sheet:

import SwiftUI

struct WorkoutLevelPickerView: View {
    @EnvironmentObject var workoutManager: WorkoutManager
    @Environment(\.dismiss) private var dismiss
    
    let total: Int
    
    @State var lineWidth: CGFloat = 16
    @State var color: Color = .green
    @State var completed: Double = 1.0
    @State var currentZone: HeartRateZone = zones[0]
    @State var isScrolling: Bool = false
    
    var body: some View {
        VStack {
            ZStack {
                CircleLabelView(
                    radius: 30,
                    tracking: 0,
                    size: .init(width: 120, height: 120),
                    text: currentZone.intensity.uppercased()
                )
                .font(.headline)
                .frame(width: 50, height: 50)
                .rotationEffect(Angle(degrees: Double(currentZone.labelRotationAngleModifier * currentZone.intensity.count / 2)))
                .opacity(isScrolling ? 0 : 1)
                .animation(.easeInOut, value: isScrolling)
                
                WorkoutLevelPickerBackgroundView(total: total, lineWidth: lineWidth)
                withAnimation(.spring()) {
                    WorkoutLevelPickerProgressView(total: total, completed: Int(completed), lineWidth: lineWidth, zone: currentZone)
                }
                VStack {                    
                    Button {
                        dismiss()
                    } label: {
                        Image(systemName: currentZone.iconName)
                            .padding()
                            .font(.title2)
                    }
                    .frame(width: 70, height: 70)
                }
            }
            .frame(width: 150, height: 150)
            .focusable()
            .digitalCrownRotation(
                detent: $completed.animation(.spring()),
                from: 1.0,
                through: 5.0,
                by: 1.0,
                sensitivity: .low,
                isContinuous: false,
                isHapticFeedbackEnabled: true,
                onChange: { _ in
                    isScrolling = true
                },
                onIdle: {
                    isScrolling = false
                }
            )
            .digitalCrownAccessory(.hidden)
            .onChange(of: completed) {_ in
                if(Int(completed) != currentZone.id) {
                    currentZone = zones[Int(completed) - 1]
                    print(currentZone.tint.description)
                }
            }
        }
        
    }
}

问题演示

模拟器中的问题演示

已尝试的解决方法

  • 从其他视图展示Sheet
  • 通过绑定而非dismiss()动作关闭Sheet

两种方式均未解决问题,Sheet仍会在关闭后立即重新弹出。

补充说明:当Sheet弹出时,按侧边按钮可关闭它,应用会正常工作,直到用户尝试开始新运动。

内容的提问来源于stack exchange,提问作者Vlad Boancă

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 07:05:24