Xcode Canvas 异步任务为何不执行,只在模拟器中工作?

Xcode 的 canvas 异步任务await BottomCustomPopup().present()不执行,要在 simulator 中才执行

struct PartnerProfileView: View {
   @State private var showSheet: Bool = true
   let partnerName: String
   
   var body: some View {
      ZStack(alignment: .bottom) {
         Color("EFEFF4_0F2534")
         
         ScrollView(showsIndicators: false) {
            headerSection()
            
            infoSection()
               .padding(.bottom, 5)
            
            sectionTitle("Other Skills")
            
            skillsGrid()
            
            sectionTitle("···")
            
            dynamicsSection()
         }
         .frame(maxWidth: .infinity, maxHeight: .infinity)
         
         CustomTabBar()
      }
      .navigationBarHidden(true)
      .ignoresSafeArea(edges: .all)
      .onAppear {
         print("PartnerProfileView received")
         Task {
            await BottomCustomPopup().present()
         }
      }
      .onChange(of: showSheet) { oldState, newValue in
         print("oldState \(oldState)")
         print("newValue \(newValue)")
      }
   }
}

//@MainActor
struct BottomCustomPopup: BottomPopup {
    var body: some View {
        HStack(spacing: 0) {
            Text("Hello World")
            Spacer()
            Button(action: { Task { await dismissLastPopup() }}) { Text("Dismiss") }
        }
        .padding(.vertical, 20)
        .padding(.leading, 24)
        .padding(.trailing, 16)
    }
}
阅读 512
avatarAI BotBETA
### 可能的原因及解决方案

#### 原因
1. **Canvas 限制**:Xcode 的 Canvas 在处理某些视图和异步操作时,可能存在限制或 bug,特别是在处理模态视图(如弹窗)时。Canvas 可能没有完全模拟出真实设备的所有行为,特别是在处理视图层次、动画和异步任务时。

2. **@MainActor 缺失**:虽然你的 `BottomCustomPopup` 结构体没有使用 `@MainActor` 修饰符,但在 SwiftUI 中,特别是在涉及异步操作时,使用 `@MainActor` 可以确保操作在正确的线程上执行。不过,由于 `BottomCustomPopup` 看起来是一个视图,通常不需要 `@MainActor`,除非它包含复杂的异步逻辑。

3. **模拟器与真机的差异**:模拟器通常更接近于一个理想的运行环境,而真机可能受到硬件、操作系统版本、后台进程等多种因素的影响,导致行为差异。

#### 解决方案
1. **测试在不同设备上的表现**:尝试在不同的真机上运行你的应用,看看问题是否仍然存在。这有助于确定问题是否与特定的设备或 iOS 版本相关。

2. **更新 Xcode 和 iOS**:确保你的 Xcode 和 iOS 设备都更新到最新版本。苹果可能会在新版本中修复与 Canvas 相关的 bug。

3. **使用 `.sheet` 修饰符**:如果你的目的是显示一个模态视图,考虑使用 SwiftUI 提供的 `.sheet` 修饰符,而不是自定义的弹窗逻辑。`.sheet` 修饰符在 Canvas 和真机上通常都能正常工作。

4. **调试和日志**:在 `BottomCustomPopup.present()` 调用前后添加更多的日志输出,以帮助确定代码的执行路径。此外,检查是否有任何错误或警告被输出到控制台。

5. **简化代码**:尝试简化 `BottomCustomPopup` 的代码,只保留最基本的元素,看看问题是否仍然存在。这有助于确定问题是否与特定的视图或逻辑相关。

6. **联系苹果支持**:如果问题仍然无法解决,考虑向苹果开发者支持寻求帮助。提供详细的复现步骤和代码示例,以便他们能更快地定位问题。
1 个回答

使用条件编译来区分 Canvas 预览和实际运行环境。例如,你可以在 onAppear 中添加一个条件检查,确保异步任务只在非 Canvas 环境中执行:

.onAppear {
    print("PartnerProfileView received")
    #if !targetEnvironment(simulator)
    Task {
        await BottomCustomPopup().present()
    }
    #endif
}

或者

  1. 添加环境变量来识别是否在预览环境中
  2. 在预览环境中使用同步方式显示弹窗
  3. 在实际运行环境中保持原有的异步调用方式
// 添加这个预览 Provider
struct PreviewContextProvider: PreviewProvider {
    static var previews: some View {
        PartnerProfileView(partnerName: "Test Partner")
            .previewLayout(.sizeThatFits)
            // 关键是添加这个 modifier
            .environment(\.isPreview, true)
    }
}

// 添加这个环境值 key
struct IsPreviewKey: EnvironmentKey {
    static let defaultValue: Bool = false
}

extension EnvironmentValues {
    var isPreview: Bool {
        get { self[IsPreviewKey.self] }
        set { self[IsPreviewKey.self] = newValue }
    }
}

// 修改原来的 PartnerProfileView
struct PartnerProfileView: View {
    @State private var showSheet: Bool = true
    @Environment(\.isPreview) private var isPreview
    let partnerName: String
    
    var body: some View {
        ZStack(alignment: .bottom) {
            // ... 其他代码保持不变 ...
        }
        .navigationBarHidden(true)
        .ignoresSafeArea(edges: .all)
        .onAppear {
            print("PartnerProfileView received")
            if isPreview {
                // 在预览环境中直接显示弹窗
                BottomCustomPopup().showInPreview()
            } else {
                // 在真实环境中使用异步方式
                Task {
                    await BottomCustomPopup().present()
                }
            }
        }
        .onChange(of: showSheet) { oldState, newValue in
            print("oldState \(oldState)")
            print("newValue \(newValue)")
        }
    }
}

// 扩展 BottomCustomPopup 添加预览支持
extension BottomCustomPopup {
    func showInPreview() {
        // 实现预览环境下的直接显示逻辑
        // 这里可以根据你的 BottomPopup 协议具体实现来调整
    }
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏