在WWDC 2019上,Apple宣布了一种新的“卡片式”外观模态演示,并带有内置手势,可通过向下滑动卡片来消除模式视图控制器。他们还引入了新isModalInPresentation
属性,UIViewController
以便您可以选择拒绝这种解雇行为。
但是到目前为止,我还没有找到在SwiftUI中模拟这种行为的方法。使用.presentation(_ modal: Modal?)
,不,据我所知,让你以同样的方式禁止解雇手势。我还尝试将模式视图控制器放在内UIViewControllerRepresentable
View
,但这似乎也无济于事:
struct MyViewControllerView: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<MyViewControllerView>) -> UIHostingController<MyView> {
return UIHostingController(rootView: MyView())
}
func updateUIViewController(_ uiViewController: UIHostingController<MyView>, context: UIViewControllerRepresentableContext<MyViewControllerView>) {
uiViewController.isModalInPresentation = true
}
}
Run Code Online (Sandbox Code Playgroud)
即使在出席会议之后,.presentation(Modal(MyViewControllerView()))
我仍然可以向下滑动以消除视图。当前是否可以使用现有的SwiftUI构造来做到这一点?
Gui*_*iks 56
根据下面pawello2222的回答,新interactiveDismissDisabled(_:)
API现在支持这一点。
struct ContentView: View {
@State private var showSheet = false
var body: some View {
Text("Content View")
.sheet(isPresented: $showSheet) {
Text("Sheet View")
.interactiveDismissDisabled(true)
}
}
}
Run Code Online (Sandbox Code Playgroud)
我也想这样做,但在任何地方都找不到解决方案。劫持拖动手势的答案有点有效,但在通过滚动滚动视图或表单将其解除时则无效。问题中的方法也不那么笨拙,所以我进一步调查了它。
对于我的用例,我在工作表中有一个表单,理想情况下可以在没有内容时关闭该表单,但在有内容时必须通过警报进行确认。
我对这个问题的解决方案:
struct ModalSheetTest: View {
@State private var showModally = false
@State private var showSheet = false
var body: some View {
Form {
Toggle(isOn: self.$showModally) {
Text("Modal")
}
Button(action: { self.showSheet = true}) {
Text("Show sheet")
}
}
.sheet(isPresented: $showSheet) {
Form {
Button(action: { self.showSheet = false }) {
Text("Hide me")
}
}
.presentation(isModal: self.showModally) {
print("Attempted to dismiss")
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
状态值showModally
决定它是否必须以模态显示。如果是这样,将其向下拖动以关闭只会触发在示例中仅打印“尝试关闭”的关闭,但可用于显示警报以确认关闭。
struct ModalView<T: View>: UIViewControllerRepresentable {
let view: T
let isModal: Bool
let onDismissalAttempt: (()->())?
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: view)
}
func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {
context.coordinator.modalView = self
uiViewController.rootView = view
uiViewController.parent?.presentationController?.delegate = context.coordinator
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
let modalView: ModalView
init(_ modalView: ModalView) {
self.modalView = modalView
}
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
!modalView.isModal
}
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
modalView.onDismissalAttempt?()
}
}
}
extension View {
func presentation(isModal: Bool, onDismissalAttempt: (()->())? = nil) -> some View {
ModalView(view: self, isModal: isModal, onDismissalAttempt: onDismissalAttempt)
}
}
Run Code Online (Sandbox Code Playgroud)
这非常适合我的用例,希望它也能帮助你或其他人。
FRI*_*DAY 13
通过更改gesture priority
您不想被拖动的DragGesture
任何视图的,您可以阻止任何视图。例如,对于 Modal,它可以按如下方式完成:
也许这不是最佳实践,但它可以完美运行
struct ContentView: View {
@State var showModal = true
var body: some View {
Button(action: {
self.showModal.toggle()
}) {
Text("Show Modal")
}.sheet(isPresented: self.$showModal) {
ModalView()
}
}
}
Run Code Online (Sandbox Code Playgroud)
struct ModalView : View {
@Environment(\.presentationMode) var presentationMode
let dg = DragGesture()
var body: some View {
ZStack {
Rectangle()
.fill(Color.white)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.highPriorityGesture(dg)
Button("Dismiss Modal") {
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
R. *_* J. 11
注意:为了清晰和简洁,此代码已被编辑。
用一种方式来获得当前窗口的场景,从这里,你可以通过这个扩展获得顶视图控制器这里从@ BOBJ-C
extension UIApplication {
func visibleViewController() -> UIViewController? {
guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else { return nil }
guard let rootViewController = window.rootViewController else { return nil }
return UIApplication.getVisibleViewControllerFrom(vc: rootViewController)
}
private static func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
if let navigationController = vc as? UINavigationController,
let visibleController = navigationController.visibleViewController {
return UIApplication.getVisibleViewControllerFrom( vc: visibleController )
} else if let tabBarController = vc as? UITabBarController,
let selectedTabController = tabBarController.selectedViewController {
return UIApplication.getVisibleViewControllerFrom(vc: selectedTabController )
} else {
if let presentedViewController = vc.presentedViewController {
return UIApplication.getVisibleViewControllerFrom(vc: presentedViewController)
} else {
return vc
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
并将其变成这样的视图修饰符:
struct DisableModalDismiss: ViewModifier {
let disabled: Bool
func body(content: Content) -> some View {
disableModalDismiss()
return AnyView(content)
}
func disableModalDismiss() {
guard let visibleController = UIApplication.shared.visibleViewController() else { return }
visibleController.isModalInPresentation = disabled
}
}
Run Code Online (Sandbox Code Playgroud)
并使用类似:
struct ShowSheetView: View {
@State private var showSheet = true
var body: some View {
Text("Hello, World!")
.sheet(isPresented: $showSheet) {
TestView()
.modifier(DisableModalDismiss(disabled: true))
}
}
}
Run Code Online (Sandbox Code Playgroud)
小智 9
对于每个对 @Guido 的解决方案和 NavigationView 有问题的人。只需结合@Guido和@SlimeBaron的解决方案即可
class ModalHostingController<Content: View>: UIHostingController<Content>, UIAdaptivePresentationControllerDelegate {
var canDismissSheet = true
var onDismissalAttempt: (() -> ())?
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
parent?.presentationController?.delegate = self
}
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
canDismissSheet
}
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
onDismissalAttempt?()
}
}
struct ModalView<T: View>: UIViewControllerRepresentable {
let view: T
let canDismissSheet: Bool
let onDismissalAttempt: (() -> ())?
func makeUIViewController(context: Context) -> ModalHostingController<T> {
let controller = ModalHostingController(rootView: view)
controller.canDismissSheet = canDismissSheet
controller.onDismissalAttempt = onDismissalAttempt
return controller
}
func updateUIViewController(_ uiViewController: ModalHostingController<T>, context: Context) {
uiViewController.rootView = view
uiViewController.canDismissSheet = canDismissSheet
uiViewController.onDismissalAttempt = onDismissalAttempt
}
}
extension View {
func interactiveDismiss(canDismissSheet: Bool, onDismissalAttempt: (() -> ())? = nil) -> some View {
ModalView(
view: self,
canDismissSheet: canDismissSheet,
onDismissalAttempt: onDismissalAttempt
).edgesIgnoringSafeArea(.all)
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
struct ContentView: View {
@State var isPresented = false
@State var canDismissSheet = false
var body: some View {
Button("Tap me") {
isPresented = true
}
.sheet(
isPresented: $isPresented,
content: {
NavigationView {
Text("Hello World")
}
.interactiveDismiss(canDismissSheet: canDismissSheet) {
print("attemptToDismissHandler")
}
}
)
}
}
Run Code Online (Sandbox Code Playgroud)
从 iOS 15 开始我们可以使用interactiveDismissDisabled
:
func interactiveDismissDisabled(_ isDisabled: Bool = true) -> some View
Run Code Online (Sandbox Code Playgroud)
我们只需要把它附加到工作表上:
struct ContentView: View {
@State private var showSheet = false
var body: some View {
Text("Content View")
.sheet(isPresented: $showSheet) {
Text("Sheet View")
.interactiveDismissDisabled(true)
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果需要,您还可以传递一个变量来控制何时可以禁用工作表:
.interactiveDismissDisabled(!userAcceptedTermsOfUse)
Run Code Online (Sandbox Code Playgroud)
从 iOS 14 开始,如果您不想要关闭手势,则可以使用.fullScreenCover(isPresented:, content:)
( Docs )。.sheet(isPresented:, content:)
struct FullScreenCoverPresenterView: View {
@State private var isPresenting = false
var body: some View {
Button("Present Full-Screen Cover") {
isPresenting.toggle()
}
.fullScreenCover(isPresented: $isPresenting) {
Text("Tap to Dismiss")
.onTapGesture {
isPresenting.toggle()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
注意:fullScreenCover
在 macOS 上不可用,但在 iPhone 和 iPad 上运行良好。
注意:此解决方案不允许您在满足特定条件时启用关闭手势。要根据条件启用和禁用关闭手势,请参阅我的另一个答案。
归档时间: |
|
查看次数: |
707 次 |
最近记录: |