0

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:

recording

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?

Frankenstein
  • 15,732
  • 4
  • 22
  • 47
  • 2
    The fact that you initially show view with offset off-screen does not mean that it does not appear. You need instead to use `.transition` based approach, in that case view really appeared/disappeared. For example see https://stackoverflow.com/a/60785822/12299030 or https://stackoverflow.com/a/63782921/12299030 or https://stackoverflow.com/a/59087574/12299030 – Asperi Nov 10 '20 at 06:54
  • @Asperi `transition` seems to be the right approach here. Although when combining the transitions of child views the parent transition seems to be inactive(Login to Home). Is there a way to fix this? I've added the transition approach as you've stated below. – Frankenstein Nov 10 '20 at 09:00

2 Answers2

0

The issue about fetching solved! see the codes, I can update the animations or other small things! it is just fast coding for you.

enter image description here

import SwiftUI




struct ContentView: View {
    
    @State var isUserLoggedIn: Bool = false
    
    var body: some View {
        
        
        
        Group
        {
            if isUserLoggedIn
            {
                Home()
            }
            else
            {
                LoginView(isUserLoggedIn: $isUserLoggedIn)
            }
        }
        
        
        
    }
}




struct LoginView: View {
    
    @Binding var isUserLoggedIn: Bool
    
    var body: some View {
        
        
        
        Button("Login") { withAnimation(.easeInOut(duration: 1.0)) { isUserLoggedIn.toggle(); print("Successful Login!") } }.font(Font.largeTitle).padding()
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.red)
            .edgesIgnoringSafeArea(.all)
            .offset(y: isUserLoggedIn ? UIScreen.main.bounds.height : 0)
            .onAppear { print("Login appeared!") }
        
        
    }
}

enum appPage {
    case One
    case Two
    case Home
}



struct Home: View {
    
    
    
    @State var currentPage: appPage = appPage.Home
    @State var NavigationViewTitle: String = "Home"
    
    var body: some View {
        
        
        
        
        
        
        
        VStack
        {
            HStack {Text(NavigationViewTitle).font(Font.largeTitle).padding(); Spacer() }.padding()
            
            if      currentPage == appPage.One
            {
                OneView(currentPage: $currentPage, NavigationViewTitle: $NavigationViewTitle)
            }
            else if currentPage == appPage.Two
            {
                TwoView(currentPage: $currentPage, NavigationViewTitle: $NavigationViewTitle)
            }
            else if currentPage == appPage.Home
            {
                
                
                Button(action: {
                    
                    withAnimation { withAnimation(.spring(response: 1, dampingFraction: 0.4, blendDuration: 1)  ) {currentPage = appPage.One} }
                }){
                    HStack {Text("Goto One"); Image(systemName:"arrow.right")}
                }.font(Font.largeTitle).padding()
                
                
                Button(action: {
                    withAnimation { withAnimation(.spring(response: 1, dampingFraction: 0.4, blendDuration: 1)  ) {currentPage = appPage.Two} }
                }){
                    HStack {Text("Goto Two"); Image(systemName:"arrow.right")}
                }.font(Font.largeTitle).padding()
                
                
                
                
            }
            
            
            
            
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.yellow)
        .edgesIgnoringSafeArea(.all)
        .onAppear { print("Home appeared!") }
        .onChange(of: currentPage) { _ in
            
            
            if      currentPage == appPage.One
            {
                NavigationViewTitle = "View One"
            }
            else
            {
                NavigationViewTitle = "View Two"
            }
        }
        
        
    }
}





struct OneView: View {
    
    
    
    @Binding var currentPage: appPage
    @Binding var NavigationViewTitle: String
    
    var body: some View {
        
        
        
        VStack
        {
            
            Button(action: {
                
                withAnimation { withAnimation(.spring(response: 1, dampingFraction: 0.4, blendDuration: 1)  ) {currentPage = appPage.Two} }
            }){
                HStack {Text("Back to Two"); Image(systemName:"arrow.right")}
            }.font(Font.largeTitle).padding()
            
            
            
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.orange)
        .edgesIgnoringSafeArea(.all)
        .cornerRadius(30)
        .offset(x: currentPage == .Two ? UIScreen.main.bounds.width : 0)
        .onAppear { print("OneView appeared! Fetch data...") }
        
    }
}



struct TwoView: View {
    
    
    
    @Binding var currentPage: appPage
    @Binding var NavigationViewTitle: String
    
    var body: some View {
        
        
        
        
        VStack
        {
            
            Button(action: {
                
                withAnimation { withAnimation(.spring(response: 1, dampingFraction: 0.4, blendDuration: 1)  ) {currentPage = appPage.One} }
                
            }){
                HStack {Text("Back to One"); Image(systemName:"arrow.right")}
            }.font(Font.largeTitle).padding()
            
            
            
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.green)
        .edgesIgnoringSafeArea(.all)
        .cornerRadius(30)
        .offset(x: currentPage == .One ? UIScreen.main.bounds.width : 0)
        .onAppear { print("TwoView appeared! Fetch data...") }
        
    }
}
ios coder
  • 1
  • 4
  • 31
  • 91
0

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)))
            }
        }
    }
}
Frankenstein
  • 15,732
  • 4
  • 22
  • 47