46 votes

SwiftUI - Demi-modal ?

Je suis en train de recréer une Modal exactement comme Safari dans iOS13 en SwiftUI :

Voici à quoi ça ressemble :

entrez la description de l'image ici

Est-ce que quelqu'un sait si c'est possible en SwiftUI ? Je veux afficher une petite moitié de modal, avec l'option de la faire glisser en plein écran, tout comme la feuille de partage.

Tout conseil est grandement apprécié !

19voto

Miotz Points 841

J'ai écrit un package Swift qui inclut un modificateur personnalisé qui vous permet d'utiliser la fenêtre modale partielle.

Voici le lien : https://github.com/AndreaMiotto/PartialSheet

N'hésitez pas à l'utiliser ou à contribuer

enter image description here

12voto

Andre Carrera Points 1998

```html

Vous pouvez créer le vôtre et le placer à l'intérieur d'un zstack : https://www.mozzafiller.com/posts/swiftui-slide-over-card-like-maps-stocks

struct SlideOverCard : 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
        }
    }
}

```

5voto

Viktor Maric Points 363

J'ai écrit un package SwiftUI qui inclut une demi-modal personnalisée similaire à iOS 13 et ses boutons.

Dépôt GitHub : https://github.com/ViktorMaric/HalfModal

Aperçu de la demi-modal

5voto

arsenius Points 536

Dès Beta 2 Beta 3, vous ne pouvez pas présenter une View modale en tant que .fullScreen. Elle se présente comme .automatic -> .pageSheet. Même une fois que ce sera corrigé, je doute fortement qu'ils vous donnent gratuitement la capacité de glisser. Cela serait déjà inclus dans la documentation.

Vous pouvez utiliser cette réponse pour présenter en plein écran pour le moment. Gist ici.

Ensuite, après la présentation, voici un exemple rapide et simple de comment vous pouvez recréer cette interaction.

    @State var drag: CGFloat = 0.0

    var body: some View {
        ZStack(alignment: .bottom) {
            Spacer() // Utilisez tout l'espace
            Color.red
                .frame(maxHeight: 300 + self.drag) // La hauteur minimale que vous souhaitez, plus le décalage de glissement
                .gesture(
                    DragGesture(coordinateSpace: .global) // si vous utilisez .local le cadre va sauter
                        .onChanged({ (value) in
                            self.drag = max(0, -value.translation.height)
                        })
                )
        }
    }

3voto

Szymon W Points 65

Voici mon bas de feuille naïf qui s'adapte à son contenu. Sans glisser mais il devrait être relativement facile à ajouter si nécessaire :)

struct BottomSheet: ViewModifier {
@Binding var isPresented: Bool
let sheetContent: () -> SheetContent

func body(content: Content) -> some View {
    ZStack {
        content

        if isPresented {
            VStack {
                Spacer()

                VStack {
                    HStack {
                        Spacer()
                        Button(action: {
                            withAnimation(.easeInOut) {
                                self.isPresented = false
                            }
                        }) {
                            Text("terminé")
                                .padding(.top, 5)
                        }
                    }

                    sheetContent()
                }
                .padding()
            }
            .zIndex(.infinity)
            .transition(.move(edge: .bottom))
            .edgesIgnoringSafeArea(.bottom)
        }
    }
}

extension View {
    func customBottomSheet(
        isPresented: Binding,
        sheetContent: @escaping () -> SheetContent
    ) -> some View {
        self.modifier(BottomSheet(isPresented: isPresented, sheetContent: sheetContent))
    }
}

et utilisez comme ci-dessous :

.customBottomSheet(isPresented: $isPickerPresented) {
                DatePicker(
                    "heure",
                    selection: self.$time,
                    displayedComponents: .hourAndMinute
                )
                .labelsHidden()
        }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X