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())
}
}
CodePudding user response:
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!