Closing a View when it reaches the top, that has a scrollview in SwiftUI

Viewed 90

I originally posted a question here on how to begin to dismiss or close a view, that contains a ScrollView, when the scroll reaches the top, in a similar way that Apples App store cards close, or the way Instagram dismisses a view on a down gesture. But using SwiftUI

Obviously if you just add a Draggesture and use it to offset or scale the view, it wouldn't get read because the scrollview would read first and block the drag gesture from being recognized.

I didn't get an answer here but fortunately I figured it out, and rather than post an answer I just updated my code. I created a UIScrollView using UIViewRepresentable and added some lines of code to add a PanGestureRecognizer.

struct ContentView: View {
    
    @ObservedObject var vm = ViewModel.vm
    @State var scale: CGFloat = 1
    
    var body: some View {
        
            CustomScrollView {

                ZStack (alignment: .center){

                    VStack (spacing:0){

                        Image("Image")
                            .resizable()
                            .scaledToFit()

                        Text("Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.")
                            .font(.system(size: 24))
                            .padding(.horizontal)

                    } //V
                    .ignoresSafeArea()

                } //Z

            } //SCROLLVIEW
            .frame(width: Screen.width, height: Screen.height, alignment: .center)
            .ignoresSafeArea()
            .scaleEffect(self.scale)
            .onChange(of: vm.y) { y in
                
                if y < 101  { //Set point at which to begin scaling down
                    
                    withAnimation(.linear(duration: 0.15)) {
                        self.scale = (1-(y/500)) //Calculates scaling using translation.y from the Gesture Recognizer
                    }
                    
                } //IF

if y == 100 {withAnimation(.default) { //Limits the drag based on translation.y
                        self.scale = 1
                    }
                        
                    }//IF
                
            } //ONCHANGE
        
    }
    
}




struct CustomScrollView<Content:View> : UIViewRepresentable {
    
    @ObservedObject var viewValues = ViewModel.vm
    typealias UIViewType = UIScrollView
    let content: Content
    
    init (
        @ViewBuilder content: () -> Content
    ) {
        self.content = content()
    }
    
    // MARK: - MAKEUIVIEW
    
    func makeUIView(context: Context) -> UIScrollView { //Makes the initial ScrollView
        
        let scrollView = UIScrollView()
        

        // Child
        let child = UIHostingController(rootView: content)
        scrollView.addSubview(child.view)
        
        // Child Size
        let newSize = child.view.sizeThatFits(CGSize(width: Screen.width, height: .greatestFiniteMagnitude))
        child.view.frame = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
        scrollView.contentSize = newSize
        
        //Adds Pan gesture Recognizer
        scrollView.delegate = context.coordinator
        scrollView.panGestureRecognizer.addTarget(context.coordinator, action: #selector(Coordinator.dragged))

        return scrollView
        
    }
    
    //Update ScrollView
    
    func updateUIView(_ scrollView: UIScrollView, context: Context) {
                        
    }
    
    // MARK: - COORDINATOR
    
    func makeCoordinator() -> Coordinator {
        
        Coordinator()
        
    }
    
    class Coordinator: NSObject, UIScrollViewDelegate, ObservableObject {
        
        var offset: CGFloat = 0
        
        
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            
            self.offset = scrollView.contentOffset.y
                        
            if scrollView.contentOffset.y < 1 { //Turns Bounce off at top, and sets Offset to Zero.
                scrollView.contentOffset.y = 0
                scrollView.bounces = false
                
            } else { scrollView.bounces = true } //IF
            
        } //ViewDiDScroll
        
        

        
        @objc func dragged(gestureRecognizer: UIPanGestureRecognizer) { // Pan Gesture Recognizer
            
            var translation = gestureRecognizer.translation(in: gestureRecognizer.view)
            let state = gestureRecognizer.state
            
            if translation.y > 100 {translation.y = 100} //Sets a drag limit at which the view will close
            if offset == 0 { ViewModel.vm.y = translation.y } //Set a point at which the view should start closing
            if state == .ended { ViewModel.vm.y = 0 } //Reset Y value
            
            
        } //@objc
        
    } //COORDINATOR
    
} //END

class ViewModel: ObservableObject {
    
    static var vm = ViewModel()
    @Published var y : CGFloat = 1 // Scale View Down

}

struct Screen {
    static let width = UIScreen.main.bounds.width
    static let height = UIScreen.main.bounds.height
}
1 Answers

I was going to edit your View, but I could not do it because you did not provide the full code, plus there were many errors that made me unable to run your code.

Therefore, I created a sample view with the similar functionality that you wanted.

Run this below code by going to the MainView() first. When you run the MainView(), you will be able to go to another view which has a long text. When you scroll this long text all the way up, it will dismiss the view and return back to the MainView().

Note:you must add/import SwiftUITrackableScrollView package to achieve this because the provided ScrollView() by SwiftUI is kind of limited. link package to add in Xcode: https://github.com/maxnatchanon/trackable-scroll-view

import SwiftUI
//ADD AND IMPORT THIS BELOW PACKAGE
import SwiftUITrackableScrollView

//1ST VIEW
//Run MainView() First
struct MainView: View {
var body: some View {
    NavigationView {
        NavigationLink {
            SampleScroll()
        } label: {
            Text("go to scrollview")
        }

    }
}
}

//2ND VIEW
//SAMPLE SCROLL VIEW
//WHEN YOU SCROLL YOUR TEXT ALL THE WAY UP
//VIEW WILL BE DISMSSED AND RETURN BACK TO THE PREVIOUS VIEW
struct SampleScroll: View {

@State private var scrollViewContentOffset = CGFloat(0)
@Environment(\.presentationMode) var mode: Binding<PresentationMode>
@State var text = "Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world Hello world v v Hello world Hello world v Hello world"

var body: some View {
    ZStack {
        TrackableScrollView(.vertical, showIndicators: true, contentOffset: $scrollViewContentOffset)  {
            TextEditor(text: $text)
                .frame(width: 300, height: 300, alignment: .center)
        }
        .onChange(of: scrollViewContentOffset) { _ in
            
            //TO KNOW THE VALUE OF OFFSET THAT YOU NEED TO DISMISS YOUR VIEW
            print(scrollViewContentOffset)
            
            //THIS IS WHERE THE DISMISS HAPPENS
            if scrollViewContentOffset > 180 {
                mode.wrappedValue.dismiss()
            }
            
        }
    }
}
}
Related