SwiftUI matchedGeometry + LazyVStack = falha

Oct 18 2020

Levei horas para construir este exemplo e não tenho certeza se estou fazendo algo errado ou se há um bug travando o aplicativo ao usar matchedGeometry+ LazyVStack.

No vídeo abaixo, o aplicativo travou quando eu cliquei no terceiro retângulo (que não estava visível quando o aplicativo foi iniciado). Crash desaparece se eu substituir LazyVStackpor VStack, mas obviamente eu quero carregar minhas coisas preguiçosamente.

Versão do Xcode: Versão 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
                        }
                    }
            }
        }
    }
}

Respostas

Asperi Oct 18 2020 at 14:06

O problema é que você destrói o ScrollViewlayout correspondente.

Aqui está a variante fixa. Testado com 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
                        }
                    }
                        }
            }
        }
    }
}