Search code examples
swiftswiftuimatchedgeometryeffect

Items in List View get affected when applying matchedGeometryEffect to views inside the List View - SwiftUI


I'm trying to transition from the main view to a second view with animation using .matchedGeometryEffect but for some reason, if I apply the .matchedGeometryEffect directly to Text(item.name) only one item is visible, the rest of them get hidden. If I apply the .matchedGeometryEffect directly to the List all items show and I get the animation when it moves to the second view but is not the effect I'm looking for, I get a better effect when I apply .matchedGeometryEffect to Text(item.name).

Any idea why most items in the List disappear when I apply .matchedGeometryEffect to Text(item.name)?

ContentView

struct ContentView: View {
    @State var showEditView = false
    @State var selectedItem:Item?
    @Namespace var editViewAnaimation
    
    let items = [Item(name: "Fork", symbolName: "fork.knife"),
                Item(name: "Hammer", symbolName: "hammer.fill"),
                Item(name: "Lightbulb", symbolName: "lightbulb.fill")]
    
    var body: some View {
        VStack {
            if showEditView{
                Button(action: {
                    withAnimation{
                        showEditView.toggle()
                    }
                }, label: {
                    Image(systemName: "xmark.square.fill")
                        .foregroundColor(Color.red)
                })
                EditView( editViewAnaimation: editViewAnaimation, selectedItem: selectedItem!)
            }else{
                List(items, id:\.self){ item in
                    HStack{
                        Image(systemName: item.symbolName)
                            .foregroundColor(Color.red)
                        Text(item.name)
                            .foregroundColor(Color.blue)
                           /// some items disappear when applied here, why?
                           //.matchedGeometryEffect(id: "name", in: editViewAnaimation)
                    }
                    .onTapGesture {
                        selectedItem = item
                        withAnimation{
                            showEditView.toggle()
                        }
                    }
                }
                /// all items show when applied here but not the effect I want
                .matchedGeometryEffect(id: "name", in: editViewAnaimation)
            }
        }
    }
}

EDIT VIEW

struct EditView: View {
    var editViewAnaimation: Namespace.ID
    let selectedItem:Item
    @State var nameInput = ""
    
    var body: some View {
        ScrollView{
            TextField("name", text: $nameInput)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .matchedGeometryEffect(id: "name", in: editViewAnaimation)
        }
        .padding()
        .onAppear{
            nameInput = selectedItem.name
        }
    }
}

Solution

  • You are using the same id for all the Texts. This means that every Text's frame would match. The "Fork" text would match "Hammer", and would also match "Lightbulb". All of them would be on top of one another, so you only see one text.

    You should put the matchedGeometryEffect modifier on the Texts, and give an appropriate id to each of them. For example, you can use the name of the item as the id:

    Text(item.name)
        .foregroundColor(Color.blue)
        .matchedGeometryEffect(id: item.name, in: editViewAnaimation)
    
    TextField("name", text: $nameInput)
        .textFieldStyle(RoundedBorderTextFieldStyle())
        .matchedGeometryEffect(id: selectedItem.name, in: editViewAnaimation)
    

    You only want the "Fork" Text to match the "Fork" TextField, the "Hammer" Text to match the "Hammer" TextField etc.