swift 从didSet中调用didc函数

ldfqzlk8  于 5个月前  发布在  Swift
关注(0)|答案(3)|浏览(70)

当文本框的内容发生变化时,我试图运行一些javascript/await代码,以从后端服务器获取一些结果并更新UI。
文本框在Swift中使用了这个searchTerm published属性,但在didSet中调用这个xlc方法会锁定UI。

//This is used to populate a list
@Published var results : ResultsData = ResultsData()

//This is used as a binding for a TextField
@Published var searchTerm: String = "a search term"{
    didSet {
        Task {                       
            self.results = await searchService.GetData(searchTerm: searchTerm)             
        }               
    }             
}

字符串
我试过使用await MainActor.run{resultsstatic版本,甚至是全局变量来更新UI,但它没有工作,只要我在文本字段中键入一些东西,UI就会继续冻结。
如何在用户每次在文本字段中输入字符时调用这个fixc方法从后端获取结果?

cgfeq70w

cgfeq70w1#

这里有两个主题:
1.看起来'var results'是您的'searchTerm'状态的函数。
将用于定义状态的特性标记为“@Published,"并将结果特性定义为函数或计算特性。
如果您发现自己设置了一个@Published属性,结果却设置了另一个,那么您可能应该重新考虑当前的模型。
请查看WWDC关于SwiftUI状态管理的讨论,了解此主题。
1.异步演示文稿处理
您当前“触发并忘记”了您得Task,这意味着您无法跟踪进度,保证完成顺序,去抖动,取消等.理想得解决方案可能是使用合并或Task handling(请参见Async search bar)来正确处理所有这些细节.

1和#2都可能导致您正在经历的那种UI冻结。

这是一个示例解决方案(没有额外的功能,如去抖动)。

import SwiftUI

class ResultViewModel: ObservableObject {
    @Published var searchTerm: String = "a search term"
    
    func getSearchResult() async -> String {
        try? await Task.sleep(for: .milliseconds(500))
        return "result for \(searchTerm)"
    }
}

struct ResultView: View {
    @StateObject private var viewModel = ResultViewModel()
    
    var body: some View {
        VStack {
            TextField("search", text: $viewModel.searchTerm)
            AsyncViewExample(loadData: viewModel.getSearchResult) {
                Text($0)
            }
            .id(viewModel.searchTerm)
        }
    }
}

fileprivate struct AsyncViewExample<AsyncData, Content: View>: View {
    let loadData: () async -> AsyncData
    let wrappedAsyncView:  (AsyncData) -> Content
    
    @State private var loadedData: AsyncData?
    
    var body: some View {
        if let loadedData {
            wrappedAsyncView(loadedData)
        } else {
            ProgressView()
                .task {
                    loadedData = await loadData()
                }
        }
    }
}

#Preview {
    ResultView()
}

字符串

pqwbnv8z

pqwbnv8z2#

像下面这样使用带有id的任务

@MainActor
class ViewModel: ObservableObject {
    @Published var searchTerm: String = "Xyz"
    @Published var result: <YOUR TYPE>
    
    func getSearchResult() async -> String {
        // Make api call
        self.result = <YOUR RESULT>
    }
}

struct ResultView: View {
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            Text("Type something")
            TextField("search", text: $viewModel.searchTerm)
        }
        // Observe searchTerm
        .task(id: viewModel.searchTerm) {
            // On change of search term wait 300 milliseconds
            try? await Task.sleep(for: .milliseconds(300))
            // Then check task cancelation & make api call
            if !Task.isCancelled {
                await viewModel.getSearchResult()
            }
        }
    }
}

字符串

nafvub8i

nafvub8i3#

Roy Rodney的回答让我找到了一个解决方案,包括去抖动,所有这些都在同一个SwiftUI文件中,而不需要ViewModel或发布的属性(并不是说使用这些有什么错)
下面是我如何实现我的解决方案,以便在每次用户在文本字段中输入键时调用我的pwdc方法。

TextField(
                    "Enter term here",
                    text: $searchTerm
                ).onChange(of: searchTerm) { searchTerm in
                        searchTextPublisher.send(searchTerm)
                    }
                    .onReceive(
                        searchTextPublisher
                            .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
                    ) { debouncedSearchText in
                        Task{
                            await myAsyncFunction(newValue: debouncedSearchText)
                        }
                    }

字符串
这是在同一个SwiftUI文件中声明的searchTextPublisher

let searchTextPublisher = PassthroughSubject<String, Never>()

相关问题