Skip to content

Instantly share code, notes, and snippets.

@Gernot
Created January 5, 2022 18:51
Show Gist options
  • Save Gernot/72a37e52d24d2b5d0c051cc1767bc63a to your computer and use it in GitHub Desktop.
Save Gernot/72a37e52d24d2b5d0c051cc1767bc63a to your computer and use it in GitHub Desktop.
List like transitions for VStacks?
import Foundation
import SwiftUI
import PlaygroundSupport
let allTexts = ["one", "two", "three", "four", "five"]
let selectedTexts = ["two", "four"]
struct TestView: View {
@State private var expanded = false
var body: some View {
VStack { // <- Replace with "List" to see what I want
ForEach(expanded ? allTexts : selectedTexts, id:\.self) { text in
Text(text)
}
}
.animation(.default, value: expanded)
.onTapGesture { expanded.toggle() }
.frame(width: 300, height: 600)
}
}
PlaygroundPage.current.setLiveView(TestView())
@Gernot
Copy link
Author

Gernot commented Jan 6, 2022

I think I got a solution that (sort of) does what I want without rewriting VStack. The idea is to not have SwiftUI show/hide elements based on ID, but to always show them and alter their size/opacity. That way, the origin position is not static in the animation but close to the nearest visible element.

import Foundation
import SwiftUI
import PlaygroundSupport

let allTexts = ["zero", "one", "two", "three", "four", "five", "six"]
let selectedTexts = ["two", "four"]

struct TestView: View {
    
    @State private var expanded = false
    
    var body: some View {
        VStack(spacing: 0) {
            ForEach(allTexts, id:\.self) { text in
                let visible = selectedTexts.contains(text)
                Text(text)
                    .frame(height: expanded||visible ? nil : 0)
                    .opacity(expanded||visible ? 1 : 0)
                    .transition(.opacity)
            }
        }
        .listStyle(.plain)
        .onTapGesture { expanded.toggle() }
        .border(.red)
        .clipped() // <- Using `clipped` shows what the issue is. It is irritating that "One" and "Five" don't move.
        .animation(.spring(), value: expanded)
        .frame(width: 300, height: 600)
    }
}

PlaygroundPage.current.setLiveView(TestView())

@uberbruns
Copy link

I wanted to propose that but thought that if allTexts and selectedText change with a common addition the common addition would animate in in the undesired way. But if that won't happen, then I think that solution looks really nice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment