Exploring SwiftUI: DragGesture for fullScreenCover
Book

Exploring MusicKit and Apple Music API

Unlock the full power of MusicKit & Apple Music APIs in your apps with the best guide! Use code musickit-blog for a limited-time 35% discount!

Chroma Game uses a tab bar to list all the screens. For v2, I’m presenting all the views using the fullScreenCover modifier.

While playing the game, due to the habit of swiping down to close the modal, I was missing the implementation in fullScreenCover, something that we get out of the box when using the sheet modifier. I want a fullScreenCover that supports the swipe feature that sheet offers.

Apple Music uses this on the now playing screen, and I love the transition:

DragGesture

As a temporary workaround, I’m adding a DragGesture that calculates the difference between the start and last locations when the drag ends.

After some manual testing, I used the arbitrary value 150 to know that it was a full drag and the screen should dismiss.

Sample Example

Here’s an example to use fullScreenCover with drag gesture:

struct SampleView: View {
    @State private var showCoverView = false
    
    var body: some View {
        Button("PRESENT") {
            showCoverView.toggle()
        }
        .fullScreenCover(isPresented: $showCoverView) {
            CoverView()
        }
    }
}

struct CoverView: View {
    /// Use @Environment(\.presentationMode) private var presentationMode for iOS 14 and below
    @Environment(\.dismiss) private var dismiss
    
    var body: some View {
        Button("DISMISS") {
        	/// Use presentationMode.wrappedValue.dismiss() for iOS 14 and below
            dismiss()
        }
        .gesture(
            DragGesture().onEnded { value in
                if value.location.y - value.startLocation.y > 150 {
                    /// Use presentationMode.wrappedValue.dismiss() for iOS 14 and below
                    dismiss()
                }
            }
        )
    }
}

Example from Chroma Game

Here’s a stripped implementation of the HomeView where the view model contains an enum GameModeDestination that switches between RGB and HSB view.

struct HomeView: View {
    @EnvironmentObject var viewModel: HomeViewModel
    
    var body: some View {
        VStack {
            Text("MAIN VIEW IMPLEMENTATION")
            
            /// Stripped implementation of Home View
            Button("PRESENT RGB") {
                viewModel.gameDestination = .rgb
            }
        }
        .fullScreenCover(
            item: $viewModel.gameDestination,
            onDismiss: viewModel.didDismiss) { item in
            switch item {
                case .rgb: RGBView(viewModel: viewModel.new)
                case .hsb: HSBView(viewModel: viewModel.new)
            }
        }
    }
}

ContainerView has a dismiss button on the top, similar to Apple Music now playing screen:

public struct DismissButton: View {
    var action: () -> ()

    public init(_ action: @escaping () -> ()) {
        self.action = action
    }
    
    public var body: some View {
        Button(action: action) {
            RoundedRectangle(cornerRadius: 16)
                .fill(Color.gray)
                .frame(width: 50, height: 5)
        }
    }
}

Here’s the implemention in ContainerView:

struct ContainerView: View {
    @Environment(\.presentationMode) var presentation
    @ObservedObject var viewModel: MainViewModel
    
    var body: some View {
        VStack {
            DismissButton { dismiss() }
            
            Text("IMPLEMENTATION")
        }
        .gesture(
            DragGesture().onEnded { value in
                if value.location.y - value.startLocation.y > 150 {
                    dismiss()
                }
            }
        )
    }
    
    private func dismiss() {
        viewModel.invalidateTimer()
        viewModel.dismiss()
        presentation.wrappedValue.dismiss()
    }
}

And that provides an easy swipe to dismiss the full-screen cover!

Conclusion

The’ DragGesture’ solution is a simple way to add a swipe to your full-screen cover, compared to what we have for the sheet modifier.

The way how the parent view is enlarged while you’re dismissing the now playing screen view on Apple Music is what I intend to achieve in the future. I probably will have to see if there’s a way to use UIKit.

Tag @rudrankriyam on Twitter if you have already implemented something similar!

Thanks for reading, and I hope you’re enjoying it!

Book

Exploring MusicKit and Apple Music API

Unlock the full power of MusicKit & Apple Music APIs in your apps with the best guide! Use code musickit-blog for a limited-time 35% discount!

Written by

Rudrank Riyam

Hi, my name is Rudrank. I create apps for Apple Platforms while listening to music all day and night. Author of "Exploring MusicKit". Apple WWDC 2019 scholarship winner.