SwiftUI-具有全屏模式的PresentationButton
我正在尝试实现一个按钮,它用“Botton幻灯片”动画呈现另一个场景 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)) {
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)
}