最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Animate cell transition to detail view in SwiftUI - Stack Overflow

programmeradmin2浏览0评论

I want to animate the transition from a LazyHStack cell to a detail view in my iOS app, as Instagram does when you click on a story.

The detail view should transition from the cell frame to a full-screen page. And vice versa when closing the detail view.

So I guess the animation should deal with the position and scale of the detail view, but I don't know how to specify a start and end. I know GeometryReader can help me get the cell size for the starting frame, but I'm unsure how to specify the start and end frame with SwiftUI.

EDIT:

Here's a sample code (based on Alexis answer and matchedGeometryEffect ), but it doesn't work exactly as I would like :

struct ContentView: View {
    @Namespace private var animationNamespace
    @State private var selectedIndex: Int? = nil
    
    var body: some View {
        ZStack {
            if let index = selectedIndex {
                DetailView(namespace: animationNamespace,
                           selectedIndex: index) {
                    selectedIndex = nil
                }
            } else {
                ScrollView(.horizontal) {
                    LazyHStack(spacing: 10) {
                        ForEach(0..<5) { index in
                            RoundedRectangle(cornerRadius: 20)
                                .fill(Color.blue)
                                .frame(width: 80, height: 80)
                                .matchedGeometryEffect(id: "container\(index)", in: animationNamespace)
                                .onTapGesture {
                                    withAnimation(.easeInOut(duration: 2.0)) {
                                        selectedIndex = index
                                    }
                                }
                        }
                    }
                    .padding()
                }
            }
        }
    }
}

struct DetailView: View {
    var namespace: Namespace.ID
    let selectedIndex: Int
    let closeClosure: (() -> Void)
    
    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 20)
                .fill(Color.purple)
                .matchedGeometryEffect(id: "container\(selectedIndex)", in: namespace)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .onTapGesture {
                    withAnimation(.easeInOut(duration: 2.0)) {
                        closeClosure()
                    }
                }
        }
        .edgesIgnoringSafeArea(.all)
    }
}


#Preview {
    ContentView()
}

Here's the result I get :

As you can see, the source cell is moving in both the appearance and dismiss animation, and I would like to stay where it is. Is there a way to customize a bit more matchedGeometryEffect? Or should I use a low-level method?

I want to animate the transition from a LazyHStack cell to a detail view in my iOS app, as Instagram does when you click on a story.

The detail view should transition from the cell frame to a full-screen page. And vice versa when closing the detail view.

So I guess the animation should deal with the position and scale of the detail view, but I don't know how to specify a start and end. I know GeometryReader can help me get the cell size for the starting frame, but I'm unsure how to specify the start and end frame with SwiftUI.

EDIT:

Here's a sample code (based on Alexis answer and matchedGeometryEffect ), but it doesn't work exactly as I would like :

struct ContentView: View {
    @Namespace private var animationNamespace
    @State private var selectedIndex: Int? = nil
    
    var body: some View {
        ZStack {
            if let index = selectedIndex {
                DetailView(namespace: animationNamespace,
                           selectedIndex: index) {
                    selectedIndex = nil
                }
            } else {
                ScrollView(.horizontal) {
                    LazyHStack(spacing: 10) {
                        ForEach(0..<5) { index in
                            RoundedRectangle(cornerRadius: 20)
                                .fill(Color.blue)
                                .frame(width: 80, height: 80)
                                .matchedGeometryEffect(id: "container\(index)", in: animationNamespace)
                                .onTapGesture {
                                    withAnimation(.easeInOut(duration: 2.0)) {
                                        selectedIndex = index
                                    }
                                }
                        }
                    }
                    .padding()
                }
            }
        }
    }
}

struct DetailView: View {
    var namespace: Namespace.ID
    let selectedIndex: Int
    let closeClosure: (() -> Void)
    
    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 20)
                .fill(Color.purple)
                .matchedGeometryEffect(id: "container\(selectedIndex)", in: namespace)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .onTapGesture {
                    withAnimation(.easeInOut(duration: 2.0)) {
                        closeClosure()
                    }
                }
        }
        .edgesIgnoringSafeArea(.all)
    }
}


#Preview {
    ContentView()
}

Here's the result I get :

As you can see, the source cell is moving in both the appearance and dismiss animation, and I would like to stay where it is. Is there a way to customize a bit more matchedGeometryEffect? Or should I use a low-level method?

Share Improve this question edited Mar 17 at 17:15 Xys asked Mar 14 at 13:40 XysXys 11k5 gold badges52 silver badges61 bronze badges 2
  • It might be possible to use .navigationTransition + .matchedTransitionSource here. Please could you supplement your question with example code that shows the navigation happening? Also, please say which version of iOS you are targeting (or macOS, whichever is the case). – Benzy Neez Commented Mar 14 at 14:14
  • Edited my question with a sample code based on Alexis answer. It is for iOS only – Xys Commented Mar 17 at 17:16
Add a comment  | 

1 Answer 1

Reset to default 0

SwiftUI has a built-in mechanism for this called matchedGeometryEffect

struct ContentView: View {
    @Namespace private var animation
    @State private var isShowingDetail = false
    
    var body: some View {
        ZStack {
            if !isShowingDetail {
                // Your LazyHStack with cells
                ScrollView(.horizontal) {
                    LazyHStack(spacing: 10) {
                        ForEach(0..<5) { index in
                            RoundedRectangle(cornerRadius: 20)
                                .fill(Color.blue)
                                .frame(width: 80, height: 80)
                                .matchedGeometryEffect(id: "container\(index)", in: animation)
                                .onTapGesture {
                                    withAnimation(.spring()) {
                                        isShowingDetail = true
                                    }
                                }
                        }
                    }
                    .padding()
                }
            } else {
                // Detail view
                DetailView(namespace: animation, isShowing: $isShowingDetail)
            }
        }
    }
}

struct DetailView: View {
    var namespace: Namespace.ID
    @Binding var isShowing: Bool
    
    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 20)
                .fill(Color.purple)
                .matchedGeometryEffect(id: "container0", in: namespace)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .onTapGesture {
                    withAnimation(.spring()) {
                        isShowing = false
                    }
                }
        }
        .edgesIgnoringSafeArea(.all)
    }
}
发布评论

评论列表(0)

  1. 暂无评论