read

List is the equivalent of UITableView or NSTableView.

A simple list of items

struct ListDemo: View {
    var items = ["China", "United States"]

    var body: some View {
        List(items, id: \.self) { item in
            Text(item)
        }
        .listStyle(GroupedListStyle())
    }
}

List requires an item’s keypath, to use it as an identifier. For this array of String, simply use itself – \.self. Custom models should implement the protocol Identifiable.

The example above uses GroupedListStyle().

Selecting a row

There is no UITableViewDelegate to callback when a row is selected.

The following is a workaround.

List(items, id: \.self) { item in
    HStack {
        Text(item)
        Spacer()
    }
    .contentShape(Rectangle())
    .onTapGesture {
        // Handle the item
    }

The HStack is used as a row, with a spacer so that the whole width is filled.

The contentShape(Rectangle()) makes it possible to tap on the Spacer()!

contentShape defines the content shape for hit testing.

Dismiss the view

This is not about a list, but after selecting a row, you can dismiss the modal view using presentationMode.

// Declare the environment
@Environment(\.presentationMode) var presentationMode

// Call dismiss
.onTapGesture {
    self.presentationMode.wrappedValue.dismiss()
}

Change row background

The row background can be set with listRowBackground(). But our code above will somehow not work. eg. HStack(...).listRowBackground(Color.blue) will do nothing.

Strangely, but using ForEach will work.

List {
    ForEach(items, id: \.self) { item in
        Text(item)
            .listRowBackground(Color.blue)
    }
}

And that brings us to the next section, which tells us ForEach should be preferred over convenience List.init(_: id:).

Section, and the use of ForEach

You need multiple ForEach when you have multiple sections in the table.

List() {
    Section(header: Text("Section 1"), footer: Color.blue) {
        ForEach(items, id: \.self) { item in
            Text(item)
        }
    }

    // Another section
    Section() { }

    // In fact you can mix any kind of view in a List
    Image(systemName: "bolt")
}

Edit, delete, and move

You handle with onDelete() and onMove() in the ForEach (NOT List).

struct EditListDemo: View {
    @State var items = ["iPhone", "iPad", "Apple Watch", "Apple TV"]

    var body: some View {
        List {
            ForEach(items, id: \.self) { item in
                Text(item)
            }
            .onDelete { set in
                items.remove(atOffsets: set)
            }
            .onMove { set, i in
                items.move(fromOffsets: set, toOffset: i)
            }
        }
        .navigationBarItems(trailing: EditButton())
    }
}

Drag and drop

Drag and drop is via the onInsert() method.

// Must import this for the UTI types
import MobileCoreServices

.onInsert(of: [String(kUTTypeText)]) { i, itemProviders in
    for provider in itemProviders {
        if provider.canLoadObject(ofClass: String.self) {
            _ = provider.loadObject(ofClass: String.self) { s, error in
                self.items.insert(s!, at: i)
            }
        }
    }
}

The above handles 1 type – a text. It can be multiple types.

The NSItemProviders will be able to resolve the type, and you insert into your items.

Selection

To select multiple rows, provide the Binding to List.

@State var items = ["black", "lives", "matter"]
@State var selection = Set<String>()

List(items, id: \.self, selection: $selection) {
    Text($0)
}
.navigationBarItems(trailing: EditButton())

NOTE: There’s a bug where selection will not work if the list is in a Form.


Image

@samwize

¯\_(ツ)_/¯

Back to Home