4

I have a Picker with .pickerStyle(SegmentedPickerStyle()) to make it a segmented control. I want to make the pages smoothly swipe between, rather than replacing the view using a conditional statement.

Here is a gif of what I have made so far:

What I have made

Here is the code so far (controlled by an if, instead of switching between different pages):

struct AuthView: View {

    @State private var authPath = 0

    /* ... */

    var body: some View {
        VStack {
            Picker(selection: $authPath, label: Text("Authentication Path")) {
                Text("Log In").tag(0)
                Text("Sign Up").tag(1)
            }
            .pickerStyle(SegmentedPickerStyle())

            Spacer()

            if authPath == 0 {
                LogInView(/* ... */)
            } else {
                SignUpView(/* ... */)
            }

            Spacer()
        }
        .padding()
        .background(Color("Color.Background").edgesIgnoringSafeArea(.all))
    }
}

I want something similar to UIPageViewController. If there is a SwiftUI version or a good alternative, that would really help.

However, if I do need to resort to UIKit with UIViewRepresentable, I don't know how I would implement it with SwiftUI.

George
  • 25,988
  • 10
  • 79
  • 133

4 Answers4

4

I managed to achieve this by creating both Picker and TabView like this:

struct ContentView: View {
    @State private var selectedPage: Int = 0

    var body: some View {
        VStack {
            Picker("", selection: $selectedPage) {
                Text("First").tag(0)
                Text("Second").tag(1)
                Text("Third").tag(2)
            }.pickerStyle(.segmented)
                
            TabView(selection: $selectedPage) {
                FirstView().tag(0)
                    
                SecondView().tag(1)
                    
                ThirdView().tag(2)
            }.tabViewStyle(.page)
        }
    }
}

Both of the Picker and TabView share the same selection state so when the Picker get selected or the TabView get scrolled, the other changed as well.

cleanrun
  • 549
  • 6
  • 20
2

Here is possible approach (please note, for testing it needs to use Simulator, because Preview does not handle .move transitions correctly)

Demo (just used stub views, the parameters of animation and transition can be configured, but the idea remains the same).

Note: background of transitioning view should be opaque (here Color.white is used), otherwise transitions look... not good.

SwiftUI move view transition

struct TestTwoViewMoveIn: View {
    @State private var authPath: Int? = nil

    /* ... */

    var body: some View {
        VStack {
            Picker(selection: Binding<Int>(
                get: { self.authPath ?? 0 },
                set: { tag in
                    withAnimation { // needed explicit for transitions
                        self.authPath = tag
                    }
                }),
                   label: Text("Authentication Path")) {
                Text("Log In").tag(0)
                Text("Sign Up").tag(1)
            }
            .pickerStyle(SegmentedPickerStyle())

            Spacer()

            ZStack {
                Rectangle().fill(Color.clear)
                if nil == authPath {
                    LogInView(/* ... */)
                        .background(Color.white) // << set your background 
                }

                if authPath == 0 {
                    LogInView(/* ... */)
                        .background(Color.white) // << set your background 
                        .transition(.move(edge: .leading))
                }

                if authPath == 1 {
                    SignUpView(/* ... */)
                        .background(Color.white) // << set your background 
                        .transition(.move(edge: .trailing))
                }
            }

            Spacer()
        }
        .padding()
        .background(Color("Color.Background").edgesIgnoringSafeArea(.all))
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
0

You may wrap LoginView and SignupView inside some container to make it animate.

                 var body: some View {
        VStack {

            Picker(selection: $authPath, label: Text("Authentication Path")) {
              //  withAnimation(Animation.easeInOut.speed(0.5)){
                Text("Log In").tag(0)
                    Text("Sign Up").tag(1)}
                //}
            .pickerStyle(SegmentedPickerStyle())

            Spacer()


            if authPath == 0 {
               NavigationView{
                Text("1")
                }.animation(.default).transition(.move(edge: .leading))
                //(/* ... */)
            } else {
                NavigationView{
                Text("2")
                }.animation(.default).transition(.move(edge: .leading))

             //(/* ... */)

                }
            Spacer()
        }
        .padding()
        .background(Color("Color.Background").edgesIgnoringSafeArea(.all))
    }
E.Coms
  • 11,065
  • 2
  • 23
  • 35
0

These other answers pointed me in the right direction, but the code seems a bit verbose or didn't function as intended.

Here is what changed to get mine working:

  • Padding was added to the Picker.
  • Padding was removed from the end of the VStack.
  • The if-else was changed to 2 ifs.
  • Added animation, transition, and padding modifiers to each of LogInView and SignInView.

Original:

struct AuthView: View {

    @State private var authPath = 0

    /* ... */

    var body: some View {
        VStack {
            Picker(selection: $authPath, label: Text("Authentication Path")) {
                Text("Log In").tag(0)
                Text("Sign Up").tag(1)
            }
            .pickerStyle(SegmentedPickerStyle())

            Spacer()

            if authPath == 0 {
                LogInView(/* ... */)
            } else {
                SignUpView(/* ... */)
            }

            Spacer()
        }
        .padding()
        .background(Color("Color.Background").edgesIgnoringSafeArea(.all))
    }
}

New:

struct AuthView: View {

    @State private var authPath = 0

    /* ... */

    var body: some View {
        VStack {
            Picker(selection: $authPath, label: Text("Authentication Path")) {
                Text("Log In").tag(0)
                Text("Sign Up").tag(1)
            }
            .pickerStyle(SegmentedPickerStyle())
            .padding()

            Spacer()

            if authPath == 0 {
                LogInView(/* ... */)
                    .animation(.default)
                    .transition(.move(edge: .leading))
                    .padding()
            }
            if authPath == 1 {
                SignUpView(/* ... */)
                    .animation(.default)
                    .transition(.move(edge: .trailing))
                    .padding()
            }

            Spacer()
        }
        .background(Color("Color.Background").edgesIgnoringSafeArea(.all))
    }
}
George
  • 25,988
  • 10
  • 79
  • 133