UITableView와 마찬가지로 List의 요소들을 Section으로 구분할 수 있습니다. Section이라는 뷰로 List에 들어갈 View를 감싸서 구현합니다. (List 안에 작은 List를 다시 구현한다고 생각하면 편합니다.)
DataSource를 통해 title을 구현해야하는 UITableView와는 달리 훨씬 더 편리하게 Header와 Footer를 구현할 수 있습니다. Section을 구현할 때 Header와 Footer를 구성할 뷰를 인자로 전달하면 됩니다.
import SwiftUI
struct Student: Identifiable {
let id = UUID()
let name: String
let grade: Int
}
let students = [
Student(name: "Kim", grade: 23),
Student(name: "Lee", grade: 65),
Student(name: "Park", grade: 47),
Student(name: "Choi", grade: 85),
Student(name: "Jung", grade: 45),
Student(name: "Moon", grade: 100),
Student(name: "Sung", grade: 12),
]
struct ContentView: View {
let over50 = students.filter { $0.grade >= 50 }
let under50 = students.filter { $0.grade < 50 }
var body: some View {
List {
Section {
ForEach(over50) { student in
Text("\(student.name)의 점수는 \(student.grade)점입니다.")
}
} header: {
Text("50점 이상")
} footer: {
Text("참 잘했어요!")
}
Section {
ForEach(under50) { student in
Text("\(student.name)의 점수는 \(student.grade)점입니다.")
}
} header: {
Text("50점 미만")
} footer: {
Text("좀 더 노력하세요!")
}
}
}
}
List에서 원하는 데이터를 삭제하는 메소드입니다. Delegate 패턴으로 구현된 UITableView와는 달리 좀 더 간단하게 구현할 수 있습니다. List의 요소의 onDelete라는 메소드에 삭제할 때 실행할 클로저를 전달하면 됩니다.
해당 클로저는 indexSet을 인자로 받는데요. 이 인자는 리스트를 하나의 Collection으로 간주하고 삭제될 요소의 인덱스 정보를 담고 있습니다. 따라서 해당 클로저 안에서는 아래처럼 List의 DataSource가 되는 배열에서 해당 index에 속한 데이터를 삭제하면 됩니다.
import SwiftUI
struct Student: Identifiable {
let id = UUID()
let name: String
let grade: Int
}
struct ContentView: View {
//⭐️ View 내부에 @State로 선언해야 배열이 변경되면
@State var students = [
Student(name: "Kim", grade: 23),
Student(name: "Lee", grade: 65),
Student(name: "Park", grade: 47),
Student(name: "Choi", grade: 85),
Student(name: "Jung", grade: 45),
Student(name: "Moon", grade: 100),
Student(name: "Sung", grade: 12),
]
var body: some View {
List {
ForEach(students) { student in
Text("\(student.name)의 점수는 \(student.grade)점입니다.")
}
.onDelete { indexSet in
self.students.remove(atOffsets: indexSet)
}
}
}
}
다른 collection의 index들을 나타내기 위한 collection의 일종이라고 합니다. (index는 정수이며, 중복이 불가능하므로 당연히 유일한 정수값들의 collection 이겠죠?)
예를 들어 위에서 사용한 Array의 .remove 메소드는 하나의 index를 인자로 받거나 index들의 collection인 indexSet을 인자로 받을 수도 있습니다.
// 1. 인덱스(= Int)를 인자로
self.students.remove(at: index)
// 2. IndexSet을 인자로
self.students.remove(atOffsets: indexSet)
onMove 메소드는 List의 요소들의 순서를 바꾸는 메소드입니다. onDelete와 마찬가지로 클로저를 인자로 받습니다. 해당 클로저는 인자를 원래 있던 index, 이동할 index 각각 2개를 받는 클로저인데요. 특이하게 첫 번째는 IndexSet 타입이고 두 번째는 Int 타입입니다.
다행히도 Array 타입에는 해당 인자를 똑같이 받아서 element의 순서를 바꾸어주는 메소드가 존재합니다. 따라서 해당 메소드를 클로저 안에서 선언해주면 됩니다.
하지만 이렇게만 구현하면 되는 onDelete와는 다르게 필연적으로 추가해주어야 할 요소가 하나 더 있습니다. 바로 리스트를 Edit 모드로 바꾸어주는 EditButton입니다. EditButton은 이미 모든 것이 SwiftUI에 구현되어 있습니다. 우리가 할 일은 어디에 위치할 것인지만 정해주면 됩니다. 간단하게 Navigation Bar를 구현한 후 Navigation Bar의 오른쪽에 버튼을 위치하도록 하겠습니다.
아래 코드를 통해서 Edit버튼을 누르고 Drag & Drop을 통해서 리스트의 순서를 바꿀 수 있게 됩니다.
import SwiftUI
struct Student: Identifiable {
let id = UUID()
let name: String
let grade: Int
}
struct ContentView: View {
@State var students = [
Student(name: "Kim", grade: 23),
Student(name: "Lee", grade: 65),
Student(name: "Park", grade: 47),
Student(name: "Choi", grade: 85),
Student(name: "Jung", grade: 45),
Student(name: "Moon", grade: 100),
Student(name: "Sung", grade: 12),
]
var body: some View {
NavigationView {
List {
ForEach(students) { student in
Text("\(student.name)의 점수는 \(student.grade)점입니다.")
}
.onDelete { indexSet in
self.students.remove(atOffsets: indexSet)
}
.onMove { fromIndexSet, toIndex in
self.students.move(fromOffsets: fromIndexSet, toOffset: toIndex)
}
}
.navigationTitle("중간고사 점수")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
}
}
}
}