Reputation: 53231
Animating View
s between a LazyVGrid
and an HStack
with another view in between them (in this case, a Button
), using matchedGeometryEffect
, works great:
Note how the animating views move above the Done button.
However, when the views are contained within a ScrollView
, the animating views now move behind the intermediate view:
I've tried setting the zIndex
of the ScrollView
s to > 0 (or more) but this doesn't seem to change anything.
Any thoughts on how to fix this?
Person
struct Person: Identifiable, Equatable {
var id: String { name }
let name: String
var image: Image { Image(name) }
static var all: [Person] {
["Joe", "Kamala", "Donald", "Mike"].map(Person.init)
}
}
ContentView
struct ContentView: View {
@State var people: [Person]
@State private var selectedPeople: [Person] = []
@Namespace var namespace
var body: some View {
VStack(alignment: .leading, spacing: 0) {
ScrollView(.horizontal) {
SelectedPeopleView(people: $selectedPeople, namespace: namespace) { person in
withAnimation(.easeOut(duration: 1)) {
selectPerson(person)
}
}
.background(Color.orange)
}
doneButton()
ScrollView(.vertical) {
PeopleView(people: people, namespace: namespace) { person in
withAnimation(.easeOut(duration: 1)) {
deselectPerson(person)
}
}
}
Spacer()
}
.padding()
}
func selectPerson(_ person: Person) {
_ = selectedPeople.firstIndex(of: person).map { selectedPeople.remove(at: $0)}
people.append(person)
}
func deselectPerson(_ person: Person) {
_ = people.firstIndex(of: person).map { people.remove(at: $0)}
selectedPeople.append(person)
}
func doneButton() -> some View {
Button("Done") {
}
.font(.title2)
.accentColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.gray)
}
}
SelectedPeopleView
struct SelectedPeopleView: View {
@Binding var people: [Person]
let namespace: Namespace.ID
let didSelect: (Person) -> Void
var body: some View {
HStack {
ForEach(people) { person in
Button(action: { didSelect(person) } ) {
Text(person.name)
.padding(10)
.background(Color.yellow.cornerRadius(6))
.foregroundColor(.black)
.matchedGeometryEffect(id: person.id, in: namespace)
}
}
}
.frame(height: 80)
}
}
PeopleView
struct PeopleView: View {
let people: [Person]
let namespace: Namespace.ID
let didSelect: (Person) -> Void
let columns: [GridItem] = Array(repeating: .init(.flexible(minimum: .leastNormalMagnitude, maximum: .greatestFiniteMagnitude)), count: 2)
var body: some View {
LazyVGrid(columns: columns) {
ForEach(people) { person in
Button(action: { didSelect(person) }) {
person.image
.resizable()
.scaledToFill()
.layoutPriority(-1)
.clipped()
.aspectRatio(1, contentMode: .fit)
.cornerRadius(6)
}
.zIndex(zIndex(for: person))
.matchedGeometryEffect(id: person.id, in: namespace)
}
}
}
func zIndex(for person: Person) -> Double {
Double(people.firstIndex(of: person)!)
}
}
Upvotes: 11
Views: 1361
Reputation: 258541
This looks like a bug in SwiftUI, because even if you put Color.clear
of any height in place of and instead of your doneButton
(or even .padding
of some height for bottom ScrollView
) the effect will be the same.
As it is seen from view hierarchy there is nothing in between two ScrollView
and rendering of images is performed in one single background view
Upvotes: 4