SwiftUI - PresentationButton с модальным полноэкранным режимом
Я пытаюсь реализовать кнопку, которая представляет другую сцену с анимацией "Slide from 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, чтобы сделать его полноэкранным, а не перетаскиваемым?
Я также попробовал навигационную кнопку, но:
- не "скользит снизу"
- Он создает "кнопку назад" в подробном представлении, которое я не хочу
Спасибо!
Ответы
Ответ 1
К сожалению, что касается Beta 2 Beta 3, это не возможно в чистом SwiftUI. Вы можете видеть, что Modal
не имеет параметров для чего-либо подобного UIModalPresentationStyle.fullScreen
. Аналогично для PresentationButton.
Я предлагаю подать радар.
Самое близкое, что вы можете сделать сейчас, это что-то вроде:
@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)
}
Конечно, оттуда вы можете добавить любой переход в оверлей, который вам нравится.
Ответ 2
Хотя мой другой ответ в настоящее время правильный, люди, вероятно, хотят сделать это сейчас. Мы можем использовать Environment
, чтобы передать контроллер представления детям. Суть здесь
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: UIViewControllerHolder {
get { return self[ViewControllerKey.self] }
set { self[ViewControllerKey.self] = newValue }
}
}
Добавить расширение для UIViewController
extension UIViewController {
func present<Content: View>(style: UIModalPresentationStyle = .automatic, @ViewBuilder builder: () -> Content) {
// Must instantiate HostingController with some sort of view...
let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
... but then we can reset that view to include the environment
toPresent.rootView = AnyView(
builder()
.environment(\.viewController, ViewControllerHolder(value: toPresent))
)
self.present(toPresent, animated: true, completion: nil)
}
}
И всякий раз, когда нам это нужно, используйте его:
struct MyView: View {
@Environment(\.viewController) private var viewControllerHolder: UIViewController?
private var viewController: UIViewController? {
self.viewControllerHolder.value
}
var body: some View {
Button(action: {
self.viewController?.present(style: .fullScreen) {
MyView()
}
}) {
Text("Present me!")
}
}
}
[РЕДАКТИРОВАТЬ] Хотя было бы предпочтительнее сделать что-то вроде @Environment(\.viewController) var viewController: UIViewController?
, это приведет к сохранению цикла. Поэтому вам нужно использовать держатель.
Ответ 3
Так что я боролся с этим, и мне не понравилась функция наложения, а также версия в оболочке ViewController, поскольку она дала мне некоторую ошибку памяти, и я очень плохо знаком с iOS и знаю только SwiftUI и никакой UIKit.
Я разработал титры следующего с помощью только SwiftUI, что, вероятно, и делает оверлей, но для моих целей он гораздо более гибкий:
struct FullscreenModalView<Presenting, Content>: View where Presenting: View, Content: View {
var isShowing: Binding<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()
ZStack {
self.content()
VStack {
HStack {
Spacer()
Button(action: {
withAnimation {
self.isShowing.wrappedValue.toggle()
}
}, label: {
Text("done_button").frame(width: 200, height: 120)
})
.frame(width: 200, height: 120)
}
Spacer()
}
}
.frame(width: geometry.size.width, height: geometry.size.height)
.animation(Animation.easeInOut(duration: 0.3))
.background(Color.primary.colorInvert())
.offset(x: 0, y: self.isShowing.wrappedValue ? 0 : geometry.size.height + geometry.safeAreaInsets.top)
}
}
}
}
Добавление расширения к View
:
extension View {
func modal<Content>(isShowing: Binding<Bool>, @ViewBuilder content: @escaping () -> Content) -> some View where Content: View {
FullscreenModalView(isShowing: isShowing, parent: { self }, content: content)
}
}
Использование:
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")
})
}
}
Надеюсь, это поможет!
Привет krjw
Ответ 4
Эта версия исправляет ошибку компиляции, присутствующую в 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)
}
}
Чтобы использовать эту версию, код не отличается от предыдущей версии.
struct MyView: View {
@Environment(\.viewController) private var viewControllerHolder: UIViewController?
private var viewController: UIViewController? {
self.viewControllerHolder.value
}
var body: some View {
Button(action: {
self.viewController?.present(style: .fullScreen) {
MyView()
}
}) {
Text("Present me!")
}
}
}