SwiftUI-具有全屏模式的PresentationButton

SwiftUI-具有全屏模式的PresentationButton,swiftui,xcode11,Swiftui,Xcode11,我正在尝试实现一个按钮,它用“Botton幻灯片”动画呈现另一个场景 PresentationButton看起来是个不错的候选人,所以我尝试了一下: import SwiftUI struct ContentView : View { var body: some View { NavigationView { PresentationButton(destination: Green().frame(width: 1000.0)) {

我正在尝试实现一个按钮,它用“Botton幻灯片”动画呈现另一个场景

PresentationButton看起来是个不错的候选人,所以我尝试了一下:

import SwiftUI

struct ContentView : View {
    var body: some View {
        NavigationView {
            PresentationButton(destination: Green().frame(width: 1000.0)) {
                Text("Click")

                }.navigationBarTitle(Text("Navigation"))
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
                .previewDevice("iPhone X")
                .colorScheme(.dark)

            ContentView()
                .colorScheme(.dark)
                .previewDevice("iPad Pro (12.9-inch) (3rd generation)"

            )

        }

    }
}
#endif
结果如下:

我希望绿色视图覆盖整个屏幕,并且模态不“可拖动关闭”

是否可以将修改器添加到PresentationButton以使其全屏显示,而不可拖动

我还尝试了导航按钮,但: -它不会“从底部滑动” -它在细节视图上创建了一个“后退按钮”,这是我不想要的

谢谢

不幸的是,从Beta 2 Beta 3开始,这在纯SwiftUI中是不可能的。您可以看到
Modal
用于任何类似
UIModalPresentationStyle.fullScreen
的内容。对我来说也是

我建议安装雷达

您当前可以执行的最接近的操作如下:

    @State var showModal: Bool = false
    var body: some View {
        NavigationView {
            Button(action: {
                self.showModal = true
            }) {
                Text("Tap me!")
            }
        }
        .navigationBarTitle(Text("Navigation!"))
        .overlay(self.showModal ? Color.green : nil)
    }

当然,从那里你可以在叠加中添加任何你喜欢的过渡。

虽然我的另一个答案目前是正确的,但人们可能希望现在就可以这样做。我们可以使用
环境
将视图控制器传递给子对象

struct ViewControllerHolder{
弱var值:UIViewController?
}
结构ViewControllerKey:EnvironmentKey{
静态变量defaultValue:ViewControllerHolder{返回ViewControllerHolder(值:UIApplication.shared.windows.first?.rootViewController)}
}
扩展环境值{
var viewController:UIViewControllerHolder{
获取{return self[ViewControllerKey.self]}
设置{self[ViewControllerKey.self]=newValue}
}
}
向UIViewController添加扩展

//HSHostingController.swift

import Foundation
import SwiftUI

class HSHostingControllerParams {
    static var nextModalPresentationStyle:UIModalPresentationStyle?
}

class HSHostingController<Content> : UIHostingController<Content> where Content : View {

    override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {

        if let nextStyle = HSHostingControllerParams.nextModalPresentationStyle {
            viewControllerToPresent.modalPresentationStyle = nextStyle
            HSHostingControllerParams.nextModalPresentationStyle = nil
        }

        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }

}
扩展UIViewController{ func present(样式:UIModalPresentationStyle=.automatic,@ViewBuilder:()->Content){ //必须用某种视图实例化HostingController。。。 让toPresent=UIHostingController(rootView:AnyView(EmptyView())) toPresent.modalPresentationStyle=样式 //…但是我们可以重置rootView以包含环境 toPresent.rootView=AnyView( 建筑商() .environment(\.viewController,ViewControllerHolder(值:toPresent)) ) self.present(toPresent,动画:true,完成:nil) } } 无论何时我们需要它,都要使用它:

struct MyView:View{
@环境(\.viewController)私有变量viewControllerHolder:viewControllerHolder
私有变量viewController:UIViewController{
self.viewControllerHolder.value
}
var body:一些观点{
按钮(操作:{
self.viewController?.present(样式:。全屏){
MyView()
}
}) {
文本(“介绍我!”)
}
}
}

[编辑]尽管最好执行类似于
@Environment(\.viewController)var viewController:UIViewController?
的操作,但这会导致一个保留周期。因此,您需要使用支架。

所以我一直在努力解决这个问题,我不喜欢覆盖功能和ViewController包装版本,因为它给了我一些内存错误,我对iOS非常陌生,只知道SwiftUI,不知道UIKit

我使用SwiftUI开发了以下内容,这可能是覆盖的功能,但出于我的目的,它更加灵活:

struct FullscreenModalView<Presenting, Content>: View where Presenting: View, Content: View {

    @Binding var isShowing: Bool
    let parent: () -> Presenting
    let content: () -> Content

    @inlinable public init(isShowing: Binding<Bool>, parent: @escaping () -> Presenting, @ViewBuilder content: @escaping () -> Content) {
        self._isShowing = isShowing
        self.parent = parent
        self.content = content
    }

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                self.parent().zIndex(0)
                if self.$isShowing.wrappedValue {
                    self.content()
                    .background(Color.primary.colorInvert())
                    .edgesIgnoringSafeArea(.all)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .transition(.move(edge: .bottom))
                    .zIndex(1)

                }
            }
        }
    }
}
用法: 使用自定义视图并将
showmodel
变量作为
绑定传递
,以从视图本身取消模式

struct ContentView : View {
    @State private var showModal: Bool = false
    var body: some View {
        ZStack {
            Button(action: {
                withAnimation {
                    self.showModal.toggle()
                }
            }, label: {
                HStack{
                   Image(systemName: "eye.fill")
                    Text("Calibrate")
                }
               .frame(width: 220, height: 120)
            })
        }
        .modal(isShowing: self.$showModal, content: {
            Text("Hallo")
        })
    }
}
我希望这有帮助


此版本修复了XCode 11.1中存在的编译错误,并确保以传入的样式显示控制器

import SwiftUI

struct ViewControllerHolder {
    weak var value: UIViewController?
}

struct ViewControllerKey: EnvironmentKey {
    static var defaultValue: ViewControllerHolder {
        return ViewControllerHolder(value: UIApplication.shared.windows.first?.rootViewController)

    }
}

extension EnvironmentValues {
    var viewController: UIViewController? {
        get { return self[ViewControllerKey.self].value }
        set { self[ViewControllerKey.self].value = newValue }
    }
}

extension UIViewController {
    func present<Content: View>(style: UIModalPresentationStyle = .automatic, @ViewBuilder builder: () -> Content) {
        let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
        toPresent.modalPresentationStyle = style
        toPresent.rootView = AnyView(
            builder()
                .environment(\.viewController, toPresent)
        )
        self.present(toPresent, animated: true, completion: nil)
    }
}
我的解决方案(您可以很容易地扩展它,以允许对所显示的工作表上的其他参数进行调整)是将UIHostingController子类化

//HSHostingController.swift

import Foundation
import SwiftUI

class HSHostingControllerParams {
    static var nextModalPresentationStyle:UIModalPresentationStyle?
}

class HSHostingController<Content> : UIHostingController<Content> where Content : View {

    override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {

        if let nextStyle = HSHostingControllerParams.nextModalPresentationStyle {
            viewControllerToPresent.modalPresentationStyle = nextStyle
            HSHostingControllerParams.nextModalPresentationStyle = nil
        }

        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }

}
然后,在触发工作表之前,只需告诉HSHostingControllerParams类您想要的演示样式

        .navigationBarItems(trailing:
            HStack {
                Button("About") {
                    HSHostingControllerParams.nextModalPresentationStyle = .fullScreen
                    self.showMenuSheet.toggle()
                }
            }
        )
通过类singleton传递参数感觉有点“脏”,但在实践中,您必须创建一个非常模糊的场景,以使其无法按预期工作

你可以随意处理环境变量之类的问题(正如其他答案所做的那样)——但对我来说,增加的复杂性不值得这么单纯


更新:有关具有附加功能的扩展解决方案,请参阅

现在可能了。使用fullScreenCover()修饰符

var body: some View {
    Button("Present!") {
        self.isPresented.toggle()
    }
    .fullScreenCover(isPresented: $isPresented, content: FullScreenModalView.init)
}

此代码中是否有错误?因为我尝试在Beta 6上使用您的要点,并且在
self.viewControllerHolder.value
MyView
中,编译器对成员“value(forKey:)”给出了错误
不明确的引用,所以我尝试将
self.viewControllerHolder?.value(forKey:)用作?UIViewController
出现错误时崩溃
valueForKey:]:该类不符合密钥的键值编码。
解决了问题:只需从
self.viewControllerHolder.value
中删除
.value
。然而,在Beta 7中,这转化为标准的
.sheet
模式,因此它不是真正的全屏。我建议在
扩展的
present
函数中添加
toPresent.modalPresentationStyle=style
,使新显示的屏幕实际上是全屏。要关闭
ViewController
,我将其传递到我的
视图
,然后调用
self.vc?。关闭(动画:true,completion:{})
但我怀疑这仍然会在内存中保留一些内容,因为当我再次调用它,然后触发
发布服务器上的更新,并通过
onReceive()捕获它时
当我打开并关闭了
ViewController
有什么想法吗?@arsenius 2019年12月的代码更新了吗?我得到一个“无法将'Environment'类型的值转换为指定的'UIViewContro'类型
        .navigationBarItems(trailing:
            HStack {
                Button("About") {
                    HSHostingControllerParams.nextModalPresentationStyle = .fullScreen
                    self.showMenuSheet.toggle()
                }
            }
        )
var body: some View {
    Button("Present!") {
        self.isPresented.toggle()
    }
    .fullScreenCover(isPresented: $isPresented, content: FullScreenModalView.init)
}