Search code examples
swiftswiftuiswiftui-listswiftui-navigationlink

SwiftUI SceneDelegate - contentView Missing argument for parameter 'index' in call


I am trying to create a list using ForEach and NavigationLink of an array of data.

I believe my code (see the end of the post) is correct but my build fails due to "Missing argument for parameter 'index' in call" and takes me to SceneDelegate.swift a place I haven't had to venture before.

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

I can get the code to run if I amend to;

let contentView = ContentView(habits: HabitsList(), index: 1)

but then all my links hold the same data, which makes sense since I am naming the index position.

I have tried, index: self.index (which is what I am using in my NavigationLink) and get a different error message - Cannot convert value of type '(Any) -> Int' to expected argument type 'Int'

Below are snippets of my code for reference;

struct HabitItem: Identifiable, Codable {
    let id = UUID()
    let name: String
    let description: String
    let amount: Int
}

class HabitsList: ObservableObject {
    @Published var items = [HabitItem]()
}

struct ContentView: View {
    @ObservedObject var habits = HabitsList()
    @State private var showingAddHabit = false
    var index: Int
        
    var body: some View {
        NavigationView {
            List {
                ForEach(habits.items) { item in
                    NavigationLink(destination: HabitDetail(habits: self.habits, index: self.index)) {
                        HStack {
                            VStack(alignment: .leading) {
                                Text(item.name)
                                    .font(.headline)
                                Text(item.description)
                            }
                        }
                    }
                }
            }
        }
    }
}

struct HabitDetail: View {
    @Environment(\.presentationMode) var presentationMode
    @ObservedObject var habits: HabitsList

    var index: Int
    
    var body: some View {
        NavigationView {
            Form {
                Text(self.habits.items[index].name)
            }
        }
    }
}

Solution

  • You probably don't need to pass the whole ObservedObject to the HabitDetail.

    Passing just a HabitItem should be enough:

    struct HabitDetail: View {
        @Environment(\.presentationMode) var presentationMode
        let item: HabitItem
    
        var body: some View {
            // remove `NavigationView` form the detail view
            Form {
                Text(item.name)
            }
        }
    }
    

    Then you can modify your ContentView:

    struct ContentView: View {
        @ObservedObject var habits = HabitsList()
        @State private var showingAddHabit = false
    
        var body: some View {
            NavigationView {
                List {
                    // for every item in habits create a `linkView`
                    ForEach(habits.items, id:\.id) { item in
                        self.linkView(item: item)
                    }
                }
            }
        }
        
        // extract to another function for clarity
        func linkView(item: HabitItem) -> some View {
            // pass just a `HabitItem` to the `HabitDetail`
            NavigationLink(destination: HabitDetail(item: item)) {
                HStack {
                    VStack(alignment: .leading) {
                        Text(item.name)
                            .font(.headline)
                        Text(item.description)
                    }
                }
            }
        }
    }