简体   繁体   中英

How to navigate to a View in SwiftUI without NavigationLink

I am new to SwiftUI and would like to navigate to a "home" view in SwiftUI from any other view. I've created a home button that is added to the navigation bar on multiple views. The home button returns to the "home" view but the navigation links don't navigate correctly and display the wrong data.

I've read through multiple answers from others having a similar problem but the answers are at least two years old and SwiftUI has changed quite a bit since then. I've tried to implement many of the answers but always ran into other problems. Some functionality has been deprecated and I'm looking for a more forward looking solution.

Here is some code that shows the problem. In this example, I'm using the isActive parameter in NavigationLink to trigger the home button but this seems to break the NavigationLink. I've also tried using the tag and selection parameters and get the same results. From reading other responses, I believe using the ForEach statement in "ProductFamilyView" is the problem with referencing the wrong data. But, in this app, I need to use the ForEach statement to display the product families.

If I try calling ProductFamilyView(), nothing happens. In this example, I want to navigate back to the "ProductFamilyView". Is there another way to navigate to this view without using NavigationLink?

import SwiftUI
import Combine

class Navigation: ObservableObject {
    @Published var productFamilyIsActive : Bool = false
}

struct ContentView: View {
    
    @StateObject var navigation = Navigation()
    
    var body: some View {
        NavigationView {
            VStack {
                LoginButtonView(buttonText: "Login")
            }
        }
        .environmentObject(navigation)
        .navigationViewStyle(StackNavigationViewStyle() )
    }
}
struct LoginButtonView: View {
    
    @State private var navigated : Bool = false
    var buttonText : String
    
    var body: some View {
        Button(action: {
            self.navigated.toggle()
        }, label: {
            Text("\(buttonText)")
        })
        .navigationBarBackButtonHidden(true)
        NavigationLink(destination: ProductFamilyView(), isActive: $navigated ) {
            EmptyView()
        }
    }
}
struct ProductFamilyView: View { //This is my home view
    
    private var productFamilies = ProductFamilyViewModel.loadData()
    
    var body: some View {
        List {
            ForEach(productFamilies) { (product) in
                ProductFamilyRow(productFamily: product.productFamily)
            }
        }
        .navigationTitle("Product Families")
        .navigationBarTitleDisplayMode(.inline)
        .navigationBarBackButtonHidden(true)
    }
}
struct ProductFamilyRow: View {
    
    @EnvironmentObject var navigation : Navigation
    @State var productFamily : String

    var body: some View {
        NavigationLink(destination: PartNumberView(productFamily: productFamily), isActive: $navigation.productFamilyIsActive) {
            Text("\(productFamily)")
        }
        .isDetailLink(false)
    }
}
struct PartNumberView: View {
    
    @State var productFamily: String
    
    var productDetails = ProductViewModel.loadData()
        
    var body: some View {
        List {
            ForEach(productDetails) { (productDetail) in
                if productFamily == productDetail.productFamily {
                    NavigationLink(destination: PartNumberRow(productDetail: productDetail)) {
                        Text("\(productDetail.partNumber)")
                    }
                }
            }
        }
        .navigationTitle("\(productFamily)")
        .navigationBarItems(trailing: HomeButtonView() )
    }
}
struct PartNumberRow: View {
    
    @State var productDetail : ProductViewModel
    
    var body: some View {
        List {
            Text("Description: \(productDetail.description)")
            NavigationLink(destination: ColorView(colorOptions: productDetail.colorOptions)) {
                Text("Color Options")
            }
        }
        .navigationTitle("\(productDetail.partNumber)")
        .navigationBarItems(trailing: HomeButtonView() )
    }
}
struct ColorView: View {
    
    @State var colorOptions : [Item]
    
    var body: some View {
        List {
            ForEach(colorOptions) { (color) in
                Text("\(color.name)")
            }
        }
        .navigationBarItems(trailing: HomeButtonView() )
    }
}
struct HomeButtonView: View {
    
    @EnvironmentObject var navigation : Navigation
    
    var body: some View {
        
        Button(action: {
            self.navigation.productFamilyIsActive.toggle()
        }, label: {
            Image(systemName: "house")
        })
        .environmentObject(navigation)
    }
}
class ProductFamilyViewModel: ObservableObject, Identifiable {
    
    var id = UUID().uuidString
    var productFamily : String = ""
    
    init(productFamily: String) {
        self.productFamily = productFamily
    }
    static func loadData() -> [ProductFamilyViewModel] {
        
        let products = ["Product Family 1", "Product Family 2", "Product Family 3"]
        var productFamilies : [ProductFamilyViewModel] = []
        
        for product in products {
            productFamilies.append(ProductFamilyViewModel(productFamily: product))
        }
        return productFamilies
    }
}
struct Item: Identifiable, Codable {

    var id : String {
        self.name
    }
    var name : String
}
struct Product: Identifiable {
    
    var id = UUID().uuidString
    var partNumber    : String = "part number"
    var productFamily : String = "product family"
    var description   : String = "description"
    var colorOptions  : [Item] = []
}
class ProductViewModel: ObservableObject, Identifiable {
    
    var id = UUID().uuidString
    var partNumber    : String
    var productFamily : String
    var description   : String
    var colorOptions  : [Item]
    
    init(model: Product) {
        self.partNumber    = model.partNumber
        self.productFamily = model.productFamily
        self.description   = model.description
        self.colorOptions  = model.colorOptions
    }
    static func loadData() -> [ProductViewModel] {
        
        let colors = [Item(name: "red"), Item(name: "white"), Item(name: "blue")]
        
        let productDetails : [ProductViewModel] = [
            ProductViewModel( model: Product(
                partNumber: "100-1111-101",
                productFamily: "Product Family 1",
                description: "Part Number 100-1111-101",
                colorOptions: colors)
            ),
            ProductViewModel( model: Product(
                partNumber: "100-1111-102",
                productFamily: "Product Family 1",
                description: "Part Number 100-1111-102",
                colorOptions: colors)
            ),
            ProductViewModel( model: Product(
                partNumber: "100-1111-103",
                productFamily: "Product Family 1",
                description: "Part Number 100-1111-103",
                colorOptions: colors)
            ),
            ProductViewModel( model: Product(
                partNumber: "200-1111-101",
                productFamily: "Product Family 2",
                description: "Part Number 200-1111-101",
                colorOptions: colors)
            ),
            ProductViewModel( model: Product(
                partNumber: "200-1111-102",
                productFamily: "Product Family 2",
                description: "Part Number 200-1111-102",
                colorOptions: colors)
            ),
            ProductViewModel( model: Product(
                partNumber: "200-1111-103",
                productFamily: "Product Family 2",
                description: "Part Number 200-1111-103",
                colorOptions: colors)
            ),
            ProductViewModel( model: Product(
                partNumber: "300-1111-101",
                productFamily: "Product Family 3",
                description: "Part Number 300-1111-101",
                colorOptions: colors)
            ),
            ProductViewModel( model: Product(
                partNumber: "300-1111-102",
                productFamily: "Product Family 3",
                description: "Part Number 300-1111-102",
                colorOptions: colors)
            ),
            ProductViewModel( model: Product(
                partNumber: "300-1111-103",
                productFamily: "Product Family 3",
                description: "Part Number 300-1111-103",
                colorOptions: colors)
            )
        ]
        return productDetails
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(Navigation())
    }
}

First of all - congratulations: This is the first such complex code, that flawlessly runs on copy/paste into Xcode... doesn't happen often:)

To your question:
You almost have it. The only thing is that your productFamilyIsActive is a Bool – that can only hold info on ONE selected NavigationLink, you have 3. But it can if you change it to an optional selection:

class Navigation: ObservableObject {
    @Published var productFamilyIsActive : String? = nil // here
}

For that you use a different init on NavigationLink here:

struct ProductFamilyRow: View {
    
    @EnvironmentObject var navigation : Navigation
    @State var productFamily : String
    
    var body: some View {
        NavigationLink(destination: PartNumberView(productFamily: productFamily),
                       tag: productFamily, // here
                       selection: $navigation.productFamilyIsActive) { // and here
            Text("\(productFamily)")
        }
        .isDetailLink(false)
    }
}

Lastly you don't set to false but to nil here:

struct HomeButtonView: View {
    
    @EnvironmentObject var navigation : Navigation
    
    var body: some View {
        Button(action: {
            self.navigation.productFamilyIsActive = nil // here
        }, label: {
            Image(systemName: "house")
        })
            .environmentObject(navigation)
    }
}

Voila!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM