SwiftUI dopasowanyGeometry + LazyVStack = awaria

Oct 18 2020

Skonstruowanie tego przykładu zajęło mi godziny i nie jestem pewien, czy robię coś nie tak, czy jest błąd powodujący awarię aplikacji podczas używania matchedGeometry+ LazyVStack.

Na poniższym filmie aplikacja uległa awarii, gdy kliknąłem trzeci prostokąt (który nie był widoczny po uruchomieniu aplikacji). Awaria znika, jeśli zastąpię LazyVStackją VStack, ale oczywiście chcę leniwie ładować swoje rzeczy.

Wersja Xcode: Wersja 12.0.1 (12A7300)

struct ContentView: View {
    @Namespace var namespace
    @State var selected: Int?


    var body: some View {
        ZStack {
            VStack { 
                Text("Cool rectangles")
                
                if selected == nil {
                    ScrollView(.vertical, showsIndicators: false) {
                        BoxList(namespace: namespace, selected: $selected)
                    }
                }
            }
            
            if let id = selected {
                Rectangle()
                    .foregroundColor(.red)
                    .matchedGeometryEffect(id: id, in: namespace)
                    .onTapGesture {
                        withAnimation{
                            selected = nil
                        }
                    }

            }

        }
    }
}

struct BoxList: View {
    let namespace: Namespace.ID
    @Binding var selected: Int?
    
    var body: some View {
        LazyVStack {
            ForEach(0..<10){ item in
                Rectangle()
                    .matchedGeometryEffect(id: item, in: namespace)
                    .frame(width: 200, height: 200)
                    .onTapGesture {
                        withAnimation {
                            selected = item
                        }
                    }
            }
        }
    }
}

Odpowiedzi

Asperi Oct 18 2020 at 14:06

Problem polega na tym, że niszczysz ScrollViewłamanie dopasowanego układu.

Oto wariant stały. Testowane z Xcode 12 / iOS 14

struct ContentView: View {
    @Namespace var namespace
    @State var selected: Int?


    var body: some View {
        ZStack {
            VStack {
                Text("Cool rectangles")
                
                  ScrollView(.vertical, showsIndicators: false) {
                        BoxList(namespace: namespace, selected: $selected)
                  }.opacity(selected == nil ? 1 : 0)
            } // << or place here opacity modifier here
            
            if let id = selected {
                Rectangle()
                    .foregroundColor(.red)
                    .matchedGeometryEffect(id: id, in: namespace)
                    .onTapGesture {
                        withAnimation{
                            selected = nil
                        }
                    }

            }

        }
    }
}

struct BoxList: View {
    let namespace: Namespace.ID
    @Binding var selected: Int?
    
    var body: some View {
        LazyVStack {
            ForEach(0..<10){ item in
                    if item == selected {
                Color.clear     // placeholder to avoid duplicate match id run-time warning
                    .frame(width: 200, height: 200)
                    } else {
                Rectangle()
                    .matchedGeometryEffect(id: item, in: namespace)
                    .frame(width: 200, height: 200)
                    .onTapGesture {
                        withAnimation {
                            selected = item
                        }
                    }
                        }
            }
        }
    }
}