user19230964
user19230964

Reputation:

How do I add a vertical swipe up gesture(to go to a different view) in a scrollview in SwiftUI

I designed a SwiftUI view which is a scrollview. Now I need to add a vertical swipe gesture to it which shall take it to a different view. I tried to do it using the tabView and adding a rotating effect of -90 degrees to it. But that rotates my original view too and that's not what I want. I couldn't find any relevant help in SwiftUI which deals with swiping up a scrollview to a new view. Here's my code..

the vertical swipe I achieved using this. But my view get rotated. Setting other angles disappears the view somehow. I am new to SwiftUI, I am stuck on it for a week now.1

GeometryReader { proxy in
        TabView {
            ScrollView {
                VStack(alignment: .center) {
                    ZStack(alignment: .leading) {
                        Image("Asset 13").resizable().frame(width: percentWidth(percentage: 100), height: percentHeight(percentage: 50), alignment: .top)
                        HStack {
                            Spacer()
                            Image("Asset 1")//.padding(.bottom, 130)
                            Spacer()
                        }.padding(.bottom, 150)
                        
                        HStack {
                            VStack(spacing:2) {
                                Text("followers").foregroundColor(.white).padding(.leading, 20)
                                HStack {
                                    Image("Asset 3")
                                    Text("10.5k").foregroundColor(.white)
                                }
                            }
                            Spacer()
                            VStack {
                                Image("Asset 10").padding(.trailing)
                                Text("300K Review ").foregroundColor(.white)
                            }
                            
                        }.background(Image("Asset 2").resizable().frame(width: percentWidth(percentage: 100), height: percentHeight(percentage: 6), alignment: .leading))
                            .padding(.top, 410)
                        HStack {
                            Spacer()
                            Image("Asset 14").resizable().frame(width: percentWidth(percentage: 50), height: percentHeight(percentage: 25), alignment: .center)
                            Spacer()
                        }.padding(.top, 390)
                        
                        
                    }
                    VStack(spacing: 4) {
                        Text("Karuna Ahuja | Yoga Instructor").font(Font.custom(FontName.bold, size: 22))
                        Text("12 Years of Experience with Bhartiya Yog Sansthan").tracking(-1).font(Font.custom(FontName.light, size: 16)).opacity(0.4)
                    }
                    Divider()
                    HStack {
                        ZStack {
                            Image("Asset 6").resizable().frame(width: percentWidth(percentage: 30), height: percentHeight(percentage: 12), alignment: .center)
                            VStack {
                                Image("Asset 5").resizable().frame(width: percentWidth(percentage: 8), height: percentHeight(percentage: 4), alignment: .center)
                                Text("245").font(Font.custom(FontName.bold, size: 16))
                                Text("Video").font(Font.custom(FontName.medium, size: 16)).opacity(0.5)
                            }
                        }
                        
                        ZStack {
                            Image("Asset 6").resizable().frame(width: percentWidth(percentage: 30), height: percentHeight(percentage: 12), alignment: .center)
                            VStack {
                                Image("Asset 7").resizable().frame(width: percentWidth(percentage: 8), height: percentHeight(percentage: 4), alignment: .center)
                                Text("45").font(Font.custom(FontName.bold, size: 16))
                                Text("Live Class").font(Font.custom(FontName.medium, size: 16)).opacity(0.5)
                            }
                        }
                        
                        ZStack {
                            Image("Asset 6").resizable().frame(width: percentWidth(percentage: 30), height: percentHeight(percentage: 12), alignment: .center)
                            VStack {
                                Image("Asset 9").resizable().frame(width: percentWidth(percentage: 8), height: percentHeight(percentage: 4), alignment: .center)
                                Text("245").font(Font.custom(FontName.bold, size: 16))
                                Text("Sessions").font(Font.custom(FontName.medium, size: 16)).opacity(0.5)
                            }
                        }
                    }
                    Divider()
                    Text("Shine bright so that your light leads other. I'm a fitness junkie, high-energy yoga instructor. Let's make fitness FUN!").font(Font.custom(FontName.normal, size: 16)).tracking(-1).opacity(0.7).padding([.leading,.trailing], 6)
                    VideoPlayer(player: AVPlayer(url: videoUrl))
                        .frame(height: 320)
                    
                    Spacer()
                }.gesture(DragGesture(minimumDistance: 20, coordinateSpace: .global)
                    .onEnded { value in
                        let horizontalAmount = value.translation.width as CGFloat
                        let verticalAmount = value.translation.height as CGFloat
                        
                        if abs(horizontalAmount) > abs(verticalAmount) {
                            print(horizontalAmount < 0 ? "left swipe" : "right swipe")
                        } else {
                            print(verticalAmount < 0 ? "up swipe" : "down swipe")
                        }
                    })
            }.edgesIgnoringSafeArea(.all)
                .ignoresSafeArea()
            Text("this")
            Text("this")
            Text("this")
            
            //                ForEach(colors, id: \.self) { color in
            //                    color // Your cell content
            //                }
            //                .rotationEffect(.degrees(-90)) // Rotate content
            //                .frame(
            //                    width: proxy.size.width,
            //                    height: proxy.size.height
            //                )
        }
        .frame(
            width: proxy.size.height, // Height & width swap
            height: proxy.size.width
        )
        .rotationEffect(.degrees(90), anchor: .topLeading) // Rotate TabView
        .offset(x: proxy.size.width) // Offset back into screens bounds
        .tabViewStyle(
            PageTabViewStyle(indexDisplayMode: .never)
        )
    }

Upvotes: 1

Views: 2129

Answers (2)

tohidmahmoudvand
tohidmahmoudvand

Reputation: 262

Use this code:

import SwiftUI

struct PullToRefreshView: View
{
    private static let minRefreshTimeInterval = TimeInterval(0.2)
    private static let triggerHeight = CGFloat(100)
    private static let indicatorHeight = CGFloat(100)
    private static let fullHeight = triggerHeight + indicatorHeight
    
    let backgroundColor: Color
    let foregroundColor: Color
    let isEnabled: Bool
    let onRefresh: () -> Void
    
    @State private var isRefreshIndicatorVisible = false
    @State private var refreshStartTime: Date? = nil
    
    init(bg: Color = .neutral0, fg: Color = .neutral90, isEnabled: Bool = true, onRefresh: @escaping () -> Void)
    {
        self.backgroundColor = bg
        self.foregroundColor = fg
        self.isEnabled = isEnabled
        self.onRefresh = onRefresh
    }
    
    var body: some View
    {
        VStack(spacing: 0)
        {
            LazyVStack(spacing: 0)
            {
                Color.clear
                    .frame(height: Self.triggerHeight)
                    .onAppear
                    {
                        if isEnabled
                        {
                            withAnimation
                            {
                                isRefreshIndicatorVisible = true
                            }
                            refreshStartTime = Date()
                        }
                    }
                    .onDisappear
                    {
                        if isEnabled, isRefreshIndicatorVisible, let diff = refreshStartTime?.distance(to: Date()), diff > Self.minRefreshTimeInterval
                        {
                            onRefresh()
                        }
                        withAnimation
                        {
                            isRefreshIndicatorVisible = false
                        }
                        refreshStartTime = nil
                    }
            }
            .frame(height: Self.triggerHeight)
            
            indicator
                .frame(height: Self.indicatorHeight)
        }
        .background(backgroundColor)
        .ignoresSafeArea(edges: .all)
        .frame(height: Self.fullHeight)
        .padding(.top, -Self.fullHeight)
    }
    
    private var indicator: some View
    {
        ProgressView()
            .progressViewStyle(CircularProgressViewStyle(tint: foregroundColor))
            .opacity(isRefreshIndicatorVisible ? 1 : 0)
    }
}

sample:

ScrollView{
     VStack(spacing:0) {
     //top of scrollView
         PullToRefreshView{
              //todo
        }
   }
}

Upvotes: 0

ChrisR
ChrisR

Reputation: 12115

The only pure SwiftUI way I see is to do your own ScrollView implementation, which is not too complicated. This example has two views on top of each other. If you drag the first view further up than to the middle of the screen, it swipes away to reveal the second view.

enter image description here

struct ContentView: View {
    
    @State private var offset = CGFloat.zero
    @State private var dragOffset = CGFloat.zero
    @State private var viewHeight = CGFloat.zero
    
    var body: some View {
        
        GeometryReader { fullgeo in
            ZStack(alignment: .top) {
                
                SecondView()
                // necessary for second view to resize individually
                    .frame(height: fullgeo.size.height)
                
                ScrollingView()
                    .overlay( GeometryReader { geo in Color.clear.onAppear { viewHeight = geo.size.height }})
                
                    .offset(y: offset + dragOffset)
                
                    .gesture(DragGesture()
                        .onChanged { value in
                            dragOffset = value.translation.height
                        }
                        .onEnded { value in
                            withAnimation(.easeOut) {
                                dragOffset = .zero
                                offset += value.predictedEndTranslation.height
                                
                                // if bottom dragged higher than 50% of screen > second view
                                if offset < -(viewHeight - fullgeo.size.height/2) {
                                    dragOffset = -viewHeight
                                    return
                                }
                                
                                // else constrain to top / bottom of ScrollingView
                                offset = max(min(offset, 0), -(viewHeight - fullgeo.size.height))
                                
                                
                            }
                        }
                    )
            }
        }
    }
}

struct ScrollingView: View {
    var body: some View {
        VStack {
            Text("View Top").font(.headline)
            ForEach(0..<10) { _ in
                Text("Content")
                    .frame(width: 200, height: 100)
                    .background(.white)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.gray)
    }
}

struct SecondView: View {
    var body: some View {
        Text("View Bottom").font(.headline)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(.orange)
    }
}

Upvotes: 1

Related Questions