Created
January 5, 2022 18:51
-
-
Save Gernot/72a37e52d24d2b5d0c051cc1767bc63a to your computer and use it in GitHub Desktop.
List like transitions for VStacks?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
I think I know what you mean … sadly this approach only works for appearance and not not the disappearance.
import Foundation
import SwiftUI
import PlaygroundSupport
let allTexts = ["one", "two", "three", "four", "five"]
let selectedTexts = ["two", "four"]
struct CollapseModifier: ViewModifier {
let isCollapsed: Bool
func body(content: Content) -> some View {
content
.clipped()
.opacity(isCollapsed ? 0 : 1)
.frame(height: isCollapsed ? 0 : nil)
}
}
struct TestView: View {
@State private var expanded = false
var body: some View {
VStack(spacing: 0) {
ForEach(expanded ? allTexts : selectedTexts, id:\.self) { text in
Text(text).transition(
.modifier(
active: CollapseModifier(isCollapsed: true),
identity: CollapseModifier(isCollapsed: false)
)
)
}
}
.onTapGesture { expanded.toggle() }
.border(.red)
.clipped() // <- Using `clipped` shows what the issue is. It is irritating that "One" and "Five" don't move.
.animation(.easeOut(duration: 2), value: expanded)
.frame(width: 300, height: 600)
}
}
PlaygroundPage.current.setLiveView(TestView())
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())
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
Here is a slightly different version that shows what the issue is: In a clipped view, it is irritating that the views on the edges don't move.