SwiftUI 참고 : 상위 뷰에서 목록을 삭제하면 하위 뷰가 index out of range로 떨어집니다.

14366 단어 SwiftUI
충돌하는 패턴과 하지 않는 패턴의 소개.
우선 현재의 메모.

충돌하는 예


  • ListForEach.onDelete 또는 .onMove 설정
  • 행의 내용은, 데이터를 바인드 건네준 아이 뷰에 맡긴다.
  • 겉보기에 문제없이 움직이는 것처럼 보이지만 마지막 행을 삭제하면 충돌
  • import Foundation
    import SwiftUI
    
    // お店は商品を複数持っている
    struct Shop {
        var name: String
        var items: [Item]
    }
    // 商品。とりあえず名前だけ。
    struct Item: Identifiable {
        var id = UUID()
        var name: String
    }
    
    // 親ビュー 
    struct SimpleList: View {
    
        // サンプルデータ。
        @State var shop = Shop(
            name: "くだもの屋さん",
            items: [
                Item(name: "りんご"),
                Item(name: "ばなな"),
                Item(name: "みかん")
            ]
        )
    
        var body: some View {
            VStack {
                Text(shop.name).font(.title)
                HStack(spacing:20) {
                    Button(action: onAdd){ Image(systemName: "plus") }
                    Spacer()
                    EditButton()
                }
                List {
                    // お店の商品でForEach
                    ForEach(shop.items){ item in
                        // 子ビューにぜんぶ任せる、バインドで!
                        SimpleSubView(item: $shop.items[id2index(item.id)])
                    }
                    // 移動・削除の処理
                    .onMove(perform: onMove)
                    .onDelete(perform: onDelete)
                }
            }
            .padding()
        }
    
        // IDからリストのindex番号取得
        func id2index(_ id: UUID) -> Int {
            return shop.items.firstIndex(where: { $0.id == id })!
        }
        // お店の商品追加、移動、削除
        func onAdd() {
            let newItem = Item(name: "new item")
            shop.items.append(newItem)
        }
        func onMove(_ iset: IndexSet, _ newOffset: Int) {
            shop.items.move(fromOffsets: iset, toOffset: newOffset)
        }
        func onDelete(_ iset: IndexSet) {
            shop.items.remove(atOffsets: iset)
        }
    }
    
    // 子ビュー
    struct SimpleSubView: View {
        // 編集したり色々する予定でバインドで受け取っている
        @Binding var item: Item  
        var body: some View {
            Text(item.name)    // が、今はとりあえず、単純に表示
        }
    }
    

    동작 스크린샷








    오류 메시지



    Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
    (타임 스탬프) (프로젝트 이름)[9600:1174243] Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
    (lldb)

    인덱스가 범위외…?

    충돌하지 않는 예



    구구나 시험해 보았던 것은,
  • ForEach(リスト, id:_) 하고 있는 바람에 명시적으로 id를 지시하라」적인 기사를 발견했지만, 지금 현재(2021년 1월) id 지정해도 관계없었다.

  • 아이 뷰에 건네주지 않고, 직접 ForEach내에 쓰면 크래쉬 하지 않는다
  • 바인드가 아닌 값으로 자식 뷰에 전달하면 충돌하지 않습니다

  • ForEach 안에 오랫동안 코드를 쓰는 것은 나중에 보기 힘들고 후회할 것 같다.
    그래서, 아이 뷰에는 값으로 건네주면서, 어떻게든 아이 뷰로부터 원래의 리스트를 갱신하는 방향으로 생각했다.

    목록은 목록에서 별도로 바인딩으로 전달했습니다.



    앞의 코드에서 변경된 사항
    // 親ビューのForEachの中
    // SimpleSubView(item: $shop.items[id2index(item.id)])
    SimpleSubView(item: item, items: $shop.items)
    // 親の id2index(UUID)->Int は不要になる。
    
    // 子ビュー
    struct SimpleSubView: View {
        @State var item: Item          // 普通に値で受け取る
        @Binding var items: [Item]     // 加えてリスト全体をバインドで受け取る
    
        var body: some View {
            HStack {
                // 値で受け取ったitemで編集。コミット時にバインドされたリストに反映
                TextField("name", text: $item.name, onCommit: commit)
            }
        }
    
        // 値をリストに反映する
        func commit() {
            // リストから自分のIDを探してインデックス取得。
            // 見つからない(たぶん親が削除した)なら、何もせず終了。
            guard let index = items.firstIndex(where: { $0.id == item.id })
            else { return }
            // 編集内容をリスト内の商品に反映。
            // indexが存在することは確認済み、out of index とは言わせないぞ
            items[index].name = item.name
        }
    }
    
    

    지워도 괜찮아졌다.
  • 리스트를 바인드로 건네주는 대신, @EnvironmentObject에 넣는 손도 있다.
  • 그쪽으로 위의 commit() 상당한 메소드 만들어, 아이 뷰에서는 item 통째로 건네주는 것만으로, 하면 즐겁게

  • 자식 뷰에서 commit()를 부르는 타이밍은 요 검토.
  • TextFieldonCommit 는 엔터 키를 눌렀을 때라든가이므로, 조작 방법에 의해 발생하지 않는 경우도 있을지도
  • 편집 화면과 표시 화면이 별개로, 편집 끝나면 화면 전환한다, 라든가라면, .onDisappear 로 확실하게 반영할 수 있는 것은?

  • 좋은 웹페이지 즐겨찾기