수정 기능 추가하기
Leading edge부에도 SwipeAction을 추가하여 수정을 하게 만들어보려고한다.
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)
}
}
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을 해보았는데
true
false
true
false
이런식으로 계속 바뀌는걸 확인했다.
그러다 StackOverflow 에 비슷한 글을 보았고 List 쪽에 modifier를 써야한다는걸 알았다.
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를 한 곳에 모아두어 관리하기 쉽고 일관되지만, 개별 항목의 편집 상태를 세밀하게 관리하는 데는 부적합하다.
또 다른 문제 해결
회고를 하던 도중
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에 출력을 해보면
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)가 다른 결과를 출력할 수 있다.
해결 방법
현재 편집 중인 항목의 데이터를 선택된 항목의 상태로 관리하여 동일한 데이터를 참조하게 하는 방법을 사용한다.
수정된 코드 예시
@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에서 같은 데이터를 참조하게 함으로써 문제를 해결할 수 있다.