List
Skip support for SwiftUI.List on Android. Consult the SkipUI module for a complete list of supported SwiftUI.
The following example screens and source code is from SkipUI’s
Showcase sample app
ListPlayground.swift
import SwiftUI
enum ListPlaygroundType: String, CaseIterable {
case fixedContent
case indexedContent
case collectionContent
case forEachContent
case sectioned
case empty
case emptyItems
case plainStyle
case plainStyleSectioned
case plainStyleEmpty
case hiddenBackground
case hiddenBackgroundPlainStyle
case editActions
case observableEditActions
case sectionedEditActions
case onMoveDelete
var title: String {
switch self {
case .fixedContent:
return "Fixed Content"
case .indexedContent:
return "Indexed Content"
case .collectionContent:
return "Collection Content"
case .forEachContent:
return "ForEach Content"
case .sectioned:
return "Sectioned"
case .empty:
return "Empty"
case .emptyItems:
return "Empty Items"
case .plainStyle:
return "Plain Style"
case .plainStyleSectioned:
return "Plain Style Sectioned"
case .plainStyleEmpty:
return "Plain Style Empty"
case .hiddenBackground:
return "Hidden Background"
case .hiddenBackgroundPlainStyle:
return "Hidden Background Plain Style"
case .editActions:
return "EditActions"
case .observableEditActions:
return "Observable EditActions"
case .sectionedEditActions:
return "Sectioned EditActions"
case .onMoveDelete:
return ".onMove, .onDelete"
}
}
}
struct ListPlayground: View {
@StateObject var editActionsModel = ObservableEditActionsListPlayground.Model()
var body: some View {
List(ListPlaygroundType.allCases, id: \.self) { type in
NavigationLink(type.title, value: type)
}
.toolbar {
PlaygroundSourceLink(file: "ListPlayground.swift")
}
.navigationDestination(for: ListPlaygroundType.self) {
switch $0 {
case .fixedContent:
FixedContentListPlayground()
.navigationTitle($0.title)
case .indexedContent:
IndexedContentListPlayground()
.navigationTitle($0.title)
case .collectionContent:
CollectionContentListPlayground()
.navigationTitle($0.title)
case .forEachContent:
ForEachContentListPlayground()
.navigationTitle($0.title)
case .sectioned:
SectionedListPlayground()
.navigationTitle($0.title)
case .empty:
EmptyListPlayground()
.navigationTitle($0.title)
case .emptyItems:
EmptyItemsListPlayground()
.navigationTitle($0.title)
case .plainStyle:
PlainStyleListPlayground()
.navigationTitle($0.title)
case .plainStyleSectioned:
PlainStyleSectionedListPlayground()
.navigationTitle($0.title)
case .plainStyleEmpty:
PlainStyleEmptyListPlayground()
.navigationTitle($0.title)
case .hiddenBackground:
HiddenBackgroundListPlayground()
.navigationTitle($0.title)
case .hiddenBackgroundPlainStyle:
HiddenBackgroundPlainStyleListPlayground()
.navigationTitle($0.title)
case .editActions:
EditActionsListPlayground()
.navigationTitle($0.title)
case .observableEditActions:
ObservableEditActionsListPlayground(model: editActionsModel)
.navigationTitle($0.title)
case .sectionedEditActions:
SectionedEditActionsListPlayground()
.navigationTitle($0.title)
case .onMoveDelete:
OnMoveDeleteListPlayground()
.navigationTitle($0.title)
}
}
}
}
struct FixedContentListPlayground: View {
var body: some View {
List {
Group {
Text("Row 1")
Text("Row 2")
}
VStack {
Text("Row 3a")
Text("Row 3b")
}
}
}
}
struct IndexedContentListPlayground: View {
var body: some View {
List(100..<200) {
Text("Row \($0)")
}
}
}
struct CollectionContentListPlayground: View {
struct ListItem {
let i: Int
let s: String
}
func items() -> [ListItem] {
var items: [ListItem] = []
for i in 0..<100 {
items.append(ListItem(i: i, s: "Item \(i)"))
}
return items
}
var body: some View {
List(items(), id: \.i) {
Text($0.s)
}
}
}
struct ForEachContentListPlayground: View {
struct ListItem {
let id: UUID // Test ID serialization
let i: Int
let s: String
}
func items() -> [ListItem] {
var items: [ListItem] = []
for i in 0..<10 {
items.append(ListItem(id: UUID(), i: i, s: "Foreach object row \(i)"))
}
return items
}
var body: some View {
List {
Text("Standalone row 1")
ForEach(0..<10) { index in
Text("ForEach index row: \(index)")
}
Text("Standalone row 2")
ForEach(items(), id: \.id) {
Text($0.s)
}
Text("Standalone row 3")
ForEach(0..<10) { index1 in
ForEach(1..<3) { index2 in
Text("Nested ForEach row: \(index1).\(index2)")
}
}
}
}
}
struct SectionedListPlayground: View {
var body: some View {
List {
Section("Section 1") {
Text("Row 1.1")
ForEach(0..<10) { index in
Text("ForEach row: 1.\(index)")
}
}
Section {
Text("Row 2.1")
ForEach(0..<10) { index in
Text("ForEach row: 2.\(index)")
}
} header: {
Text("Section 2")
} footer: {
Text("Footer 2")
}
ForEach(0..<2) { index1 in
Section("ForEach section \(index1)") {
ForEach(0..<5) { index2 in
Text("ForEach row: \(index1).\(index2)")
}
}
}
}
}
}
struct EmptyListPlayground: View {
var body: some View {
List {
}
}
}
struct EmptyItemsListPlayground: View {
var body: some View {
List {
EmptyView()
Text("After initial empty row")
Section("Section 1") {
EmptyView()
Text("After initial section empty row")
EmptyView()
Text("After another section empty row")
}
Section("Section 2") {
Text("Before final section empty row")
EmptyView()
}
Text("Before final empty row")
EmptyView()
}
}
}
struct PlainStyleListPlayground: View {
var body: some View {
List(0..<100) {
Text("Row \($0)")
}
.listStyle(.plain)
}
}
struct PlainStyleSectionedListPlayground: View {
var body: some View {
List {
Section("Section 1") {
Text("Row 1.1")
ForEach(0..<30) { index in
Text("ForEach row: 1.\(index)")
}
}
Section {
Text("Row 2.1")
ForEach(0..<30) { index in
Text("ForEach row: 2.\(index)")
}
} header: {
Text("Section 2")
} footer: {
Text("Footer 2")
}
Section {
ForEach(0..<30) { index in
Text("ForEach row: 3.\(index)")
}
} footer: {
Text("Footer 3")
}
}
.listStyle(.plain)
}
}
struct PlainStyleEmptyListPlayground: View {
var body: some View {
List {
}
.listStyle(.plain)
}
}
struct HiddenBackgroundListPlayground: View {
var body: some View {
ZStack {
Color.yellow.opacity(0.5)
List {
Section("Standard Row Background") {
ForEach(0..<30) { index in
Text("Row: 1.\(index)")
}
}
Section {
ForEach(0..<30) { index in
Text("Row: 2.\(index)")
.listRowBackground(Color.pink)
.listRowSeparator(.hidden)
}
} header: {
Text("Pink Row Background")
} footer: {
Text("... and hidden separators")
}
}
.scrollContentBackground(.hidden)
}
}
}
struct HiddenBackgroundPlainStyleListPlayground: View {
var body: some View {
ZStack {
Color.yellow.opacity(0.5)
List {
Section("Section 1") {
ForEach(0..<30) { index in
Text("Row: 1.\(index)")
}
}
Section {
ForEach(0..<30) { index in
Text("Row: 2.\(index)")
.listRowBackground(Color.pink)
.listRowSeparator(.hidden)
}
} header: {
Text("Section 2")
} footer: {
Text("Footer")
}
}
.listStyle(.plain)
.scrollContentBackground(.hidden)
}
}
}
struct EditActionsListPlayground: View {
struct ListItem {
let i: Int
let s: String
var toggled = false
}
@State var items = {
var items: [ListItem] = []
for i in 0..<50 {
items.append(ListItem(i: i, s: "Item \(i)"))
}
return items
}()
var body: some View {
List($items, id: \.i, editActions: .all) { $item in
if item.i % 5 == 0 {
Text("\(item.s) .deleteDisabled")
.deleteDisabled(true)
} else if item.i % 4 == 0 {
Text("\(item.s) .moveDisabled")
.moveDisabled(true)
} else {
HStack {
Text(item.s)
Spacer()
Toggle("isOn", isOn: $item.toggled)
.labelsHidden()
}
}
}
}
}
struct ObservableEditActionsListPlayground: View {
class Model: ObservableObject {
@Published var items: [ListItem] = {
var items: [ListItem] = []
for i in 0..<50 {
items.append(ListItem(i: i, s: "Item \(i)"))
}
return items
}()
}
struct ListItem {
let i: Int
let s: String
var toggled = false
}
@ObservedObject var model: Model
var body: some View {
List($model.items, id: \.i, editActions: .all) { $item in
HStack {
Text(item.s)
Spacer()
Toggle("isOn", isOn: $item.toggled)
.labelsHidden()
}
}
}
}
struct SectionedEditActionsListPlayground: View {
@State var items0 = {
var items: [Int] = []
for i in 0..<10 {
items.append(i)
}
return items
}()
@State var items1 = {
var items: [Int] = []
for i in 11..<20 {
items.append(i)
}
return items
}()
@State var items2 = {
var items: [Int] = []
for i in 21..<30 {
items.append(i)
}
return items
}()
var body: some View {
List {
Section("Section 0 (Fixed)") {
ForEach(items0, id: \.self) { item in
Text("Item \(item)")
}
}
Section("Section 1") {
ForEach($items1, id: \.self, editActions: .all) { $item in
Text("Item \(item)")
}
}
Section("Section 2") {
ForEach($items2, id: \.self, editActions: .all) { $item in
Text("Item \(item)")
}
}
}
}
}
struct OnMoveDeleteListPlayground: View {
@State var items0 = {
var items: [Int] = []
for i in 0..<10 {
items.append(i)
}
return items
}()
@State var items1 = {
var items: [Int] = []
for i in 11..<20 {
items.append(i)
}
return items
}()
@State var items2 = {
var items: [Int] = []
for i in 21..<30 {
items.append(i)
}
return items
}()
@State var actionString = ""
@State var action: () -> Void = {}
@State var actionIsPresented = false
var body: some View {
List {
Section(".onMove") {
ForEach(items0, id: \.self) { item in
Text("Item \(item)")
}
.onMove { fromOffsets, toOffset in
actionString = "Move \(fromOffsets.count) item(s)"
action = {
withAnimation { items0.move(fromOffsets: fromOffsets, toOffset: toOffset) }
action = {}
}
actionIsPresented = true
}
}
Section(".onDelete") {
ForEach(items1, id: \.self) { item in
Text("Item \(item)")
}
.onDelete { offsets in
actionString = "Delete \(offsets.count) item(s)"
action = {
withAnimation { items1.remove(atOffsets: offsets) }
action = {}
}
actionIsPresented = true
}
}
Section(".onMove, .onDelete") {
ForEach(items2, id: \.self) { item in
Text("Item \(item)")
}
.onMove { fromOffsets, toOffset in
actionString = "Move \(fromOffsets.count) item(s)"
action = {
withAnimation { items2.move(fromOffsets: fromOffsets, toOffset: toOffset) }
action = {}
}
actionIsPresented = true
}
.onDelete { offsets in
actionString = "Delete \(offsets.count) item(s)"
action = {
withAnimation { items2.remove(atOffsets: offsets) }
action = {}
}
actionIsPresented = true
}
}
}
.confirmationDialog(actionString, isPresented: $actionIsPresented) {
Button(actionString, role: .destructive, action: action)
}
}
}