Aswath
Aswath

Reputation: 1490

Is there any way to tell which state change caused the view to be rebuilt in SwiftUI?

I have a view that needs to work like an accordion where a user can drag to open/ collapse it. And I'd like it to not have any animation when the user is dragging, but animate while he leaves it. So, I thought of having two state variables(say, height and tempHeight) in my view, but I'd animate only when the height variable triggers the view rebuilding process.

Animating on both the height and tempHeight changes looks weird when dragging and feels like that there is a lag.

Sample code describing my problem:

struct ContentView: View {
    @State var tempHeight: CGFloat = 200
    @State var height: CGFloat = 200
    var dragGesture: some Gesture {
        
        DragGesture(),
            .onChanged { value in
                
                tempHeight = min(400, max(200, height + value.translation.height))
            }
            .onEnded { value in
                
                height = tempHeight > 300 ? 400 : 200
                tempHeight = height
            }
    }
    
    var body: some View {
        VStack {
            Spacer()
                .frame(height: 100)
            Color.blue.frame(height: tempHeight)
                .animation(.easeIn)
                .gesture(dragGesture)
            Spacer()
        }
    }
}

How I tried to work around this was to have a static var which keeps track of which state change happened last and it seems to work. My current workaround:

enum StateChange {
    case height
    case tempHeight
    
    static var lastChangeBy: StateChange = .height
}

struct ContentView: View {
    @State var tempHeight: CGFloat = 200
    @State var height: CGFloat = 200
    
    var dragGesture: some Gesture {
        
        DragGesture()
            .onChanged { value in
                
                tempHeight = min(400, max(200, height + value.translation.height))
                StateChange.lastChangeBy = .tempHeight
            }
            .onEnded { value in
                
                height = tempHeight > 300 ? 400 : 200
                tempHeight = height
                StateChange.lastChangeBy = .height
                
            }
    }
    
    var body: some View {
        VStack {
            Spacer()
                .frame(height: 100)
            Color.blue.frame(height: tempHeight)
                .gesture(dragGesture)
                .animation(StateChange.lastChangeBy == .height ? .easeInOut : .none)
                .animation(.easeInOut)
            Spacer()
        }
    }
}

It feels like a hack to me. If I have many @State wrapped variables, it just becomes a mess quickly. Is it possible to know which state change triggers building the view inside the body, so that I can conditionally apply the animation to a particular trigger or a better way to write this.

Upvotes: 3

Views: 247

Answers (1)

Asperi
Asperi

Reputation: 257789

You can specify explicitly which value changes activate animation in modifier, like

  Color.blue.frame(height: tempHeight)
        .gesture(dragGesture)
        .animation(.easeInOut, value: height)   // << here

Upvotes: 0

Related Questions