bestsource

Swift를 사용하여 루트 보기로 팝업하려면 어떻게 해야 합니까?UI?

bestsource 2023. 4. 19. 23:18
반응형

Swift를 사용하여 루트 보기로 팝업하려면 어떻게 해야 합니까?UI?

마지막으로 베타 5에서 상위 보기로 프로그래밍 방식으로 팝업할 수 있습니다.그러나 내 앱에는 여러 단계 과정을 마치고 처음으로 돌아가는 "저장" 버튼이 있는 곳이 몇 군데 있습니다.UIKit에서는 popToRootViewController()를 사용하고 있습니다만, Swift에서도 같은 방법을 찾을 수 없습니다.UI.

다음은 제가 달성하려는 패턴의 간단한 예입니다.

어떻게 해야 하죠?

import SwiftUI

struct DetailViewB: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var body: some View {
        VStack {
            Text("This is Detail View B.")

            Button(action: { self.presentationMode.value.dismiss() } )
            { Text("Pop to Detail View A.") }

            Button(action: { /* How to do equivalent to popToRootViewController() here?? */ } )
            { Text("Pop two levels to Master View.") }

        }
    }
}

struct DetailViewA: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var body: some View {
        VStack {
            Text("This is Detail View A.")

            NavigationLink(destination: DetailViewB() )
            { Text("Push to Detail View B.") }

            Button(action: { self.presentationMode.value.dismiss() } )
            { Text("Pop one level to Master.") }
        }
    }
}

struct MasterView: View {
    var body: some View {
        VStack {
            Text("This is Master View.")

            NavigationLink(destination: DetailViewA() )
            { Text("Push to Detail View A.") }
        }
    }
}

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

설정 " " "isDetailLink로로 합니다.false NavigationLinkpop-to-root로 하기 입니다. isDetailLinktrue는 기본적으로 포함되어 있는 보기에 맞게 조정됩니다.되고 "iPad"는 "Split view"와 "Split view는 "Split view"와 "iPad"로 구분됩니다.isDetailLink아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 맞다. ★★isDetailLink로로 합니다.false따라서 행선지 뷰는 항상 네비게이션스택에 푸시되므로 언제든지 팝오프할 수 있습니다.

「 」의 설정과 isDetailLink로로 합니다.falseNavigationLink을합니다.isActive이후의 각 행선지 뷰에 바인드 합니다.으로 루트 뷰로 때을 '루트 뷰'로 합니다.false을 사용법

import SwiftUI

struct ContentView: View {
    @State var isActive : Bool = false

    var body: some View {
        NavigationView {
            NavigationLink(
                destination: ContentView2(rootIsActive: self.$isActive),
                isActive: self.$isActive
            ) {
                Text("Hello, World!")
            }
            .isDetailLink(false)
            .navigationBarTitle("Root")
        }
    }
}

struct ContentView2: View {
    @Binding var rootIsActive : Bool

    var body: some View {
        NavigationLink(destination: ContentView3(shouldPopToRootView: self.$rootIsActive)) {
            Text("Hello, World #2!")
        }
        .isDetailLink(false)
        .navigationBarTitle("Two")
    }
}

struct ContentView3: View {
    @Binding var shouldPopToRootView : Bool

    var body: some View {
        VStack {
            Text("Hello, World #3!")
            Button (action: { self.shouldPopToRootView = false } ){
                Text("Pop to root")
            }
        }.navigationBarTitle("Three")
    }
}

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

화면 캡처

물론 malhal이 솔루션의 키를 가지고 있지만, Binding을 View에 매개 변수로 전달하는 것은 현실적이지 않습니다.환경은 Imthath가 지적한 바와 같이 훨씬 더 좋은 방법이다.

다음은 이전 View로 팝업하는 Apple의 게시된 ofcept() 메서드를 본뜬 또 다른 접근법입니다.

환경에 대한 확장을 정의합니다.

struct RootPresentationModeKey: EnvironmentKey {
    static let defaultValue: Binding<RootPresentationMode> = .constant(RootPresentationMode())
}

extension EnvironmentValues {
    var rootPresentationMode: Binding<RootPresentationMode> {
        get { return self[RootPresentationModeKey.self] }
        set { self[RootPresentationModeKey.self] = newValue }
    }
}

typealias RootPresentationMode = Bool

extension RootPresentationMode {
    
    public mutating func dismiss() {
        self.toggle()
    }
}

용도:

  1. .environment(\.rootPresentationMode, self.$isPresented)NavigationView서, snowledge.isPresentedBool첫 번째 아이 뷰를 표시하는 데 사용됩니다.

  2. " " " 를 합니다..navigationViewStyle(StackNavigationViewStyle()) NavigationView 「」를 추가합니다..isDetailLink(false)NavigationLink첫 번째 어린이 보기를 위해.

  3. @Environment(\.rootPresentationMode) private var rootPresentationMode팝을 수행해야 하는 모든 하위 보기로 이동합니다.

  4. '아까부터'를 합니다.self.rootPresentationMode.wrappedValue.dismiss()해당 하위 보기에서 루트 보기로 이동합니다.

GitHub에 완전한 작업 예를 게재했습니다.

Swift(Swift)부터 UI를 UINavigation Controller로 .UINavigation Controller(UINavigation Controller)입니다.popToRootViewController(animated:).UINavigation Controller는 다음과 같습니다.

struct NavigationUtil {
  static func popToRootView() {
    findNavigationController(viewController: UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController)?
      .popToRootViewController(animated: true)
  }

  static func findNavigationController(viewController: UIViewController?) -> UINavigationController? {
    guard let viewController = viewController else {
      return nil
    }

    if let navigationController = viewController as? UINavigationController {
      return navigationController
    }

    for childViewController in viewController.children {
      return findNavigationController(viewController: childViewController)
    }

    return nil
  }
}

그리고 이렇게 사용하세요.

struct ContentView: View {
    var body: some View {
      NavigationView { DummyView(number: 1) }
    }
}

struct DummyView: View {
  let number: Int

  var body: some View {
    VStack(spacing: 10) {
      Text("This is view \(number)")
      NavigationLink(destination: DummyView(number: number + 1)) {
        Text("Go to view \(number + 1)")
      }
      Button(action: { NavigationUtil.popToRootView() }) {
        Text("Or go to root view!")
      }
    }
  }
}

이 문제에 대한 Apple의 솔루션을 소개합니다.

또한 Hacking With Swift(이걸 훔쳤다는 뜻)를 통해 프로그래밍 방식의 네비게이션 아래 다음과 같이 표시되었습니다.

(Xcode 12 및 iOS 14에서 테스트 완료)

으로는 ★★★★★★★★★★★★★★★★★★★★★★★.tag ★★★★★★★★★★★★★★★★★」selection에 inside inside inside navigationlink원하는 페이지로 바로 이동할 수 있습니다.

struct ContentView: View {
    @State private var selection: String? = nil

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: Text("Second View"), tag: "Second", selection: $selection) { EmptyView() }
                NavigationLink(destination: Text("Third View"), tag: "Third", selection: $selection) { EmptyView() }
                Button("Tap to show second") {
                    self.selection = "Second"
                }
                Button("Tap to show third") {
                    self.selection = "Third"
                }
            }
            .navigationBarTitle("Navigation")
        }
    }
}

'어울리지 않다'를 사용할 수 요.@environmentobjectinjected injected injected into ContentView()택을처 처리: :

class NavigationHelper: ObservableObject {
    @Published var selection: String? = nil
}

앱에 주입:

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(NavigationHelper())
        }
    }
}

사용할 수 있습니다.

struct ContentView: View {
    @EnvironmentObject var navigationHelper: NavigationHelper

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: Text("Second View"), tag: "Second", selection: $navigationHelper.selection) { EmptyView() }
                NavigationLink(destination: Text("Third View"), tag: "Third", selection: $navigationHelper.selection) { EmptyView() }
                Button("Tap to show second") {
                    self.navigationHelper.selection = "Second"
                }
                Button("Tap to show third") {
                    self.navigationHelper.selection = "Third"
                }
            }
            .navigationBarTitle("Navigation")
        }
    }
}

, 「」를 만 하면 .navigationHelper.selection = nil.

원하지 않을 경우 태그와 선택 항목을 후속 하위 탐색 링크에 사용할 필요가 없습니다. 단, 특정 탐색 링크로 이동하는 기능은 없습니다.

현재 베타 5에서는 쉽게 할 수 있는 방법이 없습니다.내가 찾은 유일한 방법은 매우 해킹하는 것이지만, 효과가 있다.

기본적으로 DetailViewB에서 트리거되는 퍼블리셔를 DetailViewA에 추가합니다.DetailViewB에서 보기를 해제하고 게시자에게 알립니다. 게시자는 DetailViewA를 닫습니다.

    struct DetailViewB: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var publisher = PassthroughSubject<Void, Never>()

    var body: some View {
        VStack {
            Text("This is Detail View B.")

            Button(action: { self.presentationMode.value.dismiss() } )
            { Text("Pop to Detail View A.") }

            Button(action: {
                DispatchQueue.main.async {
                self.presentationMode.wrappedValue.dismiss()
                self.publisher.send()
                }
            } )
            { Text("Pop two levels to Master View.") }

        }
    }
}

struct DetailViewA: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var publisher = PassthroughSubject<Void, Never>()

    var body: some View {
        VStack {
            Text("This is Detail View A.")

            NavigationLink(destination: DetailViewB(publisher:self.publisher) )
            { Text("Push to Detail View B.") }

            Button(action: { self.presentationMode.value.dismiss() } )
            { Text("Pop one level to Master.") }
        }
        .onReceive(publisher, perform: { _ in
            DispatchQueue.main.async {
                print("Go Back to Master")
                self.presentationMode.wrappedValue.dismiss()
            }
        })
    }
}

그리고 베타 6에는 아직 해결책이 없습니다.

루트로 돌아갈 수 있는 다른 방법을 찾았는데 이번에는 애니메이션을 잃고 루트로 직행하고 있어요.루트 뷰를 강제로 새로 고치면 네비게이션스택이 정리됩니다.

그러나 Swift에서는 내비게이션 스택 관리를 이용할 수 없기 때문에 궁극적으로 애플만이 적절한 솔루션을 제공할 수 있었다.UI.

주의: 아래 알림에 의한 심플한 솔루션은 watch OS가 아닌 iOS 상에서 동작합니다.OS는 2개의 네비게이션레벨이 끝나면 메모리에서 루트뷰를 클리어합니다.하지만 감시 대상 상태를 관리하는 외부 클래스가 있습니다.OS는 정상적으로 동작합니다.

struct DetailViewB: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @State var fullDissmiss:Bool = false
    var body: some View {
        SGNavigationChildsView(fullDissmiss: self.fullDissmiss){
            VStack {
                Text("This is Detail View B.")

                Button(action: { self.presentationMode.wrappedValue.dismiss() } )
                { Text("Pop to Detail View A.") }

                Button(action: {
                    self.fullDissmiss = true
                } )
                { Text("Pop two levels to Master View with SGGoToRoot.") }
            }
        }
    }
}

struct DetailViewA: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @State var fullDissmiss:Bool = false
    var body: some View {
        SGNavigationChildsView(fullDissmiss: self.fullDissmiss){
            VStack {
                Text("This is Detail View A.")

                NavigationLink(destination: DetailViewB() )
                { Text("Push to Detail View B.") }

                Button(action: { self.presentationMode.wrappedValue.dismiss() } )
                { Text("Pop one level to Master.") }

                Button(action: { self.fullDissmiss = true } )
                { Text("Pop one level to Master with SGGoToRoot.") }
            }
        }
    }
}

struct MasterView: View {
    var body: some View {
        VStack {
            Text("This is Master View.")
            NavigationLink(destination: DetailViewA() )
            { Text("Push to Detail View A.") }
        }
    }
}

struct ContentView: View {

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

struct SGRootNavigationView<Content>: View where Content: View {
    let cancellable = NotificationCenter.default.publisher(for: Notification.Name("SGGoToRoot"), object: nil)

    let content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    @State var goToRoot:Bool = false

    var body: some View {
        return
            Group{
            if goToRoot == false{
                NavigationView {
                content()
                }
            }else{
                NavigationView {
                content()
                }
            }
            }.onReceive(cancellable, perform: {_ in
                DispatchQueue.main.async {
                    self.goToRoot.toggle()
                }
            })
    }
}

struct SGNavigationChildsView<Content>: View where Content: View {
    let notification = Notification(name: Notification.Name("SGGoToRoot"))

    var fullDissmiss:Bool{
        get{ return false }
        set{ if newValue {self.goToRoot()} }
    }

    let content: () -> Content

    init(fullDissmiss:Bool, @ViewBuilder content: @escaping () -> Content) {
        self.content = content
        self.fullDissmiss = fullDissmiss
    }

    var body: some View {
        return Group{
            content()
        }
    }

    func goToRoot(){
        NotificationCenter.default.post(self.notification)
    }
}

루트 뷰로 바로 이동할 수 있는 간단한 해결책을 찾아냈어요.알림을 보내고 Navigation View의 ID를 변경하라는 알림을 수신합니다. 그러면 Navigation View가 새로 고쳐집니다.애니메이션은 없지만 보기 좋다.다음은 예를 제시하겠습니다.

@main
struct SampleApp: App {
    @State private var navigationId = UUID()

    var body: some Scene {
        WindowGroup {
            NavigationView {
                Screen1()
            }
            .id(navigationId)
            .onReceive(NotificationCenter.default.publisher(for: Notification.Name("popToRootView"))) { output in
                navigationId = UUID()
            }
        }
    }
}

struct Screen1: View {
    var body: some View {
        VStack {
            Text("This is screen 1")
            NavigationLink("Show Screen 2", destination: Screen2())
        }
    }
}

struct Screen2: View {
    var body: some View {
        VStack {
            Text("This is screen 2")
            Button("Go to Home") {
                NotificationCenter.default.post(name: Notification.Name("popToRootView"), object: nil)
            }
        }
    }
}

나는 스위프트에서 복잡한 내비게이션을 사용하는 방법을 알아냈다.UI. 뷰의 모든 상태를 수집하여 표시 여부를 확인하는 것이 중요합니다.

먼저 Navigation Controller를 정의합니다.탭뷰 탭의 선택 항목과 특정 뷰가 표시되는지 여부를 나타내는 부울 값을 추가했습니다.

import SwiftUI

final class NavigationController: ObservableObject  {

  @Published var selection: Int = 1

  @Published var tab1Detail1IsShown = false
  @Published var tab1Detail2IsShown = false

  @Published var tab2Detail1IsShown = false
  @Published var tab2Detail2IsShown = false
}

탭뷰를 2개의 탭으로 설정하고 Navigation Controller를 바인드합니다.탭 보기로 선택:

import SwiftUI

struct ContentView: View {

  @EnvironmentObject var nav: NavigationController

  var body: some View {

    TabView(selection: self.$nav.selection) {

      FirstMasterView()
      .tabItem {
        Text("First")
      }
      .tag(0)

      SecondMasterView()
      .tabItem {
        Text("Second")
      }
      .tag(1)
    }
  }

}

예를 들어, 이것은 1개의 NavigationStacks 입니다.

import SwiftUI

struct FirstMasterView: View {

  @EnvironmentObject var nav: NavigationController

  var body: some View {
    NavigationView {
      VStack {

        NavigationLink(destination: FirstDetailView(), isActive: self.$nav.tab1Detail1IsShown) {
          Text("go to first detail")
        }
      } .navigationBarTitle(Text("First MasterView"))
    }
  }
}

struct FirstDetailView: View {

  @EnvironmentObject var nav: NavigationController
  @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

  var body: some View {

    VStack(spacing: 20) {
      Text("first detail View").font(.title)

      NavigationLink(destination: FirstTabLastView(), isActive: self.$nav.tab1Detail2IsShown) {
        Text("go to last detail on nav stack")
      }

      Button(action: {
        self.nav.tab2Detail1IsShown = false // true will go directly to detail
        self.nav.tab2Detail2IsShown = false

        self.nav.selection = 1
      }) {
        Text("Go to second tab")
      }
    }

    // In case of collapsing all the way back
    // there is a bug with the environment object
    // to go all the way back I have to use the presentationMode
    .onReceive(self.nav.$tab1Detail2IsShown, perform: { (out) in
      if out ==  false {
        self.presentationMode.wrappedValue.dismiss()
      }
    })
  }
}

struct FirstTabLastView: View {
  @EnvironmentObject var nav: NavigationController

  var body: some View {
    Button(action: {
      self.nav.tab1Detail1IsShown = false
      self.nav.tab1Detail2IsShown = false
    }) {
      Text("Done and go back to beginning of navigation stack")
    }
  }
}

이 접근방식은 매우 신속합니다.UI-state 지향.

IOS 16 솔루션

으로 새로 할 수 .NavigationStack

struct DataObject: Identifiable, Hashable {
    let id = UUID()
    let name: String
}

@available(iOS 16.0, *)
struct ContentView8: View {
    @State private var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
            Text("Root Pop")
                .font(.largeTitle)
                .foregroundColor(.primary)
            
            NavigationLink("Click Item", value: DataObject.init(name: "Item"))
            
            .listStyle(.plain)
            .navigationDestination(for: DataObject.self) { course in
                Text(course.name)
                NavigationLink("Go Deeper", value: DataObject.init(name: "Item"))
                Button("Back to root") {
                    path = NavigationPath()
                }
            }
        }
        .padding()
    }
}

저는 Swift에서 네비게이션하기 위해 UI, 【Swift】【Swift】【Swift】. »UINavigationControllerSceneDelegate네비게이션 뷰

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        UINavigationBar.appearance().tintColor = .black

        let contentView = OnBoardingView()
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            let hostingVC = UIHostingController(rootView: contentView)
            let mainNavVC = UINavigationController(rootViewController: hostingVC)
            mainNavVC.navigationBar.isHidden = true
            window.rootViewController = mainNavVC
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

이 '과 '확장'을 .HasRootNavigationController

import SwiftUI
import UIKit

protocol HasRootNavigationController {
    var rootVC:UINavigationController? { get }

    func push<Content:View>(view: Content, animated:Bool)
    func setRootNavigation<Content:View>(views:[Content], animated:Bool)
    func pop(animated: Bool)
    func popToRoot(animated: Bool)
}

extension HasRootNavigationController where Self:View {

    var rootVC:UINavigationController? {
        guard let scene = UIApplication.shared.connectedScenes.first,
            let sceneDelegate = scene as? UIWindowScene,
            let rootvc = sceneDelegate.windows.first?.rootViewController
                as? UINavigationController else { return nil }
        return rootvc
    }

    func push<Content:View>(view: Content, animated:Bool = true) {
        rootVC?.pushViewController(UIHostingController(rootView: view), animated: animated)
    }

    func setRootNavigation<Content:View>(views: [Content], animated:Bool = true) {
        let controllers =  views.compactMap { UIHostingController(rootView: $0) }
        rootVC?.setViewControllers(controllers, animated: animated)
    }

    func pop(animated:Bool = true) {
        rootVC?.popViewController(animated: animated)
    }

    func popToRoot(animated: Bool = true) {
        rootVC?.popToRootViewController(animated: animated)
    }
}

후, 의 Swift my after after after my my after after after after after after after View, View를 .HasRootNavigationController and extension (프로토콜 및 )

extension YouSwiftUIView:HasRootNavigationController {

    func switchToMainScreen() {
        self.setRootNavigation(views: [MainView()])
    }

    func pushToMainScreen() {
         self.push(view: [MainView()])
    }

    func goBack() {
         self.pop()
    }

    func showTheInitialView() {
         self.popToRoot()
    }
}

업데이트 시 코드 요지는 다음과 같습니다.https://gist.github.com/michaelhenry/945fc63da49e960953b72bbc567458e6

Malhal의 @Binding 솔루션 덕분에 저는 이 솔루션이 부족하다는 것을 알게 되었습니다..isDetailLink(false)수식어

제 경우 이후의 모든 뷰에서 @Binding을 사용하고 싶지 않습니다.

이것이 Environment Object를 사용하고 있는 솔루션입니다.

1: 「」1: 「」을 합니다.AppStateObservable Object(관찰 객체)

import SwiftUI
import Combine

class AppState: ObservableObject {
    @Published var moveToDashboard: Bool = false
}

스텝 2: 인스턴스를 생성하여 SceneDelegate추가합니다.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Create the SwiftUI view that provides the window contents.
    let contentView = ContentView()
    let appState = AppState()

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView:
            contentView
                .environmentObject(appState)
        )
        self.window = window
        window.makeKeyAndVisible()
    }
}

3: ★★의 3:★★ContentView.swift

는 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★appState입니다.이 은, 「」를 사용합니다..onReceive()하여 contentView를 .isActiveNavigation Link(네비게이션 링크)는 false(거짓)입니다.

한 것은 '우리'를 사용하는 입니다..isDetailLink(false)네비게이션 링크그렇지 않으면 작동하지 않습니다.

import SwiftUI
import Combine

class AppState: ObservableObject {
    @Published var moveToDashboard: Bool = false
}

struct ContentView: View {
    @EnvironmentObject var appState: AppState
    @State var isView1Active: Bool = false

    var body: some View {
        NavigationView {
            VStack {
                Text("Content View")
                    .font(.headline)

                NavigationLink(destination: View1(), isActive: $isView1Active) {
                    Text("View 1")
                        .font(.headline)
                }
                .isDetailLink(false)
            }
            .onReceive(self.appState.$moveToDashboard) { moveToDashboard in
                if moveToDashboard {
                    print("Move to dashboard: \(moveToDashboard)")
                    self.isView1Active = false
                    self.appState.moveToDashboard = false
                }
            }
        }
    }
}

// MARK:- View 1
struct View1: View {

    var body: some View {
        VStack {
            Text("View 1")
                .font(.headline)
            NavigationLink(destination: View2()) {
                Text("View 2")
                    .font(.headline)
            }
        }
    }
}

// MARK:- View 2
struct View2: View {
    @EnvironmentObject var appState: AppState

    var body: some View {
        VStack {
            Text("View 2")
                .font(.headline)
            Button(action: {
                self.appState.moveToDashboard = true
            }) {
                Text("Move to Dashboard")
                .font(.headline)
            }
        }
    }
}


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

여기에 이미지 설명을 입력하십시오.

이 솔루션은 malhal의 답변을 기반으로 ImthathFlorin Odagiu제안을 사용하여 Paul Hudson의 Navigation View 비디오를 통해 모든 것을 통합해야 했습니다.

아이디어는 매우 간단하다.navigationLink의 isActive 파라미터는 탭 시 true로 설정됩니다.그러면 두 번째 보기가 나타납니다.추가 링크를 사용하여 보기를 추가할 수 있습니다.루트로 돌아가려면 isActive를 false로 설정합니다.두 번째 보기와 쌓였을 수 있는 다른 보기는 사라집니다.

import SwiftUI

class Views: ObservableObject {
    @Published var stacked = false
}

struct ContentView: View {
    @ObservedObject var views = Views()

    var body: some View {
        NavigationView {
            NavigationLink(destination: ContentView2(), isActive: self.$views.stacked) {
                Text("Go to View 2") // Tapping this link sets stacked to true
            }
            .isDetailLink(false)
            .navigationBarTitle("ContentView")
        }
        .environmentObject(views) // Inject a new views instance into the navigation view environment so that it's available to all views presented by the navigation view.
    }
}

struct ContentView2: View {

    var body: some View {
        NavigationLink(destination: ContentView3()) {
            Text("Go to View 3")
        }
        .isDetailLink(false)
        .navigationBarTitle("View 2")
    }
}

struct ContentView3: View {
    @EnvironmentObject var views: Views

    var body: some View {

        Button("Pop to root") {
            self.views.stacked = false // By setting this to false, the second view that was active is no more. Which means, the content view is being shown once again.
        }
        .navigationBarTitle("View 3")
    }
}

이것은 x0randgat3의 답변에 대한 업데이트로, 여러 번 기능합니다.NavigationViews의 범위 내에서TabView.

struct NavigationUtil {
  static func popToRootView() {
    findNavigationController(viewController: UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController)?
      .popToRootViewController(animated: true)
  }

  static func findNavigationController(viewController: UIViewController?) -> UINavigationController? {
    guard let viewController = viewController else {
      return nil
    }

    if let navigationController = viewController as? UITabBarController {
      return findNavigationController(viewController: navigationController.selectedViewController)
    }

    if let navigationController = viewController as? UINavigationController {
      return navigationController
    }

    for childViewController in viewController.children {
      return findNavigationController(viewController: childViewController)
    }

    return nil
  }
}

다음은 Xcode 11 및 iOS 13.1에서 유효한 onAppear를 사용한 느린 애니메이션 및 약간 거친 역방향 팝 솔루션입니다.

import SwiftUI
import Combine


struct NestedViewLevel3: View {
    @Binding var resetView:Bool
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var body: some View {
        VStack {
            Spacer()
            Text("Level 3")
            Spacer()
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Back")
                    .padding(.horizontal, 15)
                    .padding(.vertical, 2)
                    .foregroundColor(Color.white)
                    .clipped(antialiased: true)
                    .background(
                        RoundedRectangle(cornerRadius: 20)
                            .foregroundColor(Color.blue)
                            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
                )}
            Spacer()
            Button(action: {
                self.$resetView.wrappedValue = true
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Reset")
                    .padding(.horizontal, 15)
                    .padding(.vertical, 2)
                    .foregroundColor(Color.white)
                    .clipped(antialiased: true)
                    .background(
                        RoundedRectangle(cornerRadius: 20)
                            .foregroundColor(Color.blue)
                            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
                )}
            Spacer()
        }
        .navigationBarBackButtonHidden(false)
        .navigationBarTitle("Level 3", displayMode: .inline)
        .onAppear(perform: {print("onAppear level 3")})
        .onDisappear(perform: {print("onDisappear level 3")})
    }
}

struct NestedViewLevel2: View {
    @Binding var resetView:Bool
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var body: some View {
        VStack {
            Spacer()
            NavigationLink(destination: NestedViewLevel3(resetView:$resetView)) {
                Text("To level 3")
                    .padding(.horizontal, 15)
                    .padding(.vertical, 2)
                    .foregroundColor(Color.white)
                    .clipped(antialiased: true)
                    .background(
                        RoundedRectangle(cornerRadius: 20)
                            .foregroundColor(Color.gray)
                            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
                )
                    .shadow(radius: 10)
            }
            Spacer()
            Text("Level 2")
            Spacer()
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Back")
                    .padding(.horizontal, 15)
                    .padding(.vertical, 2)
                    .foregroundColor(Color.white)
                    .clipped(antialiased: true)
                    .background(
                        RoundedRectangle(cornerRadius: 20)
                            .foregroundColor(Color.blue)
                            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
                )}
            Spacer()
        }
        .navigationBarBackButtonHidden(false)
        .navigationBarTitle("Level 2", displayMode: .inline)
        .onAppear(perform: {
            print("onAppear level 2")
            if self.$resetView.wrappedValue {
                self.presentationMode.wrappedValue.dismiss()
            }
        })
        .onDisappear(perform: {print("onDisappear level 2")})
    }
}

struct NestedViewLevel1: View {
    @Binding var resetView:Bool
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var body: some View {
        VStack {
            Spacer()
            NavigationLink(destination: NestedViewLevel2(resetView:$resetView)) {
                Text("To level 2")
                    .padding(.horizontal, 15)
                    .padding(.vertical, 2)
                    .foregroundColor(Color.white)
                    .clipped(antialiased: true)
                    .background(
                        RoundedRectangle(cornerRadius: 20)
                            .foregroundColor(Color.gray)
                            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
                )
                    .shadow(radius: 10)
            }
            Spacer()
            Text("Level 1")
            Spacer()
            Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Back")
                    .padding(.horizontal, 15)
                    .padding(.vertical, 2)
                    .foregroundColor(Color.white)
                    .clipped(antialiased: true)
                    .background(
                        RoundedRectangle(cornerRadius: 20)
                            .foregroundColor(Color.blue)
                            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
            )}
            Spacer()
        }
        .navigationBarBackButtonHidden(false)
        .navigationBarTitle("Level 1", displayMode: .inline)
        .onAppear(perform: {
            print("onAppear level 1")
            if self.$resetView.wrappedValue {
                self.presentationMode.wrappedValue.dismiss()
            }
        })
        .onDisappear(perform: {print("onDisappear level 1")})
    }
}

struct RootViewLevel0: View {
    @Binding var resetView:Bool
    var body: some View {
        NavigationView {
            VStack {
                Spacer()
                NavigationLink(destination: NestedViewLevel1(resetView:$resetView)) {
                Text("To level 1")
                    .padding(.horizontal, 15)
                    .padding(.vertical, 2)
                    .foregroundColor(Color.white)
                    .clipped(antialiased: true)
                    .background(
                        RoundedRectangle(cornerRadius: 20)
                        .foregroundColor(Color.gray)
                        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 40, alignment: .center)
                    )
                    .shadow(radius: 10)
                }
                //.disabled(false)
                //.hidden()
                Spacer()
            }
        }
        //.frame(width:UIScreen.main.bounds.width,height:  UIScreen.main.bounds.height - 110)
        .navigationBarTitle("Root level 0", displayMode: .inline)
        .navigationBarBackButtonHidden(false)
        .navigationViewStyle(StackNavigationViewStyle())
        .onAppear(perform: {
            print("onAppear root level 0")
            self.resetNavView()
        })
        .onDisappear(perform: {print("onDisappear root level 0")})
    }

    func resetNavView(){
        print("resetting objects")
        self.$resetView.wrappedValue = false
    }

}


struct ContentView: View {
    @State var resetView = false
    var body: some View {
        RootViewLevel0(resetView:$resetView)
    }
}

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

제 해결책은 이렇습니다.IT는 의존하지 않고 어디서나 작동합니다.

let window = UIApplication.shared.connectedScenes
  .filter { $0.activationState == .foregroundActive }
  .map { $0 as? UIWindowScene }
  .compactMap { $0 }
  .first?.windows
  .filter { $0.isKeyWindow }
  .first
let nvc = window?.rootViewController?.children.first as? UINavigationController
nvc?.popToRootViewController(animated: true)

내비게이션 뷰 키트

import NavigationViewKit
NavigationView {
    List(0..<10) { _ in
        NavigationLink("abc", destination: DetailView())
    }
}
.navigationViewManager(for: "nv1", afterBackDo: {print("back to root") })

NavigationView의 모든 뷰:

@Environment(\.navigationManager) var nvmanager

Button("back to root view") {
    nvmanager.wrappedValue.popToRoot(tag:"nv1") {
        print("other back")
    }
}

보기에서 호출하지 않고 Notification Center를 통해 호출할 수도 있습니다.

let backToRootItem = NavigationViewManager.BackToRootItem(tag: "nv1", animated: false, action: {})
NotificationCenter.default.post(name: .NavigationViewManagerBackToRoot, object: backToRootItem)

효과가 있는 다른 기술을 생각해 냈는데 아직도 이상한 느낌이 들어요.두 화면 모두 애니메이션으로 표시되지만 조금 더 깔끔합니다.A) 클로저를 후속 상세 화면으로 넘기거나 B) 상세 내역 B를 전달할 수 있습니다.presentationMode 상상 B를 하고 detailA를 에 복귀할 까지 잠시 시켜야 합니다.둘 다 세부 정보 B를 해제하고 세부 정보 A를 해제하기 전에 세부 정보 A가 화면에 다시 표시되도록 잠시 지연시켜야 합니다.

let minDelay = TimeInterval(0.001)

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink("Push Detail A", destination: DetailViewA())
            }.navigationBarTitle("Root View")
        }
    }
}

struct DetailViewA: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    var body: some View {
        VStack {
            Spacer()

            NavigationLink("Push Detail With Closure",
                           destination: DetailViewWithClosure(dismissParent: { self.dismiss() }))

            Spacer()

            NavigationLink("Push Detail with Parent Binding",
                           destination: DetailViewWithParentBinding(parentPresentationMode: self.presentationMode))

            Spacer()

        }.navigationBarTitle("Detail A")
    }

    func dismiss() {
        print ("Detail View A dismissing self.")
        presentationMode.wrappedValue.dismiss()
    }
}

struct DetailViewWithClosure: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @State var dismissParent: () -> Void

    var body: some View {
        VStack {
            Button("Pop Both Details") { self.popParent() }
        }.navigationBarTitle("Detail With Closure")
    }

    func popParent() {
        presentationMode.wrappedValue.dismiss()
        DispatchQueue.main.asyncAfter(deadline: .now() + minDelay) { self.dismissParent() }
    }
}

struct DetailViewWithParentBinding: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @Binding var parentPresentationMode: PresentationMode

    var body: some View {
        VStack {
            Button("Pop Both Details") { self.popParent() }
        }.navigationBarTitle("Detail With Binding")
    }

    func popParent() {
        presentationMode.wrappedValue.dismiss()
        DispatchQueue.main.asyncAfter(deadline: .now() + minDelay) { self.parentPresentationMode.dismiss() }
    }
}

Swift가 어떻게 했는지를 생각하면 할수록UI가 동작하고, 사물의 구조가 구조화될수록, 애플이 제공하는 것과 동등한 것은 줄어들 것입니다.popToRootViewController또는 네비게이션 스택을 직접 편집합니다.그것은 Swift의 길을 거스른다.UI는 하위 보기가 상위 상태에 도달하여 이를 조작할 수 있도록 하기 때문에 보기 구조를 구축합니다.이것이 바로 이러한 접근법이 하는 일입니다. 하지만 그들은 그것을 명백하고 노골적으로 합니다. DetailViewA는, 자신의 상태에의 액세스를 제공하지 않고는, 어느쪽의 행선지 뷰도 작성할 수 없습니다.즉, 작성자는, 그 액세스의 제공의 의미를 심사숙고할 필요가 있습니다.

세부 사항

  • Xcode 버전 13.2.1 (13C100), Swift 5.5

솔루션

링크 리스트

https://github.com/raywenderlich/swift-algorithm-club/blob/master/Linked%20List/LinkedList.swift

네비게이션 스택

import SwiftUI
import Combine

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MARK: Custom NavigationLink
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

final class CustomNavigationLinkViewModel<CustomViewID>: ObservableObject where CustomViewID: Equatable {
  private weak var navigationStack: NavigationStack<CustomViewID>?
  /// `viewId` is used to find a `CustomNavigationLinkViewModel` in the `NavigationStack`
  let viewId = UUID().uuidString
  
  /// `customId` is used to mark a `CustomNavigationLink` in the `NavigationStack`. This is kind of external id.
  /// In `NavigationStack` we always prefer to use `viewId`. But from time to time we need to implement `pop several views`
  /// and that is the purpose of the `customId`
  /// Developer can just create a link with `customId` e.g. `navigationStack.navigationLink(customId: "123") { .. }`
  /// And to pop directly to  view `"123"` should use `navigationStack.popToLast(customId: "123")`
  let customId: CustomViewID?

  @Published var isActive = false {
    didSet { navigationStack?.updated(linkViewModel: self) }
  }

  init (navigationStack: NavigationStack<CustomViewID>, customId: CustomViewID? = nil) {
    self.navigationStack = navigationStack
    self.customId = customId
  }
}

extension CustomNavigationLinkViewModel: Equatable {
  static func == (lhs: CustomNavigationLinkViewModel, rhs: CustomNavigationLinkViewModel) -> Bool {
    lhs.viewId == rhs.viewId && lhs.customId == rhs.customId
  }
}

struct CustomNavigationLink<Label, Destination, CustomViewID>: View where Label: View, Destination: View, CustomViewID: Equatable {

  /// Link `ViewModel` where all states are stored
  @StateObject var viewModel: CustomNavigationLinkViewModel<CustomViewID>

  let destination: () -> Destination
  let label: () -> Label

  var body: some View {
    NavigationLink(isActive: $viewModel.isActive, destination: destination, label: label)
  }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MARK: NavigationStack
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class NavigationStack<CustomViewID>: ObservableObject where CustomViewID: Equatable {
  
  typealias Link = WeakReference<CustomNavigationLinkViewModel<CustomViewID>>
  private var linkedList = LinkedList<Link>()

  func navigationLink<Label, Destination>(customId: CustomViewID? = nil,
                                          @ViewBuilder destination: @escaping () -> Destination,
                                          @ViewBuilder label: @escaping () -> Label)
  -> some View where Label: View, Destination: View {
    createNavigationLink(customId: customId, destination: destination, label: label)
  }
  
  private func createNavigationLink<Label, Destination>(customId: CustomViewID? = nil,
                                                        @ViewBuilder destination: @escaping () -> Destination,
                                                        @ViewBuilder label: @escaping () -> Label)
  -> CustomNavigationLink<Label, Destination, CustomViewID> where Label: View, Destination: View {
    .init(viewModel: CustomNavigationLinkViewModel(navigationStack: self, customId: customId),
          destination: destination,
          label: label)
  }
}

// MARK: Nested Types

extension NavigationStack {
  /// To avoid retain cycle it is important to store weak reference to the `CustomNavigationLinkViewModel`
  final class WeakReference<T> where T: AnyObject {
    private(set) weak var weakReference: T?
    init(value: T) { self.weakReference = value }
    deinit { print("deinited WeakReference") }
  }
}

// MARK: Searching

extension NavigationStack {
  private func last(where condition: (Link) -> Bool) -> LinkedList<Link>.Node? {
    var node = linkedList.last
    while(node != nil) {
      if let node = node, condition(node.value) {
        return node
      }
      node = node?.previous
    }
    return nil
  }
}

// MARK: Binding

extension NavigationStack {
  fileprivate func updated(linkViewModel: CustomNavigationLinkViewModel<CustomViewID>) {
    guard linkViewModel.isActive else {
      switch linkedList.head?.value.weakReference {
      case nil: break
      case linkViewModel: linkedList.removeAll()
      default:
        last (where: { $0.weakReference === linkViewModel })?.previous?.next = nil
      }
      return
    }
    linkedList.append(WeakReference(value: linkViewModel))
  }
}

// MARK: pop functionality

extension NavigationStack {
  func popToRoot() {
    linkedList.head?.value.weakReference?.isActive = false
  }
  
  func pop() {
    linkedList.last?.value.weakReference?.isActive = false
  }
  
  func popToLast(customId: CustomViewID) {
    last (where: { $0.weakReference?.customId == customId })?.value.weakReference?.isActive = false
  }
}

#if DEBUG

extension NavigationStack {
  var isEmpty: Bool { linkedList.isEmpty }
  var count: Int { linkedList.count }
  func testCreateNavigationLink<Label, Destination>(viewModel: CustomNavigationLinkViewModel<CustomViewID>,
                                                    @ViewBuilder destination: @escaping () -> Destination,
                                                    @ViewBuilder label: @escaping () -> Label)
  -> CustomNavigationLink<Label, Destination, CustomViewID> where Label: View, Destination: View {
    .init(viewModel: viewModel, destination: destination, label: label)
  }
  
}
#endif

사용방법(짧은 샘플)

Navigation Link 만들기:

struct Page: View {
    @EnvironmentObject var navigationStack: NavigationStack<String>
    var body: some View {
        navigationStack.navigationLink {
            NextView(...)
        } label: {
            Text("Next page")
        }
    }
}

팝 기능

struct Page: View {
    @EnvironmentObject var navigationStack: NavigationStack<String>
    var body: some View {
        Button("Pop") {
            navigationStack.pop()
        }
        Button("Pop to Page 1") {
            navigationStack.popToLast(customId: "1")
        }
        Button("Pop to root") {
            navigationStack.popToRoot()
        }
    }
}

사용방법(전체 샘플)

import SwiftUI

struct ContentView: View {
  var body: some View {
    TabView {
      addTab(title: "Tab 1", systemImageName: "house")
      addTab(title: "Tab 2", systemImageName: "bookmark")
    }
  }
  
  func addTab(title: String, systemImageName: String) -> some View {
    NavigationView {
      RootPage(title: "\(title) home")
        .navigationBarTitle(title)
    }
    .environmentObject(NavigationStack<String>())
    .navigationViewStyle(StackNavigationViewStyle())
    .tabItem {
      Image(systemName: systemImageName)
      Text(title)
    }
  }
}

struct RootPage: View {
  let title: String
  var body: some View {
    SimplePage(title: title, pageCount: 0)
  }
}

struct SimplePage: View {
  @EnvironmentObject var navigationStack: NavigationStack<String>

  var title: String
  var pageCount: Int
  var body: some View {
    VStack {
      navigationStack.navigationLink(customId: "\(pageCount)") {
     // router.navigationLink {
        SimplePage(title: "Page: \(pageCount + 1)", pageCount: pageCount + 1)
      } label: {
        Text("Next page")
      }
      Button("Pop") {
        navigationStack.pop()
      }
      Button("Pop to Page 1") {
        navigationStack.popToLast(customId: "1")
      }
      Button("Pop to root") {
        navigationStack.popToRoot()
      }
    }
    .navigationTitle(title)
  }
}

일부 유닛 테스트

@testable import SwiftUIPop
import XCTest
import SwiftUI
import Combine

class SwiftUIPopTests: XCTestCase {
  typealias CustomLinkID = String
  typealias Stack = NavigationStack<CustomLinkID>
  private let stack = Stack()
}

// MARK: Empty Navigation Stack

extension SwiftUIPopTests {
  func testNoCrashOnPopToRootOnEmptyStack() {
    XCTAssertTrue(stack.isEmpty)
    stack.popToRoot()
  }
  
  func testNoCrashOnPopToLastOnEmptyStack() {
    XCTAssertTrue(stack.isEmpty)
    stack.popToLast(customId: "123")
  }
  
  func testNoCrashOnPopOnEmptyStack() {
    XCTAssertTrue(stack.isEmpty)
    stack.pop()
  }
}

// MARK: expectation functions

private extension SwiftUIPopTests {
  func navigationStackShould(beEmpty: Bool) {
    if beEmpty {
      XCTAssertTrue(stack.isEmpty, "Navigation Stack should be empty")
    } else {
      XCTAssertFalse(stack.isEmpty, "Navigation Stack should not be empty")
    }
  }
}

// MARK: Data / model generators

private extension SwiftUIPopTests {
  func createNavigationLink(viewModel: CustomNavigationLinkViewModel<CustomLinkID>, stack: Stack)
  -> CustomNavigationLink<EmptyView, EmptyView, CustomLinkID> {
    stack.testCreateNavigationLink(viewModel: viewModel) {
      EmptyView()
    } label: {
      EmptyView()
    }
  }
  
  func createNavigationLinkViewModel(customId: CustomLinkID? = nil) -> CustomNavigationLinkViewModel<CustomLinkID> {
    .init(navigationStack: stack, customId: customId)
  }
}

// MARK: test `isActive` changing from `true` to `false` on `pop`

extension SwiftUIPopTests {
  private func isActiveChangeOnPop(customId: String? = nil,
                                   popAction: (Stack) -> Void,
                                   file: StaticString = #file,
                                   line: UInt = #line) {
    navigationStackShould(beEmpty: true)
    let expec = expectation(description: "Wait for viewModel.isActive changing")
    
    var canalables = Set<AnyCancellable>()
    let viewModel = createNavigationLinkViewModel(customId: customId)
    let navigationLink = createNavigationLink(viewModel: viewModel, stack: stack)
    navigationLink.viewModel.isActive = true
    navigationLink.viewModel.$isActive.dropFirst().sink { value in
      expec.fulfill()
    }.store(in: &canalables)
    
    navigationStackShould(beEmpty: false)
    popAction(stack)
    waitForExpectations(timeout: 2)
    navigationStackShould(beEmpty: true)
  }
  
  func testIsActiveChangeOnPop() {
    isActiveChangeOnPop { $0.pop() }
  }
  
  func testIsActiveChangeOnPopToRoot() {
    isActiveChangeOnPop { $0.popToRoot() }
  }
  
  func testIsActiveChangeOnPopToLast() {
    let customId = "1234"
    isActiveChangeOnPop(customId: customId) { $0.popToLast(customId: customId) }
  }
  
  func testIsActiveChangeOnPopToLast2() {
    navigationStackShould(beEmpty: true)
    let expec = expectation(description: "Wait")

    var canalables = Set<AnyCancellable>()
    let viewModel = createNavigationLinkViewModel(customId: "123")
    let navigationLink = createNavigationLink(viewModel: viewModel, stack: stack)
    navigationLink.viewModel.isActive = true
    navigationLink.viewModel.$isActive.dropFirst().sink { value in
      expec.fulfill()
    }.store(in: &canalables)

    navigationStackShould(beEmpty: false)
    stack.popToLast(customId: "1234")
    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
      expec.fulfill()
    }
    waitForExpectations(timeout: 3)
    navigationStackShould(beEmpty: false)
  }
}

// MARK: Check that changing `CustomNavigationLinkViewModel.isActive` will update `Navigation Stack`

extension SwiftUIPopTests {

  // Add and remove view to the empty stack
  private func isActiveChangeUpdatesNavigationStack1(createLink: (Stack) -> CustomNavigationLink<EmptyView, EmptyView, String>) {
    navigationStackShould(beEmpty: true)
    let navigationLink = createLink(stack)
    navigationStackShould(beEmpty: true)
    navigationLink.viewModel.isActive = true
    navigationStackShould(beEmpty: false)
    navigationLink.viewModel.isActive = false
    navigationStackShould(beEmpty: true)
  }

  func testIsActiveChangeUpdatesNavigationStack1() {
    isActiveChangeUpdatesNavigationStack1 { stack in
      let viewModel = createNavigationLinkViewModel()
      return createNavigationLink(viewModel: viewModel, stack: stack)
    }
  }

  func testIsActiveChangeUpdatesNavigationStack2() {
    isActiveChangeUpdatesNavigationStack1 { stack in
      let viewModel = createNavigationLinkViewModel(customId: "123")
      return createNavigationLink(viewModel: viewModel, stack: stack)
    }
  }

  // Add and remove view to the non-empty stack
  private func isActiveChangeUpdatesNavigationStack2(createLink: (Stack) -> CustomNavigationLink<EmptyView, EmptyView, String>) {
    navigationStackShould(beEmpty: true)
    let viewModel1 = createNavigationLinkViewModel()
    let navigationLink1 = createNavigationLink(viewModel: viewModel1, stack: stack)
    navigationLink1.viewModel.isActive = true
    navigationStackShould(beEmpty: false)
    XCTAssertEqual(stack.count, 1, "Navigation Stack Should contains only one link")

    let navigationLink2 = createLink(stack)
    navigationLink2.viewModel.isActive = true
    navigationStackShould(beEmpty: false)
    navigationLink2.viewModel.isActive = false
    XCTAssertEqual(stack.count, 1, "Navigation Stack Should contains only one link")
  }

  func testIsActiveChangeUpdatesNavigationStack3() {
    isActiveChangeUpdatesNavigationStack2 { stack in
      let viewModel = createNavigationLinkViewModel()
      return createNavigationLink(viewModel: viewModel, stack: stack)
    }
  }

  func testIsActiveChangeUpdatesNavigationStack4() {
    isActiveChangeUpdatesNavigationStack2 { stack in
      let viewModel = createNavigationLinkViewModel(customId: "123")
      return createNavigationLink(viewModel: viewModel, stack: stack)
    }
  }
}

저는 최근에 swiftui-navigation-stack이라는 오픈 소스 프로젝트를 만들었습니다.Swift를 위한 대체 내비게이션 스택입니다.UI. 자세한 것은, README 를 봐 주세요.사용하기 매우 편리합니다.

화면 간 보기을 원하는 , 화면 간 화면 보기)을 합니다.Screen 표시:

struct Screen<Content>: View where Content: View {
    let myAppBackgroundColour = Color.white
    let content: () -> Content

    var body: some View {
        ZStack {
            myAppBackgroundColour.edgesIgnoringSafeArea(.all)
            content()
        }
    }
}

다음에 , 뿌리가 있다, 뿌리가 있다, 뿌리가 .NavigationStackView(스탠다드)와)NavigationView

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

이제 기본적인 동작을 보여주기 위해 몇 가지 하위 보기를 만들어 보겠습니다.

struct Homepage: View {
    var body: some View {
        Screen {
            PushView(destination: FirstChild()) {
                Text("PUSH FORWARD")
            }
        }
    }
}

struct FirstChild: View {
    var body: some View {
        Screen {
            VStack {
                PopView {
                    Text("JUST POP")
                }
                PushView(destination: SecondChild()) {
                    Text("PUSH FORWARD")
                }
            }
        }
    }
}

struct SecondChild: View {
    var body: some View {
        Screen {
            VStack {
                PopView {
                    Text("JUST POP")
                }
                PopView(destination: .root) {
                    Text("POP TO ROOT")
                }
            }
        }
    }
}

하실 수 .PushView ★★★★★★★★★★★★★★★★★」PopView앞뒤로 이동할 수 있습니다., ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★SceneDelegate하다

// Create the SwiftUI view that provides the window contents.
let contentView = RootView()

결과는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오.

iOS 15 에서는, display() 를 사용해 subview 에 display 를 건네주는 간단한 솔루션이 있습니다.

struct ContentView: View {
    @State private var showingSheet = false
    var body: some View {
        NavigationView {
            Button("show sheet", action: { showingSheet.toggle()})
                .navigationTitle("ContentView")
        }.sheet(isPresented: $showingSheet) { FirstSheetView() }
    }
}

struct FirstSheetView: View {
    @Environment(\.dismiss) var dismiss
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: SecondSheetView(dismiss: _dismiss)) {
                    Text("show 2nd Sheet view")
                }
                NavigationLink(destination: ThirdSheetView(dismiss: _dismiss)) {
                    Text("show 3rd Sheet view")
                }
                Button("cancel", action: {dismiss()})
            } .navigationTitle("1. SheetView")
        }
    }
}

struct SecondSheetView: View {
    @Environment(\.dismiss) var dismiss
    var body: some View {
        List {
            NavigationLink(destination: ThirdSheetView(dismiss: _dismiss)) {
                Text("show 3rd SheetView")
            }
            Button("cancel", action: {dismiss()})
        } .navigationTitle("2. SheetView")
    }
}

struct ThirdSheetView: View {
    @Environment(\.dismiss) var dismiss
    var body: some View {
        List {
            Button("cancel", action: {dismiss()})
        } .navigationTitle("3. SheetView")
    }
}

스위프트에서 해결책을 찾지 못했다.UI는 아직이지만 라이브러리는 깨끗합니다.UI.

CUNavigation 클래스를 사용하면 원하는 내비게이션 패턴을 정확하게 얻을 수 있습니다.

라이브러리의 README 예:

NavigationView {
    Button(action: {
      CUNavigation.pushToSwiftUiView(YOUR_VIEW_HERE)
    }){
      Text("Push To SwiftUI View")
    }

    Button(action: {
      CUNavigation.popToRootView()
    }){
      Text("Pop to the Root View")
    }

    Button(action: {
      CUNavigation.pushBottomSheet(YOUR_VIEW_HERE)
    }){
      Text("Push to a Botton-Sheet")
    }
}

초등.루트 보기(돌아가려는 위치)에서 isActive 디자이너와 NavigationLink를 충분히 사용할 수 있습니다.마지막 뷰에서 isActive 파라미터를 제어하는 FALSE 변수로 전환합니다.

Swift 버전 5.5에서는 .isDetaillink(false)는 옵션입니다.

예시와 같이 몇 가지 공통 클래스를 사용하거나 바인딩을 통해 이 변수를 VIEW 계층 아래로 전송할 수 있습니다.당신에게 더 편리한 방법을 사용하세요.

class ViewModel: ObservableObject {
    @Published var isActivate = false
}

@main
struct TestPopToRootApp: App {
    let vm = ViewModel()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(vm)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var vm: ViewModel
    
    var body: some View {
        NavigationView {
            NavigationLink("Go to view2", destination: NavView2(), isActive: $vm.isActivate)
            .navigationTitle(Text("Root view"))
        }
    }
}

struct NavView2: View {
    var body: some View {
        NavigationLink("Go to view3", destination: NavView3())
        .navigationTitle(Text("view2"))
    }
}

struct NavView3: View {
    @EnvironmentObject var vm: ViewModel
    
    var body: some View {
        Button {
            vm.isActivate = false
        } label: {
            Text("Back to root")
        }

        .navigationTitle(Text("view3"))
    }
}

★★★로 Root View하지 않고.isDetailLink(false)를 지워야 요.NavigationLink Root View

class NavigationLinkStore: ObservableObject {
    static let shared = NavigationLinkStore()

    @Published var showLink = false
}

struct NavigationLinkView: View {
    @ObservedObject var store = NavigationLinkStore.shared
    @State var isActive = false

    var body: some View {
        NavigationView {
            VStack {
                Text("Main")

                Button("Go to View1") {
                    Task {
                        store.showLink = true
                        try await Task.sleep(seconds: 0.1)
                        isActive = true
                    }
                }

                if store.showLink {
                    NavigationLink(
                        isActive: $isActive,
                        destination: { NavigationLink1View() },
                        label: { EmptyView() }
                    )
                }
            }
        }
    }
}

struct NavigationLink1View: View {
    var body: some View {
        VStack {
            Text("View1")
            NavigationLink("Go to View 2", destination: NavigationLink2View())
        }
    }
}

struct NavigationLink2View: View {
    @ObservedObject var store = NavigationLinkStore.shared

    var body: some View {
        VStack {
            Text("View2")
            Button("Go to root") {
                store.showLink = false
            }
        }
    }
}

여기에서는 복잡한 내비게이션에 대한 일반적인 접근방식을 설명하겠습니다.이 패턴은 루트에 팝업백할 필요가 있는 플로우가 1개뿐 아니라 여러 개 있는 경우에 도움이 됩니다.

먼저 환경 Observable Object를 설정하고 읽기 쉽도록 열거형을 사용하여 보기를 입력합니다.

class ActiveView : ObservableObject {
  @Published var selection: AppView? = nil
}

enum AppView : Comparable {
  case Main, Screen_11, Screen_12, Screen_21, Screen_22
}

[...]
let activeView = ActiveView()
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(activeView))

메인 ContentView에서 EmptyView()의 NavigationLink 버튼을 사용합니다.태그와 선택 대신 NavigationLink의 isActive 파라미터를 사용하기 위해서입니다.메인 뷰의 Screen_11은 Screen_12에서 활성화 상태를 유지해야 하며, 반대로 Screen_21은 Screen_22에서 활성화 상태를 유지해야 합니다. 그렇지 않으면 뷰가 팝업됩니다.isDetailLink를 false로 설정하는 것을 잊지 마십시오.

struct ContentView: View {
  @EnvironmentObject private var activeView: ActiveView

  var body: some View {
    NavigationView {
      VStack {

        // These buttons navigate by setting the environment variable.
        Button(action: { self.activeView.selection = AppView.Screen_1.1}) {
            Text("Navigate to Screen 1.1")
        }

        Button(action: { self.activeView.selection = AppView.Screen_2.1}) {
            Text("Navigate to Screen 2.1")
        }

       // These are the navigation link bound to empty views so invisible
        NavigationLink(
          destination: Screen_11(),
          isActive: orBinding(b: self.$activeView.selection, value1: AppView.Screen_11, value2: AppView.Screen_12)) {
            EmptyView()
        }.isDetailLink(false)

        NavigationLink(
          destination: Screen_21(),
          isActive: orBinding(b: self.$activeView.selection, value1: AppView.Screen_21, value2: AppView.Screen_22)) {
            EmptyView()
        }.isDetailLink(false)
      }
    }
  }

Screen_11에서 동일한 패턴을 사용하여 Screen_12로 이동할 수 있습니다.

이 복잡한 네비게이션의 돌파구는 orBinding입니다.이를 통해 탐색 흐름의 뷰 스택이 활성 상태를 유지할 수 있습니다.Screen_11이든 Screen_12이든 Navigation Link(Screen_11)를 활성화해야 합니다.

// This function create a new Binding<Bool> compatible with NavigationLink.isActive
func orBinding<T:Comparable>(b: Binding<T?>, value1: T, value2: T) -> Binding<Bool> {
  return Binding<Bool>(
      get: {
          return (b.wrappedValue == value1) || (b.wrappedValue == value2)
      },
      set: { newValue in  } // Don't care the set
    )
}

나는 나에게 잘 맞는 해결책을 찾았다.동작은 다음과 같습니다.

GIF 이미지는 작동 방식을 보여 줍니다.

서서 ContentView.swift 삭제:

  1. RootSelection, 클래스 선언@EnvironmentObjectRootSelection의 활성 NavigationLink루트 뷰에만 있습니다.
  2. .isDetailLink(false)NavigationLink최종 상세 뷰가 아닙니다.
  3. 하여 ""를 .NavigationView.
  4. 뷰에 여러 개의 루트 뷰가 정상적으로 합니다.NavigationLink.
import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            SubView(folder: rootFolder)
        }
    }
}

struct SubView: View {
    @EnvironmentObject var rootSelection: RootSelection
    var folder: Folder

    var body: some View {
        List(self.folder.documents) { item in
            if self.folder.documents.count == 0 {
                Text("empty folder")
            } else {
                if self.folder.id == rootFolder.id {
                    NavigationLink(item.name, destination: SubView(folder: item as! Folder), tag: item.id, selection: self.$rootSelection.tag)
                        .isDetailLink(false)
                } else {
                    NavigationLink(item.name, destination: SubView(folder: item as! Folder))
                        .isDetailLink(false)
                }
            }
        }
        .navigationBarTitle(self.folder.name, displayMode: .large)
        .listStyle(SidebarListStyle())
        .overlay(
            Button(action: {
                rootSelection.tag = nil
            }, label: {
                Text("back to root")
            })
            .disabled(self.folder.id == rootFolder.id)
        )
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(RootSelection())
    }
}

class RootSelection: ObservableObject {
    @Published var tag: UUID? = nil
}

class Document: Identifiable {
    let id = UUID()
    var name: String

    init(name: String) {
        self.name = name
    }
}

class File: Document {}

class Folder: Document {
    var documents: [Document]

    init(name: String, documents: [Document]) {
        self.documents = documents
        super.init(name: name)
    }
}

let rootFolder = Folder(name: "root", documents: [
    Folder(name: "folder1", documents: [
        Folder(name: "folder1.1", documents: []),
        Folder(name: "folder1.2", documents: []),
    ]),
    Folder(name: "folder2", documents: [
        Folder(name: "folder2.1", documents: []),
        Folder(name: "folder2.2", documents: []),
    ])
])

.environmentObject(RootSelection())입니다.ContentView()에 의 xxxApp.swiftfiles.complete files files files files files files files files.

import SwiftUI

@main
struct DraftApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(RootSelection())
        }
    }
}

말할의 대답이 분명 맞는 답이야나는 에 대한 포장지를 만들었다.NavigationLink 수 되어 .isDetailLink(false)필요한 모든 데이터를 캡처할 수 있습니다.

「 」, 「 」를 캡쳐 .isActive "binding" 입니다.tag이 경우 루트로 선언된 뷰로 팝업하고 싶을 때 리셋할 수 있습니다.

★★isRoot = true되며, 에는 해당 뷰의 바인딩이 됩니다.dismiss팝이 발생할 때 작업이 필요한 경우 파라미터는 옵션 닫기를 수행합니다.

는 Swift의 기본 .NavigationLink단순한 부울 또는 태그 기반 네비게이션을 위한 이니셜라이저로 기존 사용법을 쉽게 편집할 수 있습니다.필요한 경우 다른 항목을 추가하는 것은 간단해야 합니다.

포장지는 다음과 같습니다.

struct NavigationStackLink<Label, Destination> : View where Label : View, Destination : View {
    var isActive: Binding<Bool>? // Optionality implies whether tag or Bool binding is used
    var isRoot: Bool = false
    let link: NavigationLink<Label, Destination>

    private var dismisser: () -> Void = {}

    /// Wraps [NavigationLink](https://developer.apple.com/documentation/swiftui/navigationlink/init(isactive:destination:label:))
    /// `init(isActive: Binding<Bool>, destination: () -> Destination, label: () -> Label)`
    /// - Parameters:
    ///     - isActive:  A Boolean binding controlling the presentation state of the destination
    ///     - isRoot: Indicate if this is the root view. Used to pop to root level. Default `false`
    ///     - dismiss: A closure that is called when the link destination is about to be dismissed
    ///     - destination: The link destination view
    ///     - label: The links label
    init(isActive: Binding<Bool>, isRoot : Bool = false, dismiss: @escaping () -> Void = {}, @ViewBuilder destination: @escaping () -> Destination, @ViewBuilder label: @escaping () -> Label) {
        self.isActive = isActive
        self.isRoot = isRoot
        self.link = NavigationLink(isActive: isActive, destination: destination, label: label)
        self.dismisser = dismiss
    }

    /// Wraps [NavigationLink ](https://developer.apple.com/documentation/swiftui/navigationlink/init(tag:selection:destination:label:))
    init<V>(tag: V, selection: Binding<V?>, isRoot : Bool = false, dismiss: @escaping () -> Void = {}, @ViewBuilder destination: @escaping () -> Destination, @ViewBuilder label: @escaping () -> Label) where V : Hashable
    {
        self.isRoot = isRoot
        self.link = NavigationLink(tag: tag, selection: selection, destination: destination, label: label)
        self.dismisser = dismiss
        self.isActive = Binding (get: {
            selection.wrappedValue == tag
        }, set: { newValue in
            if newValue {
              selection.wrappedValue = tag
            } else {
              selection.wrappedValue = nil
            }
        })
    }

    // Make sure you inject your external store into your view hierarchy
    @EnvironmentObject var viewRouter: ViewRouter
    var body: some View {
        // Store whatever you need to in your external object
        if isRoot {
            viewRouter.root = isActive
        }
        viewRouter.dismissals.append(self.dismisser)
        // Return the link with whatever modification you need
        return link
            .isDetailLink(false)
    }
}

ViewRouter필요한 건 뭐든지 될 수 있어요사용하였습니다.ObservableObject으로는 '어느 정도'를 Published「 」 、 「 」 、 「 」:

class ViewRouter: ObservableObject {

    var root: Binding<Bool>?
    typealias Dismiss = () -> Void
    var dismissals : [Dismiss] = []

    func popToRoot() {
        dismissals.forEach { dismiss in
            dismiss()
        }
        dismissals = []
        root?.wrappedValue = false
    }
}

처음에는 여기에 게시Chuck H의 솔루션을 사용하고 있었습니다.

하지만 이 솔루션이 제 경우 효과가 없을 때 저는 문제에 직면했습니다.는 "2"를 할 수 있는 되었습니다.pop to root경우솔루션은 하나의 공통 상태를 가지고 있기 때문에 작동하지 않았습니다.@Environment(\.rootPresentationMode) private var rootPresentationMode

는 the든 the 를 만들었다.RouteManager 열거형 " " "을 하여"를 지정합니다.Route, 가 「이러한 플로우」를할 수 있는 를 나타냅니다.pop to root

Route Manager:

final class RouteManager: ObservableObject {
    @Published
    private var routers: [Int: Route] = [:]

    subscript(for route: Route) -> Route? {
        get {
            routers[route.rawValue]
        }
        set {
            routers[route.rawValue] = route
        }
    }

    func select(_ route: Route) {
        routers[route.rawValue] = route
    }

    func unselect(_ route: Route) {
        routers[route.rawValue] = nil
    }
}

경로:

enum Route: Int, Hashable {
    case signUp
    case restorePassword
    case orderDetails
}

사용방법:

struct ContentView: View {
    @EnvironmentObject
    var routeManager: RouteManager

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(
                    destination: SignUp(),
                    tag: .signUp,
                    selection: $routeManager[for: .signUp]
                ) { EmptyView() }.isDetailLink(false)
                NavigationLink(
                    destination: RestorePassword(),
                    tag: .restorePassword,
                    selection: $routeManager[for: .restorePassword]
                ) { EmptyView() }.isDetailLink(false)
                Button("Sign Up") {
                    routeManager.select(.signUp)
                }
                Button("Restore Password") {
                    routeManager.select(.restorePassword)
                }
            }
            .navigationBarTitle("Navigation")
            .onAppear {
                routeManager.unselect(.signUp)
                routeManager.unselect(.restorePassword)
            }
        }.navigationViewStyle(StackNavigationViewStyle())
    }
}

!!중요!!

하다를 돼요.unselect의 of의 RouteManager사용자가 흐름으로 이동한 후 뒤로 버튼을 눌러 되돌아가는 경우. 이전에 한 흐름의 않은치 않은).「 」 、 「 」 、 「 」( 「 」 ) 。

.onAppear {
    routeManager.unselect(.signUp)
    routeManager.unselect(.restorePassword)
}

데모

완전한 데모 프로젝트는 이쪽에서 보실 수 있습니다.

하다 보면 NavigationView ★★★★★★★★★★★★★★★★★」NavigationLink단, UIPilot 라이브러리를 사용하는 경우, 이 라이브러리는 작은 랩으로 둘러싸여 있습니다.NavigationView어떤 목적지로든 가는 것은 매우 간단합니다.

루트가 있다고 칩시다.

enum AppRoute: Equatable {
    case Home
    case Detail
    case NestedDetail
}

다음과 같은 루트 뷰를 설정할 수 있습니다.

struct ContentView: View {
    @StateObject var pilot = UIPilot(initial: AppRoute.Home)

    var body: some View {
        UIPilotHost(pilot)  { route in
            switch route {
                case .Home: return AnyView(HomeView())
                case .Detail: return AnyView(DetailView())
                case .NestedDetail: return AnyView(NestedDetail())
            }
        }
    }
}

그리고 당신은 팝업을 하고 싶다.Home NestedDetail, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아.popTo★★★★★★ 。

struct NestedDetail: View {
    @EnvironmentObject var pilot: UIPilot<AppRoute>

    var body: some View {
        VStack {
            Button("Go to home", action: {
                pilot.popTo(.Home)   // Pop to home
            })
        }.navigationTitle("Nested detail")
    }
}

저는 "그냥 작동"하는 솔루션을 만들었고, 매우 만족합니다.마법 솔루션을 사용하려면 몇 가지 단계만 수행하면 됩니다.

먼저 이 스레드에서 사용되는 rootPresentationMode를 사용합니다.다음 코드 추가:

// Create a custom environment key
struct RootPresentationModeKey: EnvironmentKey {
    static let defaultValue: Binding<RootPresentationMode> = .constant(RootPresentationMode())
}

extension EnvironmentValues {
    var rootPresentationMode: Binding<RootPresentationMode> {
        get { self[RootPresentationModeKey.self] }
        set { self[RootPresentationModeKey.self] = newValue }
    }
}

typealias RootPresentationMode = Bool

extension RootPresentationMode: Equatable {
    mutating func dismiss() {
        toggle()
    }
}

다음은 마법입니다.두 가지 단계가 있습니다.

  1. 하는 뷰 .rootPresentationMode★★★★★★ 。

    struct WithRoot: ViewModifier {
        @Environment(\.rootPresentationMode) private var rootPresentationMode
        @Binding var rootBinding: Bool
    
        func body(content: Content) -> some View {
            content
                .onChange(of: rootBinding) { newValue in
                    // We only care if it's set to true
                    if newValue {
                        rootPresentationMode.wrappedValue = true
                    }
                }
                .onChange(of: rootPresentationMode.wrappedValue) { newValue in
                    // We only care if it's set to false
                    if !newValue {
                        rootBinding = false
                    }
                }
        }
    }
    
    extension View {
        func withRoot(rootBinding: Binding<Bool>) -> some View {
            modifier(WithRoot(rootBinding: rootBinding))
        }
    }
    
  2. isPresented Navigation ('네비게이션뷰')에 대해서

    struct ContentView: View {
        // This seems.. unimportant, but it's crucial. This variable
        // lets us pop back to the root view from anywhere by adding
        // a withRoot() modifier
        // It's only used indirectly by the withRoot() modifier.
        @State private var isPresented = false
    
        var body: some View {
            NavigationView {
                MyMoneyMakingApp()
            }
            // rootPresentationMode MUST be set on a NavigationView to be
            // accessible from everywhere
            .environment(\.rootPresentationMode, $isPresented)
        }
    

서브뷰에서 사용하려면 다음 작업만 하면 됩니다.

struct MyMoneyMakingApp: View {
    @State private var isActive = false

    var body: some View {
        VStack {
            NavigationLink(destination: ADeepDeepLink(), isActive: $isActive) {
                Text("go deep")
            }
        }
        .withRoot(rootBinding: $isActive)
    }
}

struct ADeepDeepLink: View {
    @Environment(\.rootPresentationMode) private var rootPresentationMode

    var body: some View {
        VStack {
            NavigationLink(destination: ADeepDeepLink()) {
                Text("go deeper")
            }
            Button(action: {
                rootPresentationMode.wrappedValue.dismiss()
            }) {
                Text("pop to root")
            }
        }
    }
}

@malhal의 답변이 도움이 되었지만, 제 상황에서는 버튼을 하나씩 눌러 탐색할 때 기능이 필요했습니다.같은 처지라면 이 코드를 사용해 보세요!

//  ContentView.swift
//  Navigation View Buttons
//
//  Created by Jarren Campos on 9/10/22.
//

import SwiftUI

struct ContentView: View {

    var body: some View{
        VStack{
            ContentView1()
        }
    }
}

struct ContentView1: View {
    @State var isActive : Bool = false

    var body: some View {
        NavigationView {
            VStack{
                Button {
                    isActive = true
                } label: {
                    Text("To 2")
                }
            }
            .background{
                NavigationLink(
                    destination: ContentView2(rootIsActive: self.$isActive),
                    isActive: self.$isActive) {}
                    .isDetailLink(false)
            }
            .navigationBarTitle("One")
        }
    }
}

struct ContentView2: View {
    @Binding var rootIsActive : Bool
    @State var toThirdView: Bool = false

    var body: some View {

        VStack{
            Button {
                toThirdView = true
            } label: {
                Text("to 3")
            }
        }
        .background{
            NavigationLink(isActive: $toThirdView) {
                ContentView3(shouldPopToRootView: self.$rootIsActive)
            } label: {}
                .isDetailLink(false)
        }
        .navigationBarTitle("Two")

    }
}

struct ContentView3: View {
    @Binding var shouldPopToRootView : Bool

    var body: some View {
        VStack {
            Text("Hello, World #3!")
            Button {
                self.shouldPopToRootView = false
            } label: {
                Text("Pop to root")
            }
        }
        .navigationBarTitle("Three")
    }
}

같은 문제는 없지만 루트 뷰를 네비게이션스택을 지원하지 않는 뷰에서 지원하는 뷰로 변경하는 코드가 있습니다.비결은 스위프트에서는 하지 않는다는 것이다.UI - 이 작업은SceneDelegate를 합니다.UIHostingController새것으로.

간략한 .SceneDelegate:

    func changeRootToOnBoarding() {
        guard let window = window else {
            return
        }

        let onBoarding = OnBoarding(coordinator: notificationCoordinator)
            .environmentObject(self)

        window.rootViewController = UIHostingController(rootView: onBoarding)
    }

    func changeRootToTimerList() {
        guard let window = window else {
            return
        }

        let listView = TimerList()
            .environmentObject(self)
        window.rootViewController = UIHostingController(rootView: listView)
    }

★★★★★★★★★★★★★★★★★.SceneDelegate 할 수 있는

    /// Our "parent" SceneDelegate that can change the root view.
    @EnvironmentObject private var sceneDelegate: SceneDelegate

그리고 나서 대리인의 공개 행사에 호출합니다.그, 그 행동을 유지했을 거라고 생각합니다.View 냈습니다.UIHostingController그것을 위해 교환했다.window.rootViewController당신에게 도움이 될 수도 있어요

언급URL : https://stackoverflow.com/questions/57334455/how-can-i-pop-to-the-root-view-using-swiftui

반응형