当某些异步代码需要一些时间运行时,我正试图找出如何向用户显示一条消息。到目前为止,我已经使用了我在网上找到的一个示例来创建一个弹出横幅,并在我的视图上使用异步方法的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线程上的值来修复这个问题,但是我希望这些方法异步运行并更新用户正在发生的事情。从这个位置更新我的变量的最好方法是什么?
代码
在主应用程序中:
var body: some Scene {
WindowGroup {
ContentView(asyncmethod: myAsyncViewModel())
}
}ContentView:
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))
}
}我的异步示例方法:
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"
}
}以及弹出窗口的支持文件:
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页面这里中找到。
发布于 2022-10-21 21:49:02
您可以用@MainActor注释可观察到的类或只注释函数,或者在为已发布的变量赋值时使用DispatchQueue.main.async。
https://stackoverflow.com/questions/74159031
复制相似问题