SwiftUI - 创建弹出窗口,在执行长代码时显示更新进度消息

SwiftUI - Create popup that displays an updating progress message when executing long code

我正在创建一个应用程序,其中包含一些需要执行一些代码的代码。我希望它挂起应用程序,因为我不希望用户在执行时进行任何更改。这是一个多平台应用程序,因此在 macOS 中执行时,它会自动将鼠标更改为滚动球图像,以便正常工作。在 iOS 中根本没有反馈。我想创建一个弹出窗口(思考警报但不太挑剔),显示一条更新消息,向用户显示正在发生的事情,并明确表示他们必须等待。

现在我的视图调用一个 class 来执行代码,所以我想以某种方式传递一个在 class 中更新但在视图中实时可见的变量。理想情况下,我希望能够使用它并每次从其他视图调用不同的方法,但仍然使用带有消息的弹出窗口在代码执行时更新用户。

为了简化这一点,我创建了一个迷你项目,但我无法让它在 macOS 或 iOS 上运行,因为视图(应用程序)直到代码执行完毕后才会更新(也有打印语句以了解发生了什么)。我一直在尝试 @StateObject@PublishedObservableObject 等无济于事。

代码:ContentView.swift

import SwiftUI
import CoreData

struct ContentView: View {

@StateObject var progress = myProgress()
@State var showProgress:Bool = false

var body: some View {
    NavigationView {
        VStack {
            Button(action: {
                print("Pressed Button 1")
                showProgress = true
            }, label: {
                Text("Execute Option")
            })
            .sheet(isPresented: $showProgress, content: {
                Text(progress.message)
                    .onAppear() {
                        Task {
                            let longMethod = longMethodsCalled(currProgress: progress)
                            print("About to call method - \(progress.message)")
                            let error = longMethod.exampleOne(title: "My Passed In Title")
                            // Here I can use the returned value if it's an object or now if it passed if it's String?
                            print("Error: \(error ?? "No error")")
                            print("after printing error - \(progress.message)")
                        }
                        // If this is commented out it just shows the last message after code finishes
                        showProgress = false
                    }
            })
            
        }
    }
    
}

}

其他文件:longMethodsCalled.swift

import Foundation
import SwiftUI

class myProgress: ObservableObject {
@Published var message = "progressing..."

func changeMessage(newMessage:String) {
    message = newMessage
    print("Message changing. Now: \(message)")
    self.objectWillChange.send()
}
}

class longMethodsCalled {

@State var progress: myProgress

init(currProgress:myProgress) {
    progress = currProgress
}

public func exampleOne(title:String) -> String? {
    print("in example one - \(title)")
    progress.changeMessage(newMessage: "Starting example one")
    sleep(1)
    print("after first sleep")
    progress.changeMessage(newMessage: "Middle of example one")
    sleep(1)
    progress.changeMessage(newMessage: "About to return - example one")
    return "result of example one"
}
}

我想我想知道这是否可能?如果是这样,我该怎么做。我不知道我是快吃午饭了还是完全没吃午饭了。我会真的喜欢一种在我的代码执行时更新我的​​用户的方法。

感谢所有帮助。

这是一个使用绑定在另一个结构中执行所有视图更新的示例。它正在使用异步和等待。对于sleep(),它使用不锁定队列的Task.sleep

struct LongMethodCallMessage: View {
    
    @State var showProgress:Bool = false
    @State var progressViewMessage: String = "will do something"
    
    var body: some View {
        NavigationView {
            VStack {
                Button(action: {
                    print("Pressed Button 1")
                    progressViewMessage = "Pressed Button 1"
                    showProgress = true
                }, label: {
                    // text will be return value
                    // so one can see that it ran
                    Text(progressViewMessage)
                })
                    .sheet(isPresented: $showProgress, content: {
                        // create the vue that will display the progress
                        TheLongTaskView(progressViewMessage: $progressViewMessage, showProgress: $showProgress)
                    })
            }
        }
    }
}

struct TheLongTaskView: View, LongMethodsCalledMessage {
    @Binding var progressViewMessage: String
    @Binding var showProgress: Bool
    
    var body: some View {
        Text(progressViewMessage)
            .onAppear() {
                // create the task setting this as delegate
                // to receive message update
                Task {
                    let longMethod = LongMethodsCalled(delegate: self)
                    print("About to call method - \(progressViewMessage)")
                    let error = await longMethod.exampleOne(title: "My Passed In Title")
                    // Here I can use the returned value if it's an object or now if it passed if it's String?
                    print("Error: \(error ?? "No error")")
                    print("after printing error - \(progressViewMessage)")
                    // save the error message and close view
                    progressViewMessage = error!
                    showProgress = false
                }
            }
    }
    
    // updating the text
    func changeMessage(newMessage:String) {
        print("changeMessage: \(newMessage)")
        progressViewMessage = newMessage
    }
}

// a protocol to update the text in the long running task
protocol LongMethodsCalledMessage {
    func changeMessage(newMessage:String)
}

class LongMethodsCalled {
    var delegate: LongMethodsCalledMessage
    
    init(delegate: LongMethodsCalledMessage) {
        self.delegate = delegate
    }
    
    // make the method async
    public func exampleOne(title:String) async -> String? {
        print("in example one - \(title)")
        self.delegate.changeMessage(newMessage: "Starting example one")
        // this wait enable the text to update
        // the sleep() may lock and prevent main queue to run
        try! await Task.sleep(nanoseconds: 2_000_000_000)
        print("after first sleep")
        self.delegate.changeMessage(newMessage: "Middle of example one")
        try! await Task.sleep(nanoseconds: 2_000_000_000)
        print("after second sleep")
        self.delegate.changeMessage(newMessage: "About to return - example one")
        return "result of example one"
    }
}