I've been working on a project in SwiftUI 1.0 because it requires support for iOS 13.0 devices (So no fullScreenCover
available). I require my application to have clean transition animations between my views, so I'm using GeometryReader
extensively throughout my app, as follows:
Right now I've completed 80% of my UI and I'm slightly trying to integrate the data fetching part. So, what I did was to call the data fetching methods in my onAppear
method on my views. Here is my code:
struct ContentView: View {
@State private var isUserLoggedIn = false
var body: some View {
GeometryReader { geometry in
HomeView()
LoginView(isUserLoggedIn: $isUserLoggedIn)
.offset(y: isUserLoggedIn ? geometry.size.height + geometry.safeAreaInsets.bottom : 0)
}
}
}
struct LoginView: View {
@Binding var isUserLoggedIn: Bool
var body: some View {
Button("Login") {
withAnimation {
isUserLoggedIn.toggle()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.edgesIgnoringSafeArea(.all)
.onAppear {
print("Login appeared!")
}
}
}
struct HomeView: View {
@State private var isTwoSelected = false
var body: some View {
GeometryReader { geometry in
OneView(isTwoSelected: $isTwoSelected)
.offset(x: isTwoSelected ? -geometry.size.width : 0)
TwoView(isTwoSelected: $isTwoSelected)
.offset(x: isTwoSelected ? 0 : geometry.size.width)
}
}
}
struct OneView: View {
@Binding var isTwoSelected: Bool
var body: some View {
NavigationView {
Button("Goto Two") {
withAnimation {
isTwoSelected.toggle()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.green)
.edgesIgnoringSafeArea(.all)
.navigationBarTitle(Text("One"))
.onAppear {
print("One appeared! Fetch data...")
}
}
}
}
struct TwoView: View {
@Binding var isTwoSelected: Bool
var body: some View {
NavigationView {
Button("Goto One") {
withAnimation {
isTwoSelected.toggle()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.yellow)
.edgesIgnoringSafeArea(.all)
.navigationBarTitle(Text("Two"))
.onAppear {
print("Two appeared! Fetch data...")
}
}
}
}
But the issue that I face now is that they are triggered all at once. You can see that the console prints as follows:
Login appeared!
Two appeared! Fetch data...
One appeared! Fetch data...
How would I go about fetching the data to the corresponding views only while they appear in the view frame of the device?
As stated by @Asperi in the comments, transition
seems to be the right way to proceed here instead of offset
. Here's the implementation with transition
(only adding views that have been changed):
struct ContentView: View {
@State private var isUserLoggedIn = false
var body: some View {
if isUserLoggedIn {
HomeView()
.transition(.asymmetric(insertion: .move(edge: .top), removal: .move(edge: .bottom)))
} else {
LoginView(isUserLoggedIn: $isUserLoggedIn)
.transition(.asymmetric(insertion: .move(edge: .top), removal: .move(edge: .bottom)))
}
}
}
struct HomeView: View {
@State private var isTwoSelected = false
var body: some View {
Group {
if !isTwoSelected {
OneView(isTwoSelected: $isTwoSelected)
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing)))
} else {
TwoView(isTwoSelected: $isTwoSelected)
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))
}
}
}
}