To Do List (4)
수정 기능 추가하기
Leading edge부에도 SwipeAction을 추가하여 수정을 하게 만들어보려고한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
VStack {
List(todoLists, id: \.self) { list in
CellView(isOn: list.isCompleted,
title: "\(list.title) id: \(list.id)")
.swipeActions(edge: .trailing) {
Button(action: {
modelContext.delete(list)
}) {
Image(systemName: "trash")
}
}
.tint(.red)
.swipeActions(edge: .leading) {
Button("edit",
systemImage: "pencil") {
isEditing.toggle()
}
.alert("TodoList 수정",
isPresented: $isEditing) {
TextField("TodoList 수정", text: $title)
Button("OK",
role: .cancel) {
print("work")
}
Button("Cancel", role: .destructive){
}
}
}
.tint(.blue)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
VStack {
List(todoLists, id: \.self) { list in
CellView(isOn: list.isCompleted,
title: "\(list.title)")
.swipeActions(edge: .trailing) {
Button(action: {
modelContext.delete(list)
}) {
Image(systemName: "trash")
}
}
.tint(.red)
.swipeActions(edge: .leading) {
Button("edit",
systemImage: "pencil") {
print(isEditing)
isEditing.toggle()
print(isEditing)
}
}
.tint(.blue)
.alert("TodoList 수정",
isPresented: $isEditing) {
TextField("TodoList 수정", text: $title)
Button("OK",
role: .cancel) {
print("work")
}
Button("Cancel", role: .destructive){
}
}
}
}
현재 Delete와 유사하게 코드를 작성했다.
하지만 작동하지 않았다.
그래서 isEditing 변수에 관해 어떻게 작동을 하는지 확인을 하기위해 print을 해보았는데
1
2
3
4
true
false
true
false
이런식으로 계속 바뀌는걸 확인했다.
그러다 StackOverflow 에 비슷한 글을 보았고 List 쪽에 modifier를 써야한다는걸 알았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
VStack {
List(todoLists, id: \.self) { list in
CellView(isOn: list.isCompleted,
title: "\(list.title) id: \(list.id)")
.swipeActions(edge: .trailing) {
Button(action: {
modelContext.delete(list)
}) {
Image(systemName: "trash")
}
}
.tint(.red)
.swipeActions(edge: .leading) {
Button("edit",
systemImage: "pencil") {
isEditing.toggle()
}
}
.tint(.blue)
}
.alert("TodoList 수정",
isPresented: $isEditing) {
TextField("TodoList 수정", text: $title)
Button("OK",
role: .cancel) {
print("work")
}
Button("Cancel", role: .destructive){
}
}
}
작동이 된다.
구분 | 첫 번째 코드 (CellView 에서 alert 적용) | 두 번째 코드 (List 에서 alert 적용) |
---|---|---|
alert 적용 위치 | CellView 에 개별적으로 적용 | List 전체에 적용 |
동작 결과 | 작동 안됨: alert 가 표시되지 않음 | 정상 작동: alert 가 정상적으로 표시됨 |
작동하지 않는 이유
List
에서의 뷰 재사용 문제:SwiftUI
의List
는 내부적으로 뷰를 재사용한다. 즉, 셀(Cell)이 다시 그려질 때 뷰가 재사용될 수 있기 때문에, 개별CellView
에 대한 상태(State) 관리가 예상대로 동작하지 않을 수 있다.CellView
에 직접alert
를 적용하면 SwiftUI가 이 상태를 적절히 처리하지 못해,alert
가 표시되지 않는 문제가 발생한다.
- 뷰의 계층 구조:
alert
는 보통 상위 뷰에서 상태를 관리하며, 하위 뷰가 그 상태 변화를 인식할 수 있도록 한다. 첫 번째 코드에서는CellView
에 적용된alert
가 상위List
에 의한 상태 변화를 반영하지 못해,isEditing
상태가 바뀌더라도alert
가 나타나지 않는다.- 두 번째 코드에서는
List
의 상위 계층에alert
가 적용되었기 때문에, 전체 리스트 상태와 연동되어 정상적으로 동작한다.
해결 방법
alert
같은 상태 기반의 모디파이어는 상위 뷰에서 적용해야 한다. 이를 통해 SwiftUI의 상태 관리와 뷰 재사용 메커니즘이 정상적으로 작동하게 된다.- 즉, 상태(State)와 뷰 재사용이 일어나는 리스트 구조에서 안정적으로 작동하려면, 리스트 자체 또는 리스트의 상위 뷰에
alert
같은 모디파이어를 적용하는 것이 좋다.
첫 번째 코드가 작동하지 않는 이유는 SwiftUI의 뷰 재사용과 상태 관리 문제 때문이다. alert를 리스트 수준에서 관리하면 이런 문제를 피할 수 있고, 상태 변경이 제대로 반영되어 alert가 정상적으로 표시된다.
문제해결
구분 | 첫 번째 코드 | 두 번째 코드 |
---|---|---|
alert 위치 | CellView 내부에 있는 각 항목에서 alert 가 개별적으로 정의됨 | List 수준에서 한 번 정의되어 전체 리스트에서 상태를 공유함 |
isEditing 상태 관리 | isEditing 상태가 개별 항목에서 변경될 때마다 alert 가 그 항목에만 영향을 미침 | isEditing 상태가 리스트 전체에서 전역적으로 적용되어, 모든 항목에서 동일하게 반응함 |
상태 변경 시점 | 개별 항목의 상태가 변경될 때마다 그 항목에서 alert 가 뜨도록 유도함 | isEditing 상태가 변경될 때 리스트 전체에서 반응하며, 모든 항목이 동일하게 상태 변화를 겪음 |
가독성 및 유지보수 | 항목별로 alert 가 존재하므로 복잡도가 올라가고 유지보수 시 관리해야 할 항목이 많아짐 | List 수준에서 한 번만 정의되므로 코드가 더 간결하고 유지보수가 쉬움 |
첫 번째 코드:
CellView
내부에alert
가 정의되어 있어, 각 항목마다 개별적으로isEditing
상태를 변경하고, 해당 항목에서만alert
를 띄울 수 있다.- 그러나 여전히
isEditing
상태는 전역적으로 관리되고 있어, 모든 항목에서 동일한isEditing
상태를 공유한다는 점에서, 실질적인 개별 항목별 관리가 되지 않는다.
두 번째 코드:
alert
가List
레벨에서 한 번만 정의되어 있어,isEditing
상태가 변경되면 모든 항목에서 반응하게 된다.- 리스트 전체에서 하나의 상태로 관리되고 있어, 상태 관리가 더 일관성 있게 동작하지만, 개별 항목의 상태를 별도로 관리하고자 할 때는 유연성이 떨어진다.
결론:
- 첫 번째 코드는 각 항목에서 개별적으로
alert
를 정의해 유연성을 제공하지만, 상태 관리 측면에서는 불완전하다. - 두 번째 코드는
alert
를 한 곳에 모아두어 관리하기 쉽고 일관되지만, 개별 항목의 편집 상태를 세밀하게 관리하는 데는 부적합하다.
또 다른 문제 해결
회고를 하던 도중
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
VStack {
List(todoLists, id: \.self) { list in
CellView(isOn: list.isCompleted,
title: "\(list.title)")
.swipeActions(edge: .trailing) {
Button(action: {
modelContext.delete(list)
}) {
Image(systemName: "trash")
}
}
.tint(.red)
.swipeActions(edge: .leading) {
Button("edit",
systemImage: "pencil") {
isEditing.toggle()
print(list.title)
}
}
.tint(.blue)
.alert("TodoList 수정",
isPresented: $isEditing) {
TextField(list.title, text: $title)
Button("OK",
role: .cancel) {
print(list.title)
}
Button("Cancel", role: .destructive){
}
}
}
}
text를 수정할때 가져오는 list.title이 다르다는것을 발견하였다.
즉 나는 첫번째 row에 대해서 내용을 수정하고 싶은데, 정작 수정을 하고보면 두번째 row가 수정이 되는 문제가 발생하였따.
첫번째 row를 버튼 클릭하고 수정 하고 확인 버튼을 눌렀을때 우선 console에 출력을 해보면
1
2
Whew
Wer4334545
이렇게 되어버린다.
즉 같은 list.title이지만 내용은 다르다는것.
print(list.title)
가 swipeActions
와 alert
에서 다르게 출력되는 이유는 SwiftUI의 List
에서 셀 재사용 방식 때문이다.
SwiftUI의 셀 재사용 관련 원인:
뷰의 재사용: SwiftUI의
List
는 셀을 재사용한다. 즉, 셀이 화면에 다시 나타나거나 스크롤될 때 기존의 셀을 새 데이터로 업데이트한다. 이로 인해swipeActions
에서 사용한list
는 그 시점의 데이터를 참조하지만,alert
가 호출될 때는 다른 셀이 재사용될 수 있다.상태의 비동기 처리:
isEditing.toggle()
가 호출되면 상태가 변하지만, SwiftUI는 내부적으로 필요한 부분만 다시 렌더링한다. 이로 인해alert
가 표시될 때의list
와swipeActions
에서의list
가 다르게 출력될 수 있다.뷰의 동작 흐름 차이:
swipeActions
에서는print(list.title)
이 즉시 해당 셀의 데이터를 출력한다.- 그러나
alert
가 표시될 때는 셀이 재사용되거나 다른list
객체가 참조될 수 있어,print(list.title)
가 다른 결과를 출력할 수 있다.
해결 방법
현재 편집 중인 항목의 데이터를 선택된 항목의 상태로 관리하여 동일한 데이터를 참조하게 하는 방법을 사용한다.
수정된 코드 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@State private var selectedList: TodoModel? = nil
VStack {
List(todoLists, id: \.self) { list in
CellView(isOn: list.isCompleted,
title: "\(list.title)")
.swipeActions(edge: .trailing) {
Button(action: {
modelContext.delete(list)
}) {
Image(systemName: "trash")
}
}
.tint(.red)
.swipeActions(edge: .leading) {
Button("edit",
systemImage: "pencil") {
selectedList = list // 현재 선택된 리스트 저장
isEditing.toggle()
print(list.title)
}
}
.tint(.blue)
.alert("TodoList 수정",
isPresented: $isEditing) {
if let selectedList = selectedList {
TextField(selectedList.title, text: $title)
Button("OK", role: .cancel) {
print(selectedList.title) // 선택된 리스트의 title 출력
}
Button("Cancel", role: .destructive) {}
}
}
}
}
정리
- 뷰 재사용 문제로 인해 List에서 list가 다르게 출력될 수 있다.
- 선택된 항목을 상태로 관리하여, alert와 swipeActions에서 같은 데이터를 참조하게 함으로써 문제를 해결할 수 있다.