swiftswiftuiuisegmentedcontrolsegmentswipe-gesture

How to make a swipeable segmented control with custom view in SwiftUI?


I have a segmented control with two segments as shown in the image see image here. I want to enable the user to switch between the two segments by swiping right or left, and display the corresponding views accordingly. I have looked through several articles, but I have not found a solution to implement this functionality.

I used buttons to implement this feature, but I'm unsure how to enable swipe functionality.

  1. How to implement a left or right DragGesture() that trigger a switch case in SwiftUI?
  2. How to detect Swiping UP, DOWN, LEFT and RIGHT with SwiftUI on a View
struct ScheduleView: View {
    // MARK: - Variables
    @State private var playerName: String = ""
    @State private var pastShown: Bool = false
    @State private var presentAlert: Bool = false
    @State private var goToGroupChat: Bool = false
    private var items = ["Upcoming", "Past"]
    
    var body: some View {
        NavigationView {
            ZStack {
                VStack {
                    HStack {
                        VStack(alignment: .leading) {
                            Text("Hello!,")
                                .robotoRegularFont(size: 14)
                                .foregroundColor(Color.custom64B054Color)
                            Text("John Andrew")
                                .robotoRegularFont(size: 14)
                                .foregroundColor(Color.custom333333Color)
                        }
                        Spacer()
                        Image("appIcon_icon")
                            .resizable()
                            .frame(width: 32, height: 32)
                    }
                    .padding(.top, 24)
                    
                    // MARK: - Upcoming and Past Views
                    VStack(spacing: 8) {
                        HStack(alignment: .center, spacing: 0) {
                            Spacer()
                            
                            // MARK: - Button Upcoming
                            ZStack {
                                Button {
                                    print("Upcoming action")
                                    self.pastShown = false
                                } label: {
                                    Text("Upcoming")
                                        .font(.custom(self.pastShown ? "Roboto-Light" : "Roboto-Medium", size: 15))
                                        .foregroundColor(self.pastShown ? .black.opacity(0.7) : .black)
                                }
                            }
                            .frame(width: UIScreen.main.bounds.width / 2 - 18)
                            Spacer()
                            Spacer()
                            
                            // MARK: - Button Past
                            ZStack {
                                Button {
                                    print("Past action")
                                    self.pastShown = true
                                } label: {
                                    Text("Past")
                                        .font(.custom(self.pastShown ? "Roboto-Medium" : "Roboto-Light", size: 15))
                                        .foregroundColor(self.pastShown ? .black : .black.opacity(0.7))
                                }
                            }
                            .frame(width: UIScreen.main.bounds.width / 2 - 18)
                            Spacer()
                        }
                        
                        let upcoming = Capsule()
                            .fill(Color.black.opacity(self.pastShown ? 0.2 : 1))
                            .frame(maxWidth: .infinity, maxHeight: 1.5)
                        let past = Capsule()
                            .fill(Color.black.opacity(self.pastShown ? 1 : 0.2))
                            .frame(maxWidth: .infinity, maxHeight: 1.5)
                        
                        HStack(alignment: .center, spacing: 0) {
                            upcoming
                            past
                        }
                    }
                    .padding(.top, 8)
                    .padding(.horizontal, -18)
                    
                    ScrollView(.vertical, showsIndicators: false) {
                        // MARK: - Past View
                        if self.pastShown {
                            Text("past")
                        } else {
                            // MARK: - Upcoming View
                            LazyVStack(spacing: 20) {
                                MatchInfoCell(playerType: "Doubles", skillRange: "minimum", noOfParticipants: "2/4", date: "Tues, Oct 25", time: "04:00 PM", court: "Tour Greens Houston Serves The Houston Area", isForSchedule: true, lookingButtonAction: {
                                    print("lookingButtonAction")
                                }, gameOnButtonAction: {
                                    print("gameOnButtonAction")
                                }, noGameButtonAction: {
                                    print("noGameButtonAction")
                                }, messageButtonAction: {
                                    print("messageButtonAction")
                                    self.goToGroupChat.toggle()
                                }, addGuestButtonAction: {
                                    print("addGuestButtonAction")
                                    withAnimation {
                                        self.presentAlert.toggle()
                                    }
                                })
                            }
                        }
                    }
                    .padding(.top)
                    .background(Color.clear)
                }
                .padding(.horizontal, 18)
                
                // MARK: - Custom Alert for Add Guest
                if self.presentAlert {
                    VStack {
                        CustomAlertView(alertTitle: "Are you sure you want to add a Guest in this match?", nonFilledButtonTitle: "No", filledButtonTitle: "Yes", nonFilledButtonAction: {
                            withAnimation {
                                self.presentAlert.toggle()
                            }
                        }, filledButtonAction: {
                            withAnimation {
                                self.presentAlert.toggle()
                            }
                        }, presentAlert: $presentAlert)
                    }
                }
                
                // MARK: - Navigate to Group Chat
                NavigationLink("", destination: GroupChatView().navigationBarHidden(true).navigationBarBackButtonHidden(true), isActive: $goToGroupChat)
            }
            .navigationBarHidden(true)
            .navigationBarBackButtonHidden(true)
        }
    }
}

If someone could provide me with guidance on how to achieve this, I would greatly appreciate it!

Thank You!


Solution

  • So, to make it work I used almost of all of your code. The important part is the gesture modifier:

    .gesture(
                DragGesture()
                    .onChanged({ value in
                        let translationX = value.translation.width
                        let velocity = abs(value.velocity.width)
                        let predictedEnd = value.predictedEndTranslation.width
                        print("Entered \(translationX), velocity \(velocity)")
                        if (translationX > 200 || predictedEnd > 200) && velocity > 500 {
                            withAnimation(.smooth) {
                                self.pastShown = false
                            }
                        } else if (translationX < -200 || predictedEnd > -200) && velocity > 500 {
                            withAnimation(.smooth) {
                                self.pastShown = true
                            }
                        }
                    })
            )
    

    Play around and tune those values as you need. You can add it at the end of your ZStack which embeds your two views (Upcoming and past). Also, you need to add this line to your ScrollView enclosing the "past" tab:

    .frame(maxWidth: .infinity, maxHeight: .infinity)
    

    Making it look like this:

    ScrollView(.vertical, showsIndicators: false) {
                        // MARK: - Past View
                        if self.pastShown {
                            Text("past")
                        } else {
                            // MARK: - Upcoming View
                            LazyVStack(spacing: 20) {
                               // Your code here
                            }
                        }
                    }
                    .padding(.top)
                    .background(Color.clear)
                    .frame(maxWidth: .infinity, maxHeight: .infinity) // <-- The new line
    

    Otherwise the drag gesture would only be detected on a really narrow strip of the screen making it impossible for the swipe to work properly. Let me know if it works for youenter image description here

    Note: I used a red background to let you better see the swipeable area.