我正在尝试像Swift13中的iOS13中的Safari一样重新创建Modal:
是这样的:
有人知道在 SwiftUI 中是否可行?我想显示一个小半模态,可以选择拖动到全屏模式,就像共享表一样。
任何建议都非常感谢!
答案 0 :(得分:5)
答案 1 :(得分:4)
在 Swift 5.5 iOS 15+ 和 Mac Catalyst 15+ 中有一个
有一个新的解决方案 Generics
adaptiveSheetPresentationController
答案 2 :(得分:2)
从 Beta 2 Beta 3开始,您不能将模式View
表示为.fullScreen
。它表示为.automatic -> .pageSheet
。但是,即使解决了这个问题,我也非常怀疑它们是否会为您免费提供拖动功能。它将包含在文档中。
您现在可以使用this answer来全屏显示。 Gist here。
然后,在演示之后,这是一个如何重新创建交互的简单而肮脏的示例。
@State var drag: CGFloat = 0.0
var body: some View {
ZStack(alignment: .bottom) {
Spacer() // Use the full space
Color.red
.frame(maxHeight: 300 + self.drag) // Whatever minimum height you want, plus the drag offset
.gesture(
DragGesture(coordinateSpace: .global) // if you use .local the frame will jump around
.onChanged({ (value) in
self.drag = max(0, -value.translation.height)
})
)
}
}
答案 3 :(得分:2)
答案 4 :(得分:2)
安德烈·卡雷拉(Andre Carrera)的回答很好,可以随意使用他提供的以下指南:https://www.mozzafiller.com/posts/swiftui-slide-over-card-like-maps-stocks
我修改了SlideOverCard结构,以便它使用设备的实际高度来测量该卡应该停在的位置(您可以使用bounds.height进行调整以适应您的需求):
struct SlideOverCard<Content: View>: View {
var bounds = UIScreen.main.bounds
@GestureState private var dragState = DragState.inactive
@State var position = UIScreen.main.bounds.height/2
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 + self.dragState.translation.height)
.animation(self.dragState.isDragging ? nil : .interpolatingSpring(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 + drag.translation.height
let positionAbove: CGFloat
let positionBelow: CGFloat
let closestPosition: CGFloat
if cardTopEdgeLocation <= bounds.height/2 {
positionAbove = bounds.height/7
positionBelow = bounds.height/2
} else {
positionAbove = bounds.height/2
positionBelow = bounds.height - (bounds.height/9)
}
if (cardTopEdgeLocation - positionAbove) < (positionBelow - cardTopEdgeLocation) {
closestPosition = positionAbove
} else {
closestPosition = positionBelow
}
if verticalDirection > 0 {
self.position = positionBelow
} else if verticalDirection < 0 {
self.position = positionAbove
} else {
self.position = closestPosition
}
}
}
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
}
}
}
答案 5 :(得分:2)
我认为几乎所有使用 SwiftUI 编写任何内容的 iOS 开发人员都必须遇到这个问题。我确实做到了,但我认为这里的大多数答案要么太复杂,要么没有真正提供我想要的。
我在 GitHub 上编写了一个非常简单的部分工作表,以 Swift 包的形式提供 - HalfASheet
它可能没有其他一些解决方案的花里胡哨,但它做了它需要做的事情。另外,编写自己的代码总是有助于理解正在发生的事情。
注意 - 有几件事 - 首先,这是一项正在进行的工作,请随时改进它,等等。其次,我故意不做 .podspec 就好像你是为 SwiftUI 进行开发时,您至少使用的是 iOS 13,而且我认为 Swift 软件包要好得多...
答案 6 :(得分:1)
这是我幼稚的底页,可以根据其内容进行缩放。无需拖动,但如果需要的话,添加起来应该相对容易:)
struct BottomSheet<SheetContent: View>: 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("done")
.padding(.top, 5)
}
}
sheetContent()
}
.padding()
}
.zIndex(.infinity)
.transition(.move(edge: .bottom))
.edgesIgnoringSafeArea(.bottom)
}
}
}
extension View {
func customBottomSheet<SheetContent: View>(
isPresented: Binding<Bool>,
sheetContent: @escaping () -> SheetContent
) -> some View {
self.modifier(BottomSheet(isPresented: isPresented, sheetContent: sheetContent))
}
}
并按如下所示使用:
.customBottomSheet(isPresented: $isPickerPresented) {
DatePicker(
"time",
selection: self.$time,
displayedComponents: .hourAndMinute
)
.labelsHidden()
}
答案 7 :(得分:0)
您可以自己制作并将其放置在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
}
}
}
答案 8 :(得分:0)
我试图做同样的事情,在 SwiftUI 中以本机方式显示共享表,而不必实现/导入组件。 我在 https://jeevatamil.medium.com/how-to-create-share-sheet-uiactivityviewcontroller-in-swiftui-cef64b26f073
中找到了这个解决方案struct ShareSheetView: View {
var body: some View {
Button(action: actionSheet) {
Image(systemName: "square.and.arrow.up")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 36, height: 36)
}
}
func actionSheet() {
guard let data = URL(string: "https://www.zoho.com") else { return }
let av = UIActivityViewController(activityItems: [data], applicationActivities: nil)
UIApplication.shared.windows.first?.rootViewController?.present(av, animated: true, completion: nil)
}
}