Я не смог найти никаких ссылок на способы создания поп или увольнятьпрограммно моего представленного представления с помощью SwiftUI.
Мне кажется, что единственный способ — использовать уже интегрированное действие слайдера для модального окна (и что/как, если я хочу отключить эту функцию?) и кнопку «Назад» для стека навигации.
Кто-нибудь знает решение? Вы не знаете, это баг или так и останется?
Теперь вы можете сделать это в бета-версии 5 как для навигации, так и для модальных окон. Смотрите мой ответ ниже.
Взгляните на этот проект с открытым исходным кодом: github.com/biobeats/swiftui-navigation-стек Это альтернативный стек навигации для SwiftUI, который, среди прочего, предлагает возможность программно вставлять/выталкивать. Было бы здорово, если бы вы, ребята, присоединились ко мне в улучшении этого проекта.
@ Андреа, ты смог это решить? Я все еще застрял здесь
Здесь вы можете найти самый простой ответ с примером ?: <br> stackoverflow.com/a/62863487/12534983
Лучший ответ сейчас — использовать navigationLink tag и selection с объектом среды, который отслеживает выбор. Затем вы можете установить объект среды selection на nil, чтобы вернуться к корневому представлению из любого дочернего представления.
Начиная с iOS 15 мы можем использовать DismissAction — см. этот ответ.
Ознакомьтесь с навигационной библиотекой SwiftUI github.com/canopas/UIPilot для удобной навигации. Он не заменяет NavigationView, но оборачивает NavigationView таким образом, что его действительно легко использовать.





Вы можете попробовать использовать пользовательский вид и 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)
}
}
Вы можете проверить это - в симуляторе, а не в превью - вот так:
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.
Спасибо, Маттео, я попробую это как можно скорее, это может быть крутой временный обходной путь, надеюсь, что Apple представит удаление и поп
Основная концепция 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
)
}
}
Что, если пользователь закроет модальное окно, проведя пальцем вниз? состояние остается в неправильном состоянии. И нет способа добавить слушателя к жесту прокрутки вниз. Я почти уверен, что в следующих выпусках будут расширены функции всплывающих и закрывающихся окон.
Попробуйте onDisappear(_:) ?
Теперь есть способ программно вставить NavigationView, если хотите. Это в бета-версии 5. Обратите внимание, что вам не нужна кнопка «Назад». Вы можете программно активировать свойство showSelf в DetailView любым удобным для вас способом. И вам не нужно отображать текст «Push» в мастере. Это может быть 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
Это вызывает у меня ошибки, если я нажимаю кнопку «Назад» в представлении навигации detailView вместо нажатия кнопки «всплывать». Есть идеи как исправить?
В тех случаях, когда вы используете этот метод, вы захотите скрыть кнопку «Назад», чтобы она не мешала вашему программному способу отображения представления. Не совсем исправление, но определенно способ избежать проблемы.
Я надеюсь, что бета 6 исправит проблему.
Чтобы добавить этот отличный ответ, если вы используете инициализацию tag: , selection: вместо isActive: , вы также можете передать этот выбор в качестве переменной привязки и установить для нее значение nil (или какое-либо значение, отличное от вашего тега), чтобы открыть представление.
Дополнение к моему комментарию. Это был большой урок для меня: чтобы иметь возможность выталкивать 2 или более, вам нужно добавить .isDetailLink(false) к корневой ссылке навигации. В противном случае выбор автоматически устанавливается равным нулю, когда появляется 3-е представление в стеке.
В этом примере используется новая переменная среды, описанная в примечаниях к выпуску бета-версии 5, в которой использовалось свойство value. В более поздней бета-версии он был изменен для использования свойства wrappedValue. Этот пример теперь актуален для версии GM. Точно такая же концепция работает для отклонения модальных представлений, представленных с модификатором .sheet.
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()
}
}
}
Это действительно хорошо! Я просто хотел бы, чтобы это работало и для навигации по двойной колонке, чтобы мы могли видеть боковую панель разделенного представления, когда пользователь запускает iPad в портретном режиме.
Я думаю, что это должен быть принятый ответ. Это очень чисто и не требует никаких изменений в родительском представлении.
Это отличное решение для iOS, которое, как я знаю, было основной целью OP. К сожалению, похоже, что это не работает для списков навигации macOS, где и список, и представление отображаются одновременно. Любой известный подход для этого?
Как вызвать RootView из кнопки?
Я думаю, это то, что вы хотите: stackoverflow.com/questions/57334455/…
@mohsin - Что именно вы имеете в виду, говоря, что это не работает? Попробуйте создать новый проект SwiftUI для iOS и замените структуру ContentView всем кодом из моего ответа выше. Кажется, он все еще работает нормально.
ваш код работает нормально, но когда я попытался сделать то же самое в своем проекте. Он не работает, а также не выдает никаких ошибок. Я использую xcode 11.5
Извините, но вы должны дать нам нечто большее, чем комментарий о том, что «у меня это не работает». Публикация вашего собственного нового вопроса с реальным образцом кода, который не работает, поможет нам оказать вам необходимую помощь.
Это работает очень хорошо, однако я не вижу никакого перехода. Вид просто мгновенно исчезает.
Помогает ли это: stackoverflow.com/a/59183031/899918
У меня возникла проблема с компилятором при попытке вызвать value привязку PresentationMode. Изменение свойства на wrappedValue устранило проблему для меня. Я предполагаю, что value -> wrappedValue — это обновление языка. Я думаю, что это примечание было бы более подходящим в качестве комментария к ответу Чака Х, но у меня недостаточно очков репутации для комментариев. Я также предложил это изменение как и отредактировать, но мое редактирование было отклонено как более подходящее в качестве комментария или ответа.
SwiftUI Xcode Бета 5
Во-первых, объявите @Environment, у которого есть метод отклонения, который вы можете использовать где угодно, чтобы закрыть представление.
import SwiftUI
struct GameView: View {
@Environment(\.presentationMode) var presentation
var body: some View {
Button("Done") {
self.presentation.wrappedValue.dismiss()
}
}
}
Потрясающий; простейшее решение. Должен быть наверху.
Работает на iOS 13, Swift 5. Хорошее простое решение!
В качестве альтернативы, если вы не хотите делать это программно с помощью кнопки, вы можете испускать из модели представления всякий раз, когда вам нужно всплывать. Подпишитесь на @Published, который изменяет значение всякий раз, когда выполняется сохранение.
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-стек), который содержит NavigationStackView, альтернативный стек навигации для SwiftUI. Он предлагает несколько функций, описанных в файле readme репозитория. Например, вы можете легко добавлять и извлекать представления программно. Я покажу вам, как это сделать, на простом примере:
Прежде всего вставьте свою иерархию в 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")
})
}
}
}
}
В этом примере я использую PushView для запуска push-навигации одним касанием. Затем в View2 я использую объект среды, чтобы программно вернуться.
Вот полный пример:
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()
}
}
результат:
Только что попробовал это, и это фантастика, это намного надежнее, чем встроенный стек SwiftUI. Я рекурсивно помещаю до 20 копий экрана в стек, и встроенный запутывается, ваш NavigationStack отлично справляется с этим.
Как мы можем вывести только 2 экрана, используя self.navStack.pop()
@AliRehman Если вы хотите перейти к определенному представлению (а не только к предыдущему), вы должны указать этому представлению идентификатор. Например: PushView(destination: Child0(), destinationId: "destinationId") { Text("PUSH") }, а затем PopView(destination: .view(withId: "destinationId")) { Text("POP") }. То же самое, если вы программно получаете доступ к объекту среды стека навигации.
@matteopuc Спасибо, я звонил два раза, чтобы открыть 2 экрана. как self.navStack.pop(); self.navStack.pop(); и он работал, теперь обновил его в соответствии с вашим предложением!
Пожалуйста, проверьте следующий код, это так просто.
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)
}
}
}
}
}
Сейчас это лучший ответ, но обратите внимание, что вместо использования PresentationMode вы можете просто передать объект среды, который отслеживает выбор, и установить для выбора значение nil из любого последующего дочернего представления, чтобы вернуться к корневому представлению.
Начиная с iOS 15 мы можем использовать новый @Environment(\.dismiss):
struct SheetView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
Text("Sheet")
.toolbar {
Button("Done") {
dismiss()
}
}
}
}
}
(Больше нет необходимости использовать presentationMode.wrappedValue.dismiss().)
Полезные ссылки:
Как относительный новичок в Swift, я логически не понимаю этот шаблон вообще. Но это работает, и мое приложение будет только для iOS 15, так что СПАСИБО!
Учитывая текущий статус API, вам придется реализовать эти переходы самостоятельно.