IOS SwiftUI: открывать или закрывать представление программно

Я не смог найти никаких ссылок на способы создания поп или увольнятьпрограммно моего представленного представления с помощью SwiftUI.

Мне кажется, что единственный способ — использовать уже интегрированное действие слайдера для модального окна (и что/как, если я хочу отключить эту функцию?) и кнопку «Назад» для стека навигации.

Кто-нибудь знает решение? Вы не знаете, это баг или так и останется?

Учитывая текущий статус API, вам придется реализовать эти переходы самостоятельно.

Matteo Pacini 09.06.2019 12:05

Теперь вы можете сделать это в бета-версии 5 как для навигации, так и для модальных окон. Смотрите мой ответ ниже.

Chuck H 30.07.2019 22:32

Взгляните на этот проект с открытым исходным кодом: github.com/biobeats/swiftui-navigation-стек Это альтернативный стек навигации для SwiftUI, который, среди прочего, предлагает возможность программно вставлять/выталкивать. Было бы здорово, если бы вы, ребята, присоединились ко мне в улучшении этого проекта.

matteopuc 04.02.2020 16:19

@ Андреа, ты смог это решить? Я все еще застрял здесь

mohsin 07.07.2020 18:54

Здесь вы можете найти самый простой ответ с примером ?: <br> stackoverflow.com/a/62863487/12534983

Sapar Friday 12.07.2020 18:25

Лучший ответ сейчас — использовать navigationLink tag и selection с объектом среды, который отслеживает выбор. Затем вы можете установить объект среды selection на nil, чтобы вернуться к корневому представлению из любого дочернего представления.

Super Noob 07.09.2020 07:48

Начиная с iOS 15 мы можем использовать DismissAction — см. этот ответ.

pawello2222 16.06.2021 00:50

Ознакомьтесь с навигационной библиотекой SwiftUI github.com/canopas/UIPilot для удобной навигации. Он не заменяет NavigationView, но оборачивает NavigationView таким образом, что его действительно легко использовать.

jimmy0251 24.02.2022 11:37
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
110
8
68 736
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Вы можете попробовать использовать пользовательский вид и 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 представит удаление и поп

Andrea Miotto 09.06.2019 12:26

Основная концепция 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
        )
    }
}

Что, если пользователь закроет модальное окно, проведя пальцем вниз? состояние остается в неправильном состоянии. И нет способа добавить слушателя к жесту прокрутки вниз. Я почти уверен, что в следующих выпусках будут расширены функции всплывающих и закрывающихся окон.

Andrea Miotto 16.06.2019 17:54

Попробуйте onDisappear(_:) ?

WeZZard 18.06.2019 03:23

Теперь есть способ программно вставить 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 вместо нажатия кнопки «всплывать». Есть идеи как исправить?

MobileMon 19.08.2019 15:35

В тех случаях, когда вы используете этот метод, вы захотите скрыть кнопку «Назад», чтобы она не мешала вашему программному способу отображения представления. Не совсем исправление, но определенно способ избежать проблемы.

MScottWaller 19.08.2019 20:39

Я надеюсь, что бета 6 исправит проблему.

MobileMon 19.08.2019 20:48

Чтобы добавить этот отличный ответ, если вы используете инициализацию tag: , selection: вместо isActive: , вы также можете передать этот выбор в качестве переменной привязки и установить для нее значение nil (или какое-либо значение, отличное от вашего тега), чтобы открыть представление.

AlexMath 08.07.2020 19:45

Дополнение к моему комментарию. Это был большой урок для меня: чтобы иметь возможность выталкивать 2 или более, вам нужно добавить .isDetailLink(false) к корневой ссылке навигации. В противном случае выбор автоматически устанавливается равным нулю, когда появляется 3-е представление в стеке.

AlexMath 08.07.2020 22:43
Ответ принят как подходящий

В этом примере используется новая переменная среды, описанная в примечаниях к выпуску бета-версии 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 в портретном режиме.

MScottWaller 08.08.2019 22:14

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

Maetthu24 16.10.2019 09:20

Это отличное решение для iOS, которое, как я знаю, было основной целью OP. К сожалению, похоже, что это не работает для списков навигации macOS, где и список, и представление отображаются одновременно. Любой известный подход для этого?

TheNeil 16.12.2019 18:05

Как вызвать RootView из кнопки?

J A S K I E R 04.05.2020 02:42

Я думаю, это то, что вы хотите: stackoverflow.com/questions/57334455/…

Chuck H 04.05.2020 19:48

@mohsin - Что именно вы имеете в виду, говоря, что это не работает? Попробуйте создать новый проект SwiftUI для iOS и замените структуру ContentView всем кодом из моего ответа выше. Кажется, он все еще работает нормально.

Chuck H 07.07.2020 20:51

ваш код работает нормально, но когда я попытался сделать то же самое в своем проекте. Он не работает, а также не выдает никаких ошибок. Я использую xcode 11.5

mohsin 08.07.2020 07:42

Извините, но вы должны дать нам нечто большее, чем комментарий о том, что «у меня это не работает». Публикация вашего собственного нового вопроса с реальным образцом кода, который не работает, поможет нам оказать вам необходимую помощь.

Chuck H 08.07.2020 18:11

Это работает очень хорошо, однако я не вижу никакого перехода. Вид просто мгновенно исчезает.

alionthego 25.03.2021 00:16

Помогает ли это: stackoverflow.com/a/59183031/899918

Chuck H 26.03.2021 19:48

У меня возникла проблема с компилятором при попытке вызвать 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()
        }
    }
}

Потрясающий; простейшее решение. Должен быть наверху.

ty1 03.03.2020 05:47

Работает на iOS 13, Swift 5. Хорошее простое решение!

user3069232 04.05.2020 11:59

В качестве альтернативы, если вы не хотите делать это программно с помощью кнопки, вы можете испускать из модели представления всякий раз, когда вам нужно всплывать. Подпишитесь на @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 отлично справляется с этим.

Brett 02.03.2020 00:29

Как мы можем вывести только 2 экрана, используя self.navStack.pop()

AliRehman7141 24.02.2021 13:00

@AliRehman Если вы хотите перейти к определенному представлению (а не только к предыдущему), вы должны указать этому представлению идентификатор. Например: PushView(destination: Child0(), destinationId: "destinationId") { Text("PUSH") }, а затем PopView(destination: .view(withId: "destinationId")) { Text("POP") }. То же самое, если вы программно получаете доступ к объекту среды стека навигации.

matteopuc 24.02.2021 17:49

@matteopuc Спасибо, я звонил два раза, чтобы открыть 2 экрана. как self.navStack.pop(); self.navStack.pop(); и он работал, теперь обновил его в соответствии с вашим предложением!

AliRehman7141 09.03.2021 05:04

Пожалуйста, проверьте следующий код, это так просто.

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 из любого последующего дочернего представления, чтобы вернуться к корневому представлению.

Super Noob 07.09.2020 07:45

iOS 15

Начиная с 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, так что СПАСИБО!

Jason Farnsworth 22.08.2021 06:01

Другие вопросы по теме