skip to Main Content

I am wondering how I can animate the content size of a ViewBuilder view. I have this:

struct CardView<Content>: View where Content: View {
    
    private let content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        VStack(spacing: 0) {
            content
                .padding(16)
        }
        .background(.white)
        .cornerRadius(14)
        .shadow(color: .black.opacity(0.07), radius: 12, x: 0, y: 2)
    }
}

I would like to animate any size changes to content, but I can’t find a nice way of doing this. I found two ways that work:

  • Using animation(.linear) in CardView works, but is deprecated and discouraged since I have no value to attach the animation to.
  • Using withAnimation inside content when changing the content works, too, but I would like to encapsulate this behaviour in CardView. CardView is heavily reused and doing it in content is easy to forget and also not where this behaviour belongs in my opinion.

I also tried using GeometryReader but could not find a good way of doing it.

2

Answers


  1. Here is an approach for you:

    You may take look at this link as well:

    How to replace deprecated .animation() in SwiftUI?


    struct ContentView: View {
    
        @State private var cardSize: CGSize = CGSize(width: 150, height: 200)
        
        var body: some View {
            
            VStack {
    
                CardView(content: {
                    
                    Color.red
                        .overlay(Image(systemName: "dollarsign.circle").resizable().scaledToFit().padding())
                        .onTapGesture {
                            cardSize = CGSize(width: cardSize.width + 50, height: cardSize.height + 50)
                        }
                    
                }, cardSize: cardSize)
      
            }
    
        }
    }
    
    struct CardView<Content>: View where Content: View {
        
        let content: Content
        let cardSize: CGSize
        
        init(@ViewBuilder content: () -> Content, cardSize: CGSize) {
            self.content = content()
            self.cardSize = cardSize
        }
    
        var body: some View {
            
            content
                .frame(width: cardSize.width, height: cardSize.height)
                .cornerRadius(14)
                .padding(16)
                .background(.white)
                .shadow(color: .black.opacity(0.07), radius: 12, x: 0, y: 2)
                .animation(.easeInOut, value: cardSize)
        }
    }
    
    Login or Signup to reply.
  2. You might find this useful.

    It uses a looping animation and a user gesture for add size and resting.

    struct PilotTestPage: View {
        
        @State private var cardSize = CGSize(width: 150, height: 200)
    
        var body: some View {
            return ZStack {
                Color.yellow
                            
                CardView() {
                    Color.clear
                        .overlay(
                            Image(systemName: "dollarsign.circle")
                                .resizable()
                                .scaledToFit()
                                .padding()
                        )
                }
                .frame(
                    width: cardSize.width
                    ,height: cardSize.height
                )
                .onTapGesture {
                    withAnimation {
                        cardSize = CGSize(
                            width: cardSize.width + 50
                            ,height: cardSize.height + 50
                        )
                    }
                }
                
                RoundedRectangle(cornerRadius: 12, style: .continuous)
                    .fill(.red)
                    .frame(
                        width: 200
                        ,height: 44
                    )
                    .offset(y: -300)
                    .onTapGesture {
                        withAnimation {
                            cardSize = CGSize(
                                width: 150
                                ,height: 200
                            )
                        }
                    }
    
            }
            .ignoresSafeArea()
        }
        
        struct CardView<Content>: View where Content: View {
            let content: Content
            
            init(
                @ViewBuilder content: () -> Content
            ) {
                self.content = content()
            }
            
            @State private var isAtStart = true
            
            var body: some View {
                ZStack {
                    content
                        .background(
                            RoundedRectangle(cornerRadius: 12)
                                .fill(.white)
                                .shadow(
                                    color: .black.opacity(0.25)
                                    ,radius: 12
                                    ,x: 0
                                    ,y: 2
                                )
                        )
                }
                .scaleEffect(isAtStart ? 0.9 : 1.0)
                .rotationEffect(.degrees(isAtStart ? -2 : 2))
                .onAppear {
                    withAnimation(
                        .easeInOut(duration: 1)
                        .repeatForever()
                    ) {
                        self.isAtStart.toggle()
                    }
                }
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search