SwiftUI Selection in List

<body><p>I've got a MacOS app and I want to be able to select a particular item in a List. Here's an outline of what I'm trying to do:


import SwiftUI


struct SomeObject : Identifiable {

let id = UUID()

let name: String

// lots of other stuff

}


struct ContentView: View {

var someObjects: [SomeObject]

@Binding var selectedObject: SomeObject

var body: some View {

List(someObjects, selection: $selectedObject) { someObject in

Text(someObject.name)

}

}

init(_ someObjects: [SomeObject]) {

self.someObjects = someObjects

}

}


I get the error "Type of expression is ambiguous without more context" on the Text initializer, with the "n" in someObject.name underlined in red.


What does this error mean? If I remove the selection: from the List initializer and take out the @Binding line, it compiles fine.

Replies

I realize I didn't say what I want out of the selection. I could use either the selected SomeObject, or the index into the someObjects array. I want to display the object in another view.

I believe that error usuall means that the parameters you passed into the function (in this case, List), does not match the signiture of the function.


I got rid of the error by making SomeObject also conform to the Hashable protocol. This may be a decent ammount of work depending on what the "lots of other stuff" includes.


struct SomeObject : Identifiable, Hashable {
    let id = UUID()
    let name: String
    // lots of other stuff
}

struct ContentView: View {
    var someObjects: [SomeObject]
    @Binding var selectedObject: SomeObject?

    var body: some View {
        List(someObjects, selection: $selectedObject) { item in
            Text(item.name)
        }
    }
}


edit: also, be sure to check out NavigationView to see if that does what you need. Based on your second comment, it sounds like that may be what you want.

Thanks for the reply.


I tried your suggestion, and indeed, ContentView compiles. However, I get the an error in AppDelegate saying that selectedObject: is missing from the call to ContentView(). Couldn't figure out how to fix this. Maybe I don't really understand @Binding?


To be honest, I couldn't work out from the doc what a NavigationView does, so I just tried it. I got a view that looked like that top part of a TabView.


This is a Mac App, and I already have a view to show the selected SomeObject, I just want it to update when the selection in ContentView changes.

Maybe this article will be more useful on NavigationView. It has an example near the begining that should get you started. The example is iOS, but (through the magic of declarative programming) the same code should work on MacOS.


When there is a binding, you still need to pass the value in as an input when you create the view. One way you can do this is by creating a state in the parent view and accessing the Binding of the state (the variable name prefixed with $):

struct HappyView: View {
    @State var someObject:SomeObject?
    var body: some View {
        ContentView(someObjects: [], selectedObject: $someObject)
    }
}


Overall, I would recomend watching the SwiftUI WWDC sessions, and going through the tutorial. Getting an understanding of the foundations is important before trying to dive into something (especially when it's as new as SwiftUI).


Good luck!

(Huh... having trouble editing this)

I tried fleshing out this code:


//

// AppDelegate.swift

// SimpleMacApp

//

// Created by Eric Mader on 8/26/19.

// Copyright © 2019 Eric Mader. All rights reserved.

//


import Cocoa

import SwiftUI


@NSApplicationMain

class AppDelegate: NSObject, NSApplicationDelegate {


var window: NSWindow!


let someObjects: [SomeObject] = [

SomeObject(name: "An object", otherStuff: [

"An object 1",

"An object 2"

]),

SomeObject(name: "Another Object", otherStuff: [

"Another object 1",

"Another object 2"

])

]


func applicationDidFinishLaunching(_ aNotification: Notification) {

// Insert code here to initialize your application

window = NSWindow(

contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),

styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],

backing: .buffered, defer: false)

window.center()

window.setFrameAutosaveName("Main Window")


window.contentView = NSHostingView(rootView: ContentView(someObjects))


window.makeKeyAndOrderFront(nil)

}


func applicationWillTerminate(_ aNotification: Notification) {

// Insert code here to tear down your application

}



}


//

// ContentView.swift

// SimpleMacApp

//

// Created by Eric Mader on 8/26/19.

// Copyright © 2019 Eric Mader. All rights reserved.

//


import SwiftUI


struct ContentView: View {

var someObjects: [SomeObject]


@State var selectedObject: SomeObject?


var body: some View {

HStack {

SomeObjectView(someObjects: someObjects, selectedObject: $selectedObject)

OtherStuff(selectedObject: $selectedObject)

}

}


init(_ someObjects: [SomeObject]) {

self.someObjects = someObjects

}

}


//

// SomeObject.swift

// SimpleMacApp

//

// Created by Eric Mader on 9/2/19.

// Copyright © 2019 Eric Mader. All rights reserved.

//


import Foundation

import SwiftUI


struct SomeObject : Identifiable, Hashable {

let id = UUID()

let name: String


let otherStuff: [String]

}


struct SomeObjectView : View {

var someObjects: [SomeObject]


@Binding var selectedObject: SomeObject?


var body: some View {

List(someObjects, selection: $selectedObject) { someObject in

Text(someObject.name)

}

}

}


//

// OtherStuff.swift

// SimpleMacApp

//

// Created by Eric Mader on 9/2/19.

// Copyright © 2019 Eric Mader. All rights reserved.

//


import SwiftUI


struct OtherStuff: View {

@Binding var selectedObject: SomeObject?

var body: some View {

List(selectedObject?.otherStuff ?? ["(no selection)"], id: \.self) {stuff in

Text(stuff)

}

}

}


This compiles and runs, but the selection doesn't work. Here's a screenshot:

Huh... it didn't show the screenshot either. :-(


The screenshot just shows the two views. The OtherStuff view just shows "(no selection)." Selecting in the SomeObjectView doesn't seem to have any effect.

I tried following the example in the article about NavigationView:


//
//  SomeObject.swift
//  SimpleMacApp
//
//  Created by Eric Mader on 9/2/19.
//  Copyright © 2019 Eric Mader. All rights reserved.
//

import Foundation
import SwiftUI

struct SomeObject : Identifiable, Hashable {
    let id = UUID()
    let name: String
    
    let otherStuff: [String]
}

struct SomeObjectView : View {
    var someObjects: [SomeObject]
    
    @Binding var selectedObject: SomeObject?
    
    var body: some View {
        NavigationView {
            List(someObjects) { someObject in
                NavigationLink(destination: OtherStuff(selectedObject: someObject)) {
                    Text(someObject.name)
                }
            }
            .navigationBarTitle(Text("Objects"))
        }
    }
}


This doesn't compile. It highlights the "n" in someObject.name and says "Type of expression is ambiguous without more context." It compiles fine if I remove the NavigationView and NavigationLink.

I must miss something. I do not see where

var someObjects: [SomeObject]

is initialized

It's initialzied in AppDelegate.swift. (I know, it's kind of lame)