Job*_*e J 8 scrollview ios swiftui
我正在尝试在面向 iOS 16 的应用程序中的 Scrollview 上使用可刷新修饰符。但是,异步任务在拉动刷新手势期间被取消。
这是一些代码和附加的视频,演示了问题以及带有打印错误的图像:
探索ViemModel.swift
class ExploreViewModel: ObservableObject {
@Published var randomQuotes: [Quote] = []
init() {
Task {
await loadQuotes()
}
}
@MainActor
func loadQuotes() async {
let quotesURL = URL(string: "https://type.fit/api/quotes")!
do {
let (data, urlResponse) = try await URLSession.shared.data(from: quotesURL)
guard let response = urlResponse as? HTTPURLResponse else { print("no response"); return}
if response.statusCode == 200 {
let quotes = try JSONDecoder().decode([Quote].self, from: data)
randomQuotes.append(contentsOf: quotes)
}
} catch {
debugPrint(error)
debugPrint(error.localizedDescription)
}
}
func clearQuotes() {
randomQuotes.removeAll()
}
}
Run Code Online (Sandbox Code Playgroud)
内容视图.swift
import SwiftUI
struct ContentView: View {
@StateObject private var exploreVM = ExploreViewModel()
var body: some View {
NavigationStack {
ExploreView()
.environmentObject(exploreVM)
.refreshable {
exploreVM.clearQuotes()
await exploreVM.loadQuotes()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
探索.swift
import SwiftUI
struct ExploreView: View {
@EnvironmentObject var exploreVM: ExploreViewModel
var body: some View {
ScrollView {
VStack {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 140.0), spacing: 24.0)], spacing: 24.0) {
ForEach(exploreVM.randomQuotes) { quote in
VStack(alignment: .leading) {
Text("\(quote.text ?? "No Text")")
.font(.headline)
Text("\(quote.author ?? "No Author")")
.font(.caption)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 144.0)
.border(Color.red, width: 2.0)
}
}
}
.padding()
.navigationTitle("Explore")
}
}
}
Run Code Online (Sandbox Code Playgroud)
lor*_*sum 11
当您调用时,exploreVM.clearQuotes()
您会body
在数组被清除时重新绘制。
.refreshable
也会被重绘,因此之前正在使用的“任务”被取消。
这就是 SwiftUI 的本质。
有几种方法可以克服这个问题,最简单的方法是使用id
.
选项1
struct ExploreParentView: View {
@StateObject private var exploreVM = ExploreViewModel()
//@State can survive reloads on the `View`
@State private var taskId: UUID = .init()
var body: some View {
NavigationStack {
ExploreView()
.refreshable {
print("refreshable")
//Cause .task to re-run by changing the id.
taskId = .init()
}
//Runs when the view is first loaded and when the id changes.
//Task is perserved while the id is preserved.
.task(id: taskId) {
print("task \(taskId)")
exploreVM.clearQuotes()
await exploreVM.loadQuotes()
}
}.environmentObject(exploreVM)
}
}
Run Code Online (Sandbox Code Playgroud)
如果您使用上述方法,您应该Task
删除.init
ExploreViewModel
选项2
另一种方法是在 url 调用返回之前防止重新绘制。
class ExploreViewModel: ObservableObject {
//Remove @Published
var randomQuotes: [Quote] = []
init() {
//Floading Task that isn't needed for option 1
Task {
await loadQuotes()
}
}
@MainActor
func loadQuotes() async {
let quotesURL = URL(string: "https://type.fit/api/quotes")!
do {
let (data, urlResponse) = try await URLSession.shared.data(from: quotesURL)
guard let response = urlResponse as? HTTPURLResponse else { print("no response"); return}
if response.statusCode == 200 {
let quotes = try JSONDecoder().decode([Quote].self, from: data)
randomQuotes.append(contentsOf: quotes)
print("updated")
}
} catch {
debugPrint(error)
debugPrint(error.localizedDescription)
}
print("done")
//Tell the View to redraw
objectWillChange.send()
}
func clearQuotes() {
randomQuotes.removeAll()
}
}
Run Code Online (Sandbox Code Playgroud)
选项3
就是等到有响应才去改变数组。
class ExploreViewModel: ObservableObject {
@Published var randomQuotes: [Quote] = []
init() {
Task {
await loadQuotes()
}
}
@MainActor
func loadQuotes() async {
let quotesURL = URL(string: "https://type.fit/api/quotes")!
do {
let (data, urlResponse) = try await URLSession.shared.data(from: quotesURL)
guard let response = urlResponse as? HTTPURLResponse else { print("no response"); return}
if response.statusCode == 200 {
let quotes = try JSONDecoder().decode([Quote].self, from: data)
//Replace array
randomQuotes = quotes
print("updated")
}
} catch {
//Clear array
clearQuotes()
debugPrint(error)
debugPrint(error.localizedDescription)
}
print("done")
}
func clearQuotes() {
randomQuotes.removeAll()
}
}
Run Code Online (Sandbox Code Playgroud)
选项 1 更能抵抗取消,对于短线呼叫来说是可以的。它不会等待调用返回来关闭 ProgressView。
选项 2 在 ViewModel 内提供更多控制,但视图仍然可以由其他人重绘。
选项 3 很可能是 Apple 设想的流程,但也容易受到其他重绘的影响。
我刚刚也遇到了这个问题,我不确定为什么会.refreshable
这样。我的解决方案是将逻辑包装在.refreshable {}
一个新任务中,然后等待结果。
.refreshable {
await Task {
exploreVM.clearQuotes()
await exploreVM.loadQuotes()
}.value
}
Run Code Online (Sandbox Code Playgroud)
这样,即使可刷新任务被取消,也无法取消新任务,因此新任务将运行直到完成。
这样做的一个好处是刷新旋转器应该显示直到新任务完成。
归档时间: |
|
查看次数: |
1549 次 |
最近记录: |