UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 43 Navigation and Day 46 Challenge (new version) ... problems with concepts

Forums > 100 Days of SwiftUI

I imagine the solution for the Day 46 Navigation challenge will be posted soon. I think that should answer my questions.

In the mean time, I'm posting here, too. I clearly don't fully understand the use of NavigationLink and .navigationDestination when using it with a ForEach on a data structure. I think I understand the examples in the lessons where Paul uses Ints and Strings. I'm missing something, though, in the translation.

I'm working on a side learning project using SwiftData that includes this simple Class:

class Category {
    var name: String
}

In the current version of my app I can navigate seemingly just fine using the usual construct:

var body: some View {
    NavigationStack {
        List {
            ForEach(categories) { category in 
                NavigationLink {
                    ItemsView(category: category)
                } label: {
                    Text(category.name)
                }
            }
        }
    }

I've tried to re-do using the .navigationDestination method but can't get it to work. Latest broken attempt:

var body: some View {
    NavigationStack {
        List {
            ForEach(categories) { category in 
                NavigationLink("\(category.name)", value: category)
            }
            .navigationDestination(for: Category.self) { selection in
                ItemsView(category: selection)
        }
    }

Can someone help me connect the dots between the seemingly more basic examples in the day's lesson and what it is I'm trying to do?

I realize this is similar to the challenge #1 to modify iExpense so that solution may help when it is posted.

thanks for any guidance -- jay

3      

@jayelevy needs some help getting to a destination.

Can someone help me connect the dots? thanks for any guidance

Thanks for posting code. This helps show where you may have not grasped the concepts.

@twoStraws has a nice video on this.
See -> Different Destinations

Using a standard NavigationLink is just fine, if you have a small number of potential destinations. However, what is SwiftUI doing? For each and every tappable destination, SwiftUI is in the background building a destination view. If you have a hundred possible tappable items, SwiftUI will build that many destination views, even though your user will only tap one!

Efficient Code

Using the updated .navigationDestination syntax is much more efficient. In the List of tappable options, you'll provide what I call a data package. Then when you tap an item, SwiftUI determines the package's type and selects an appropriate destination view and will pass in the data package for the new view to render.

This gives you flexibility (see code snip) because now you can have ONE List that contains one or more TYPES of data packages. In the example below, the List contains both Integer and Wizard types of data. You'll see a different destination based on the TYPE of data you tap.

An additional benefit is that SwiftUI now builds only ONE view, rather than (potentially) hundreds of views that may never be displayed.

struct Wizard: Identifiable, Equatable, Hashable {
    static func == (lhs: Wizard, rhs: Wizard) -> Bool {
        return lhs.name == rhs.name
    }

    var id =   UUID().uuidString
    var name:  String           // Student's name
    var house: String           // Sorting Hat

    static var sampleStudents = [
        Wizard(name: "Jay",      house: "Hufflepuff" ),
        Wizard(name: "Hermione", house: "Gryffindor" ),
        Wizard(name: "Ron",      house: "Gryffindor" ),
        Wizard(name: "Luna",     house: "Ravenclaw"  )
    ]
}

struct WizardsListView: View {
    @State private var wizards = [Wizard]()
    var body: some View {
        NavigationStack {
            // Note! This list has four Wizard objects and two Integer objects.
            // Goal: Go to different views based on object TYPE.
            List {
                // For each item in the LIST, provide a package of data, and its label.
                ForEach(wizards) { wizard in
                    // Here's the package of data for a selected destination
                    NavigationLink( value: wizard ) {
                        // This is what you SEE in the selectable list
                        RowView(wizardName: wizard.name) // what to see in the list of tappable items
                    }.padding(.vertical)
                }
                // Provide a LABEL and a PACKAGE of DATA
                NavigationLink("🧙🏾‍♂️ Random Integer", value: Int.random(in:  0..<50 ))  // Note! the package is an Integer
                NavigationLink("🧙🏾‍♂️ Random Integer", value: Int.random(in: 51..<100))  // Note the package type!
            }
            // ============= Based on the type of the PACKAGE, go to a different destination ===============
            // IF a row is tapped, then build a destination view
            // using the data package noted above
            .navigationDestination(for: Wizard.self) { WizardView(theWizard:    $0) }
            .navigationDestination(for: Int.self   ) { IntegerView(someInteger: $0) }
            .navigationTitle("Wizards")

        }
        .onAppear{ wizards = Wizard.sampleStudents }  // Populate with sample students
    }
}

// This is row you see in the tappable LIST view
struct RowView: View {
    var wizardName: String
    var body: some View {
        HStack {
            Text("🧙🏾‍♂️").padding(.trailing); Text(wizardName)
        }.font(.title)
    }
}

// This is the destination for Wizards ========================== DESTINATION
struct WizardView: View {
    var theWizard: Wizard     // Object to display
    var body: some View {
        VStack(alignment: .leading) {
            Text("🧙🏾‍♂️ \(theWizard.name)").font(.largeTitle)
            Text(theWizard.house).font(.title3)
        }.padding(50).background { Color.cyan.opacity(0.3) }
    }
}

// This is the destination for Integers ======================== DESTINATION
struct IntegerView: View {
    var someInteger:     Int
    var backgroundColor: Color { someInteger > 50 ? .cyan.opacity(0.4) : .indigo.opacity(0.4) }
    var body: some View {
        ZStack {
            Rectangle().fill(backgroundColor).frame(width: 200, height: 200)
            Text("\(someInteger)").font(.largeTitle)
        }
    }
}

Keep Coding!

3      

thanks for the explanation. Very helpful

3      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.