SwiftUI - есть ли аналог PopViewController в SwiftUI?

Я играл с SwiftUI и хочу иметь возможность вернуться к предыдущему виду при нажатии кнопки, так же, как мы используем popViewController внутри UINavigationController. Есть ли способ сделать это до сих пор?

Я также пытался использовать NavigationDestinationLink чтобы сделать это без успеха.

struct AView: View {
    var body: some View {
        NavigationView {
            NavigationButton(destination: BView()) {
                Text("Go to B")
            }
        }
    }
}

struct BView: View {
    var body: some View {
        Button(action: {
            // Trying to go back to the previous view
            // previously: navigationController.popViewController(animated: true)
        }) {
            Text("Come back to A")
        }
    }
}

Ответы

Ответ 1

Измените вашу структуру BView следующим образом. Кнопка будет работать так же, как popViewController в UIKit.

struct BView: View {
    @Environment(\.presentationMode) var mode: Binding<PresentationMode>
    var body: some View {
        Button(action: { self.mode.wrappedValue.dismiss() })
        { Text("Come back to A") }
    }
}

Ответ 2

Кажется, что тонна базовых навигационных функций супер глючит, что вызывает разочарование и, возможно, на данный момент стоит отойти, чтобы сэкономить часы разочарования. Для меня PresentationButton - единственный, который работает. Вкладки TabbedView не работают должным образом, и NavigationButton не работает для меня вообще. Похоже, YMMV, если NavigationButton работает для вас.

Я надеюсь, что они исправят это одновременно с исправлением автозаполнения, что даст нам гораздо лучшее представление о том, что нам доступно. В то же время я неохотно пишу код и веду записи, когда появятся исправления. Отстойно, когда приходится выяснять, делаем ли мы что-то неправильно или просто не работает, но эта бета-версия для вас!

Ответ 3

Теперь есть возможность программно вставлять в NavigationView, если хотите. Это бета-версия 5.

Обратите внимание, что вам не нужна кнопка назад. Вы можете программно вызвать свойство showSelf в DetailView любым удобным для вас способом. И вам не нужно отображать текст "Push" в мастере. Это может быть EmptyView(), что создает невидимый переход.

(Новая функциональность NavigationLink принимает устаревший NavigationDestinationLink)

import SwiftUI

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

struct MasterView: View {
    @State 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

Ответ 4

Обновление: API NavigationDestinationLink в этом решении устарело начиная с iOS 13 Beta 5. Теперь рекомендуется использовать NavigationLink с привязкой isActive.

Я разобрался с решением для программного перетаскивания представлений в NavigationView, используя NavigationDestinationLink.

Вот простой пример:

import Combine
import SwiftUI

struct DetailView: View {
    var onDismiss: () -> Void

    var body: some View {
        Button(
            "Here are details. Tap to go back.",
            action: self.onDismiss
        )
    }
}

struct MainView: View {
    var link: NavigationDestinationLink<DetailView>
    var publisher: AnyPublisher<Void, Never>

    init() {
        let publisher = PassthroughSubject<Void, Never>()
        self.link = NavigationDestinationLink(
            DetailView(onDismiss: { publisher.send() }),
            isDetail: false
        )
        self.publisher = publisher.eraseToAnyPublisher()
    }

    var body: some View {
        VStack {
            Button("I am root. Tap for more details.", action: {
                self.link.presented?.value = true
            })
        }
            .onReceive(publisher, perform: { _ in
                self.link.presented?.value = false
            })
    }
}

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

Я написал об этом в блоге здесь.

Ответ 5

Кажется, это работает для меня на watchOS (еще не пытался на iOS):

@Environment(\.presentationMode) var presentationMode

А потом, когда вам нужно выскочить

self.presentationMode.wrappedValue.dismiss()

Ответ 6

Ниже у меня работает в XCode11 GM

self.myPresentationMode.wrappedValue.dismiss()

Ответ 7

Я думаю, что это правильный способ создания модальных представлений в SwiftUI. Вы также можете использовать элемент трейлинг-бара или оба одновременно. Нажатие на кнопку предоставит пользователю недопустимый модальный вид.

struct ContentView: View {

var body: some View {
    NavigationView {

        // define some parts of your view, e.g. a rectangle
        Rectangle()
        .navigationBarTitle(Text("SomeTitle"),
                            displayMode:.inline )
        .navigationBarItems(
            leading: PresentationButton(
                // add some image to the button
                Image(systemName: "gear")
                    .imageScale(.large)
                    .accessibility(label: Text("Recommended accessibility label")),
                // specify the destination, which is any view conforming to View protocol
                destination: Text("Name of new view")
            ))
    }
}

}

Ответ 8

РЕДАКТИРОВАТЬ: Этот ответ здесь лучше, чем мой, но оба работают: SwiftUI отклонить модальный

То, что вы действительно хотите (или должны хотеть), - это модальная презентация, о которой несколько человек упомянули здесь. Если вы пойдете по этому пути, вам определенно потребуется возможность программно отклонить модал, и у Эрики Садун есть отличный пример того, как это сделать здесь: https://ericasadun.com/2019/06/16/swiftui-modal -презентация/

Учитывая разницу между декларативным кодированием и императивным кодированием, решение там может быть неочевидным (например, переключение bool в false для исключения модального режима), но имеет смысл, если ваше состояние модели является источником истины, а не состояние самого интерфейса.

Вот мой быстрый пример Эрики, использующий привязку, переданную в TestModal, чтобы он мог отклонить себя, не будучи членом самого ContentView (как Эрика, для простоты).

struct TestModal: View {
    @State var isPresented: Binding<Bool>

    var body: some View {
        Button(action: { self.isPresented.value = false }, label: { Text("Done") })
    }
}

struct ContentView : View {
    @State var modalPresented = false

    var body: some View {
        NavigationView {
            Text("Hello World")
            .navigationBarTitle(Text("View"))
            .navigationBarItems(trailing:
                Button(action: { self.modalPresented = true }) { Text("Show Modal") })
        }
        .presentation(self.modalPresented ? Modal(TestModal(isPresented: $modalPresented)) {
            self.modalPresented.toggle()
        } : nil)
    }
}

Ответ 9

вместо NavigationButton используйте Navigation DestinationLink

но вы должны импортировать Combine

struct AView: View {
 var link: NavigationDestinationLink<BView>
var publisher: AnyPublisher<Void, Never>

init() {
    let publisher = PassthroughSubject<Void, Never>()
    self.link = NavigationDestinationLink(
        BView(onDismiss: { publisher.send() }),
        isDetail: false
    )
    self.publisher = publisher.eraseToAnyPublisher()
}

var body: some View {
    NavigationView {
        Button(action:{
        self.link.presented?.value = true


 }) {
            Text("Go to B")
        }.onReceive(publisher, perform: { _ in
            self.link.presented?.value = false
        })
    }
}
}

struct BView: View {
var onDismiss: () -> Void
var body: some View {
    Button(action: self.onDismiss) {
        Text("Come back to A")
    }
}
}

Ответ 10

В пункте назначения передайте представление, которое вы хотите перенаправить, а внутри блока передайте данные, которые вы передадите в другое представление.

NavigationLink(destination: "Pass the particuter View") {
    Text("Push")
}

Ответ 11

Возможно, если вы попытаетесь встроить этот последний BView в первый Aview чуть ниже предпоследней фигурной скобки... просто так:

struct AView: View {
    var body: some View {
        NavigationView {
            NavigationButton(destination: BView()) {
                Text("Go to B")
            }
        }
    }
}

struct BView: View {
    var body: some View {
        Button(action: {
            // Trying to go back to the previous view
            // previously: navigationController.popViewController(animated: true)
        }) {
            Text("Come back to A")
        }
    }
}