IOS SwiftUI: 以编程方式弹出或解除视图

我找不到任何关于如何使用 SwiftUI 制作我的视图的 爸爸解散 程序化的参考资料。

在我看来,唯一的方法是使用模态已经集成的幻灯片下拉动作(如果我想禁用这个功能,又该怎么办?),以及导航堆栈的后退按钮。

有人知道怎么解决吗? 你知道这是虫子还是会一直这样?

88016 次浏览

您可以尝试使用自定义视图和 Transition

这是一个自定义模式。

struct ModalView<Content>: View where Content: View {


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


var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
if (!self.isShowing) {
self.content()
}
if (self.isShowing) {
self.content()
.disabled(true)
.blur(radius: 3)


VStack {
Text("Modal")
}
.frame(width: geometry.size.width / 2,
height: geometry.size.height / 5)
.background(Color.secondary.colorInvert())
.foregroundColor(Color.primary)
.cornerRadius(20)
.transition(.moveAndFade) // associated transition to the modal view
}
}
}
}


}

我重用了 动画视图与过渡教程中的 Transition.moveAndFade

它的定义是这样的:

extension AnyTransition {
static var moveAndFade: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
.combined(with: .opacity)
let removal = AnyTransition.scale()
.combined(with: .opacity)
return .asymmetric(insertion: insertion, removal: removal)
}
}

You can test it - in the simulator, not in the preview - like this:

struct ContentView: View {


@State var isShowingModal: Bool = false


func toggleModal() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation {
self.isShowingModal = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation {
self.isShowingModal = false
}
}
}
}


var body: some View {
ModalView(isShowing: $isShowingModal) {
NavigationView {
List(["1", "2", "3", "4", "5"].identified(by: \.self)) { row in
Text(row)
}.navigationBarTitle(Text("A List"), displayMode: .large)
}.onAppear { self.toggleModal() }
}
}


}

由于这个转换,您将看到模态 sliding in from the trailing edge,它将 zoom and fade out when it is dismissed

SwiftUI 的核心概念是监视数据流。

你必须使用一个 @State变量,并变化这个变量的值,以控制弹出和解雇。

struct MyView: View {
@State
var showsUp = false


var body: some View {
Button(action: { self.showsUp.toggle() }) {
Text("Pop")
}
.presentation(
showsUp ? Modal(
Button(action: { self.showsUp.toggle() }) {
Text("Dismiss")
}
) : nil
)
}
}


如果您愿意,现在有一种以编程方式弹出 NavigationView 的方法。这是测试版5。注意,您不需要后退按钮。您可以以任何喜欢的方式在 DetailView 中以编程方式触发 showSelf 属性。而且你不必在主控中显示“推”文本。这可能是 EmptyView () ,从而创建一个不可见的转折点。

import SwiftUI


struct ContentView: View {
var body: some View {
NavigationView {
MasterView()
}
}
}


struct MasterView: View {
@State private var showDetail = false


var body: some View {
VStack {
NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
Text("Push")
}
}
}
}


struct DetailView: View {
@Binding var showSelf: Bool


var body: some View {
Button(action: {
self.showSelf = false
}) {
Text("Pop")
}
}
}


#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif

此示例使用 Beta 5发行说明中记录的新环境变量,该变量使用 value 属性。在后来的测试版中,它被更改为使用 wrappedValue 属性。这个示例现在是 GM 版本的最新示例。这个完全相同的概念可以用来忽略。片材改良剂片材改良剂。

import SwiftUI


struct DetailView: View {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
Button(
"Here is Detail View. Tap to go back.",
action: { self.presentationMode.wrappedValue.dismiss() }
)
}
}


struct RootView: View {
var body: some View {
VStack {
NavigationLink(destination: DetailView())
{ Text("I am Root. Tap for Detail View.") }
}
}
}


struct ContentView: View {
var body: some View {
NavigationView {
RootView()
}
}
}

我遇到了一个编译器问题,它试图在 presationMode 绑定上调用 value。将属性更改为 wrappedValue为我修复了这个问题。我假设 value-> wrappedValue是语言更新。我认为这个注释更适合作为对 Chuck H 的回答的评论,但是没有足够的代表点来评论,我也建议这个改变作为和编辑,但是我的编辑被拒绝作为一个评论或答案更合适。

SwiftUI Xcode Beta 5

首先,声明@Environment,它拥有一个可以在任何地方使用的解雇方法来解雇视图。

import SwiftUI


struct GameView: View {
    

@Environment(\.presentationMode) var presentation
    

var body: some View {
Button("Done") {
self.presentation.wrappedValue.dismiss()
}
}
}

Alternatively, if you don't want to do it programatically from a button, you can emit from the view model whenever you need to pop. Subscribe to a @Published that changes the value whenever the saving is done.

struct ContentView: View {
@ObservedObject var viewModel: ContentViewModel
@Environment(\.presentationMode) var presentationMode


init(viewModel: ContentViewModel) {
self.viewModel = viewModel
}


var body: some View {
Form {
TextField("Name", text: $viewModel.name)
.textContentType(.name)
}
.onAppear {
self.viewModel.cancellable = self.viewModel
.$saved
.sink(receiveValue: { saved in
guard saved else { return }
self.presentationMode.wrappedValue.dismiss()
}
)
}
}
}


class ContentViewModel: ObservableObject {
@Published var saved = false // This can store any value.
@Published var name = ""
var cancellable: AnyCancellable? // You can use a cancellable set if you have multiple observers.


func onSave() {
// Do the save.


// Emit the new value.
saved = true
}
}

我最近创建了一个名为 swiftui-navigation-stack(https://github.com/biobeats/swiftui-navigation-stack)的开源项目,其中包含 NavigationStackView,这是 SwiftUI 的一个替代导航栈。它提供了在回购自述中描述的几个特性。例如,可以通过编程方式轻松地推送和弹出视图。我将通过一个简单的例子向您展示如何做到这一点:

首先,把你的层次结构嵌入到 NavigationStackVew中:

struct RootView: View {
var body: some View {
NavigationStackView {
View1()
}
}
}

NavigationStackView使您的层次结构能够访问名为 NavigationStack的有用环境对象。你可以使用它,例如,以编程方式弹出视图,如上面的问题所示:

struct View1: View {
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 1")
Spacer()


PushView(destination: View2()) {
Text("PUSH TO VIEW 2")
}
}
}
}
}


struct View2: View {
@EnvironmentObject var navStack: NavigationStack
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 2")
Spacer()


Button(action: {
self.navStack.pop()
}, label: {
Text("PROGRAMMATICALLY POP TO VIEW 1")
})
}
}
}
}

In this example I use the PushView to trigger the push navigation with a tap. Then, in the View2 I use the environment object to programmatically come back.

下面是一个完整的例子:

import SwiftUI
import NavigationStack


struct RootView: View {
var body: some View {
NavigationStackView {
View1()
}
}
}


struct View1: View {
var body: some View {
ZStack {
Color.yellow.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 1")
Spacer()


PushView(destination: View2()) {
Text("PUSH TO VIEW 2")
}
}
}
}
}


struct View2: View {
@EnvironmentObject var navStack: NavigationStack
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Text("VIEW 2")
Spacer()


Button(action: {
self.navStack.pop()
}, label: {
Text("PROGRAMMATICALLY POP TO VIEW 1")
})
}
}
}
}


struct ContentView_Previews: PreviewProvider {
static var previews: some View {
RootView()
}
}

the result is:

enter image description here

Please check Following Code it's so simple.

FirstView

struct StartUpVC: View {
@State var selection: Int? = nil


var body: some View {
NavigationView{
NavigationLink(destination: LoginView().hiddenNavigationBarStyle(), tag: 1, selection: $selection) {
Button(action: {
print("Signup tapped")
self.selection = 1
}) {
HStack {
Spacer()
Text("Sign up")
Spacer()
}
}
}
}
}

SecondView

struct LoginView: View {
@Environment(\.presentationMode) var presentationMode
    

var body: some View {
NavigationView{
Button(action: {
print("Login tapped")
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("Back")
.resizable()
.frame(width: 20, height: 20)
.padding(.leading, 20)
}
}
}
}
}

IOS15 +

从 iOS15开始,我们可以使用新的 @Environment(\.dismiss):

struct SheetView: View {
@Environment(\.dismiss) var dismiss


var body: some View {
NavigationView {
Text("Sheet")
.toolbar {
Button("Done") {
dismiss()
}
}
}
}
}

(不再需要使用 presentationMode.wrappedValue.dismiss()。)


有用连结:

这也会让人们忽视这种观点

       let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
                

window?.rootViewController?.dismiss(animated: true, completion: {
print("dismissed")
})