SwiftUI - Half modal? SwiftUI - Half modal? xcode xcode

SwiftUI - Half modal?


I've written a Swift Package that includes a custom modifier that allows you to use the half modal sheet.

Here is the link: https://github.com/AndreaMiotto/PartialSheet

Feel free to use it or to contribute

enter image description here


You can make your own and place it inside of a zstack:https://www.mozzafiller.com/posts/swiftui-slide-over-card-like-maps-stocks

struct SlideOverCard<Content: View> : View {    @GestureState private var dragState = DragState.inactive    @State var position = CardPosition.top    var content: () -> Content    var body: some View {        let drag = DragGesture()            .updating($dragState) { drag, state, transaction in                state = .dragging(translation: drag.translation)            }            .onEnded(onDragEnded)        return Group {            Handle()            self.content()        }        .frame(height: UIScreen.main.bounds.height)        .background(Color.white)        .cornerRadius(10.0)        .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)        .offset(y: self.position.rawValue + self.dragState.translation.height)        .animation(self.dragState.isDragging ? nil : .spring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))        .gesture(drag)    }    private func onDragEnded(drag: DragGesture.Value) {        let verticalDirection = drag.predictedEndLocation.y - drag.location.y        let cardTopEdgeLocation = self.position.rawValue + drag.translation.height        let positionAbove: CardPosition        let positionBelow: CardPosition        let closestPosition: CardPosition        if cardTopEdgeLocation <= CardPosition.middle.rawValue {            positionAbove = .top            positionBelow = .middle        } else {            positionAbove = .middle            positionBelow = .bottom        }        if (cardTopEdgeLocation - positionAbove.rawValue) < (positionBelow.rawValue - cardTopEdgeLocation) {            closestPosition = positionAbove        } else {            closestPosition = positionBelow        }        if verticalDirection > 0 {            self.position = positionBelow        } else if verticalDirection < 0 {            self.position = positionAbove        } else {            self.position = closestPosition        }    }}enum CardPosition: CGFloat {    case top = 100    case middle = 500    case bottom = 850}enum DragState {    case inactive    case dragging(translation: CGSize)    var translation: CGSize {        switch self {        case .inactive:            return .zero        case .dragging(let translation):            return translation        }    }    var isDragging: Bool {        switch self {        case .inactive:            return false        case .dragging:            return true        }    }}


In Swift 5.5 iOS 15+ and Mac Catalyst 15+ there is a

There is a new solution with adaptiveSheetPresentationController

https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller/3810055-adaptivesheetpresentationcontrol?changes=__4

@available(iOS 15.0, *)struct CustomSheetParentView: View {    @State private var isPresented = false        var body: some View {        VStack{            Button("present sheet", action: {                isPresented.toggle()            }).adaptiveSheet(isPresented: $isPresented, detents: [.medium()], smallestUndimmedDetentIdentifier: .large){                Rectangle()                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)                    .foregroundColor(.clear)                    .border(Color.blue, width: 3)                    .overlay(Text("Hello, World!").frame(maxWidth: .infinity, maxHeight: .infinity)                                .onTapGesture {                        isPresented.toggle()                    }                    )            }                    }    }}@available(iOS 15.0, *)struct AdaptiveSheet<T: View>: ViewModifier {    let sheetContent: T    @Binding var isPresented: Bool    let detents : [UISheetPresentationController.Detent]    let smallestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?    let prefersScrollingExpandsWhenScrolledToEdge: Bool    let prefersEdgeAttachedInCompactHeight: Bool        init(isPresented: Binding<Bool>, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], smallestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, @ViewBuilder content: @escaping () -> T) {        self.sheetContent = content()        self.detents = detents        self.smallestUndimmedDetentIdentifier = smallestUndimmedDetentIdentifier        self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight        self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge        self._isPresented = isPresented    }    func body(content: Content) -> some View {        ZStack{            content            CustomSheet_UI(isPresented: $isPresented, detents: detents, smallestUndimmedDetentIdentifier: smallestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, content: {sheetContent}).frame(width: 0, height: 0)        }    }}@available(iOS 15.0, *)extension View {    func adaptiveSheet<T: View>(isPresented: Binding<Bool>, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], smallestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, @ViewBuilder content: @escaping () -> T)-> some View {        modifier(AdaptiveSheet(isPresented: isPresented, detents : detents, smallestUndimmedDetentIdentifier: smallestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge: prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, content: content))    }}@available(iOS 15.0, *)struct CustomSheet_UI<Content: View>: UIViewControllerRepresentable {        let content: Content    @Binding var isPresented: Bool    let detents : [UISheetPresentationController.Detent]    let smallestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?    let prefersScrollingExpandsWhenScrolledToEdge: Bool    let prefersEdgeAttachedInCompactHeight: Bool        init(isPresented: Binding<Bool>, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], smallestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, @ViewBuilder content: @escaping () -> Content) {        self.content = content()        self.detents = detents        self.smallestUndimmedDetentIdentifier = smallestUndimmedDetentIdentifier        self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight        self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge        self._isPresented = isPresented    }    func makeCoordinator() -> Coordinator {        Coordinator(self)    }    func makeUIViewController(context: Context) -> CustomSheetViewController<Content> {        let vc = CustomSheetViewController(coordinator: context.coordinator, detents : detents, smallestUndimmedDetentIdentifier: smallestUndimmedDetentIdentifier, prefersScrollingExpandsWhenScrolledToEdge:  prefersScrollingExpandsWhenScrolledToEdge, prefersEdgeAttachedInCompactHeight: prefersEdgeAttachedInCompactHeight, content: {content})        return vc    }        func updateUIViewController(_ uiViewController: CustomSheetViewController<Content>, context: Context) {        if isPresented{            uiViewController.presentModalView()        }else{            uiViewController.dismissModalView()        }    }    class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {        var parent: CustomSheet_UI        init(_ parent: CustomSheet_UI) {            self.parent = parent        }        //Adjust the variable when the user dismisses with a swipe        func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {            if parent.isPresented{                parent.isPresented = false            }                    }            }}@available(iOS 15.0, *)class CustomSheetViewController<Content: View>: UIViewController {    let content: Content    let coordinator: CustomSheet_UI<Content>.Coordinator    let detents : [UISheetPresentationController.Detent]    let smallestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier?    let prefersScrollingExpandsWhenScrolledToEdge: Bool    let prefersEdgeAttachedInCompactHeight: Bool    private var isLandscape: Bool = UIDevice.current.orientation.isLandscape    init(coordinator: CustomSheet_UI<Content>.Coordinator, detents : [UISheetPresentationController.Detent] = [.medium(), .large()], smallestUndimmedDetentIdentifier: UISheetPresentationController.Detent.Identifier? = .medium, prefersScrollingExpandsWhenScrolledToEdge: Bool = false, prefersEdgeAttachedInCompactHeight: Bool = true, @ViewBuilder content: @escaping () -> Content) {        self.content = content()        self.coordinator = coordinator        self.detents = detents        self.smallestUndimmedDetentIdentifier = smallestUndimmedDetentIdentifier        self.prefersEdgeAttachedInCompactHeight = prefersEdgeAttachedInCompactHeight        self.prefersScrollingExpandsWhenScrolledToEdge = prefersScrollingExpandsWhenScrolledToEdge        super.init(nibName: nil, bundle: .main)    }        required init?(coder: NSCoder) {        fatalError("init(coder:) has not been implemented")    }    func dismissModalView(){        dismiss(animated: true, completion: nil)    }    func presentModalView(){                let hostingController = UIHostingController(rootView: content)                hostingController.modalPresentationStyle = .popover        hostingController.presentationController?.delegate = coordinator as UIAdaptivePresentationControllerDelegate        hostingController.modalTransitionStyle = .coverVertical        if let hostPopover = hostingController.popoverPresentationController {            hostPopover.sourceView = super.view            let sheet = hostPopover.adaptiveSheetPresentationController            //As of 13 Beta 4 if .medium() is the only detent in landscape error occurs            sheet.detents = (isLandscape ? [.large()] : detents)            sheet.largestUndimmedDetentIdentifier =            smallestUndimmedDetentIdentifier            sheet.prefersScrollingExpandsWhenScrolledToEdge =            prefersScrollingExpandsWhenScrolledToEdge            sheet.prefersEdgeAttachedInCompactHeight =            prefersEdgeAttachedInCompactHeight            sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true                    }        if presentedViewController == nil{            present(hostingController, animated: true, completion: nil)        }    }    /// To compensate for orientation as of 13 Beta 4 only [.large()] works for landscape    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {        super.viewWillTransition(to: size, with: coordinator)        if UIDevice.current.orientation.isLandscape {            isLandscape = true            self.presentedViewController?.popoverPresentationController?.adaptiveSheetPresentationController.detents = [.large()]        } else {            isLandscape = false            self.presentedViewController?.popoverPresentationController?.adaptiveSheetPresentationController.detents = detents        }    }}@available(iOS 15.0, *)struct CustomSheetView_Previews: PreviewProvider {    static var previews: some View {        CustomSheetParentView()    }}