首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SwiftUI:从异步模型发布更改

SwiftUI:从异步模型发布更改
EN

Stack Overflow用户
提问于 2022-10-21 20:50:40
回答 1查看 59关注 0票数 0

当某些异步代码需要一些时间运行时,我正试图找出如何向用户显示一条消息。到目前为止,我已经使用了我在网上找到的一个示例来创建一个弹出横幅,并在我的视图上使用异步方法的ObservedObject将消息绑定在一起,然后从我的异步方法中发布值。

我的示例代码项目位于公共GitHub存储库这里上,我将将代码放在底部。

现在,在设置异步方法中的变量时,我遇到了一个问题:Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.解决方案在线似乎通过更新@mainActor线程上的值来修复这个问题,但是我希望这些方法异步运行并更新用户正在发生的事情。从这个位置更新我的变量的最好方法是什么?

代码

在主应用程序中:

代码语言:javascript
复制
var body: some Scene {
    WindowGroup {
        ContentView(asyncmethod: myAsyncViewModel())
    }
}

ContentView:

代码语言:javascript
复制
import SwiftUI

struct ContentView: View {
    @State private var isLoaderPresented = false
    @State private var isTopMessagePresented = false

    @ObservedObject var asyncmethod: myAsyncViewModel

    var body: some View {
        VStack {
            Spacer()
            Button( action: {
                    Task {
                        isTopMessagePresented = true
                        let response = await asyncmethod.thisMethodTakesTime()
                        // Want to return a string or object so I know what happens.
                        print("Response Loader: \(response ?? "no response")")
                        isTopMessagePresented = false
                    }
                },
                label: { Text("Run Top Banner Code") }
            )
            Spacer()
        }
        .foregroundColor(.black)
        .popup(isPresented: isTopMessagePresented, alignment: .top, direction: .top, content: {
            Snackbar(showForm: $isTopMessagePresented, asyncmethod: asyncmethod)
        })
    }
}

struct Snackbar: View {
    @Binding var showForm: Bool
    
    @ObservedObject var asyncmethod: myAsyncViewModel

    var body: some View {
        VStack {
            HStack() {
                Image(systemName: asyncmethod.imageName)
                    .resizable()
                    .aspectRatio(contentMode: ContentMode.fill)
                    .frame(width: 40, height: 40)
                Spacer()
                VStack(alignment: .center, spacing: 4) {
                    Text(asyncmethod.title)
                        .foregroundColor(.black)
                        .font(.headline)
                    
                    Text(asyncmethod.subTitle)
                        .font(.body)
                        .foregroundColor(.black)
                        .frame(maxWidth: .infinity)
                }
            }
            .frame(minWidth: 200)
        }
        .padding(15)
        .frame(maxWidth: .infinity, idealHeight: 100)
        .background(Color.black.opacity(0.1))
    }

}

我的异步示例方法:

代码语言:javascript
复制
import Foundation

class myAsyncViewModel:  ObservableObject  {
    @Published var imageName: String = "questionmark"
    @Published var title: String = "title"
    @Published var subTitle: String = "subtitle"
    
    func thisMethodTakesTime() async -> String? {
        print("In method: \(imageName), \(title), \(subTitle)")
        title = "MY METHOD"
        subTitle = "Starting out!"
        print("In method. Starting \(title)")
        subTitle = "This is the message"
        print("Sleeping")
        try? await Task.sleep(nanoseconds: 1_000_000_000)
        subTitle = "Between"
        try? await Task.sleep(nanoseconds: 1_000_000_000)
        print("After sleep. Ending")
        subTitle = "About to return. Success!"
        print("In method: \(imageName), \(title), \(subTitle)")
        return "RETURN RESULT"
    }
    
}

以及弹出窗口的支持文件:

代码语言:javascript
复制
import SwiftUI

struct Popup<T: View>: ViewModifier {
    let popup: T
    let isPresented: Bool
    let alignment: Alignment
    let direction: Direction

    // 1.
    init(isPresented: Bool, alignment: Alignment, direction: Direction, @ViewBuilder content: () -> T) {
        self.isPresented = isPresented
        self.alignment = alignment
        self.direction = direction
        popup = content()
    }

    // 2.
    func body(content: Content) -> some View {
        content
            .overlay(popupContent())
    }

    // 3.
    @ViewBuilder private func popupContent() -> some View {
        GeometryReader { geometry in
            if isPresented {
                withAnimation {
                    popup
                        .transition(.offset(x: 0, y: direction.offset(popupFrame: geometry.frame(in: .global))))
                        .frame(width: geometry.size.width, height: geometry.size.height, alignment: alignment)
                }
            }
        }
    }
}

extension Popup {
    enum Direction {
        case top, bottom

        func offset(popupFrame: CGRect) -> CGFloat {
            switch self {
            case .top:
                let aboveScreenEdge = -popupFrame.maxY
                return aboveScreenEdge
            case .bottom:
                let belowScreenEdge = UIScreen.main.bounds.height - popupFrame.minY
                return belowScreenEdge
            }
        }
    }
}

private extension GeometryProxy {
    var belowScreenEdge: CGFloat {
        UIScreen.main.bounds.height - frame(in: .global).minY
    }
}

extension View {
    func popup<T: View>(
        isPresented: Bool,
        alignment: Alignment = .center,
        direction: Popup<T>.Direction = .bottom,
        @ViewBuilder content: () -> T
    ) -> some View {
        return modifier(Popup(isPresented: isPresented, alignment: alignment, direction: direction, content: content))
    }
}

同样,所有这些都可以在我的GitHub页面这里中找到。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-10-21 21:49:02

您可以用@MainActor注释可观察到的类或只注释函数,或者在为已发布的变量赋值时使用DispatchQueue.main.async。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74159031

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档