There are views in a ListView. The views have the transition modifiers on them but no transition occurs. The List is made to appear first before its chid views are rendered. Here's some code to illustrate the problem:
The StateObject:
class TripsStore: ObservableObject {
@Published private(set) var trips: [Trip] = []
@MainActor
func getTrips() async throws { ... }
}
The View
struct TripsStore: View {
@StateObject private var tripsStore: TripsStore = TripsStore()
@State private var progress: RequestStatus = .idle
@State private var availableTrips: Set<UUID?> = []
var body: some View {
NavigationStack {
Group {
if progress == .loading {
ProgressView()
} else {
List(tripsStore.trips) { trip in
if availableTrips.contains(trip.id) {
TripCard(trip: trip)
.transition(.asymmetric(insertion: .opacity, removal: .scale).animation(.easeInOut(duration: 3.5)))
}
}
.onAppear {
withAnimation {
tripsStore.trips.forEach { trip in
availableTrips.insert(trip.id)
}
}
}
}
}
.task {
progress = .loading
do {
try await tripsStore.getTrips()
} catch {
print(error)
}
progress = .idle
}
}
}
}
By using a GroupView, trip data is fetched and List is only rendered when that data is available. The appearing of each trip card is animated when the list is about to appear using the .onAppear modifier. Checking if a trip is available, should "insert" the trip's card into the already appeared ListView. During insertion, the transition ought to occur. Unfortunately, it's not happening. Does anyone know why? How can it be fixed?
Post
Replies
Boosts
Views
Activity
I'm trying to use the refreshable modifier on a Scrollview in an app that targets iOS 16. But the asynchronus task gets cancelled during the pull to refresh gesture.
It was tested on a physical device.
Here is some code that demonstrates the problem and an image with the error printed:
ExploreViewModel.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()
}
}
Content.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()
}
}
}
}
ExploreView.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")
}
}
}
Printed Error