Reputation: 481
I'm implementing custom menu view with transition appearance from top trailing position. I'm using matchedGeometryEffect
on views background. The problem is with child views of second view. They appears before animation ends. You can see below:
How synchronize these button views with animation of parent view?
Below my code:
struct ContentView: View {
@State var show = false
@Namespace var namespace
var body: some View {
VStack(spacing: 0) {
HStack(spacing: 0) {
Spacer()
Button(action: {
withAnimation(.linear(duration: 1.2)) { // Longer duration for demo of issue
show.toggle()
}
}) {
Image(systemName: "ellipsis.circle")
.font(.title3)
.background( !show ?
Color.clear
.matchedGeometryEffect(id: "topMenuToggle", in: namespace, properties: .position)
: nil
)
}
}
.padding()
Color.clear
.overlay(
show ? MenuView(namespace: namespace) : nil
, alignment: .topTrailing
)
}
.foregroundColor(.white)
.background(Color.black.ignoresSafeArea())
}
}
struct MenuView: View {
let namespace: Namespace.ID
var body: some View {
VStack(spacing: 0) {
Button(action: {
}) {
HStack(spacing: 0) {
Text("Save video")
.font(.body)
Spacer(minLength: 0)
Image(systemName: "arrow.down.circle")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 20)
}
}
.padding(.top, 18)
.padding(.bottom, 18)
.padding(.horizontal, 14)
Rectangle()
.fill(Color.white.opacity(0.1))
.frame(maxWidth: .infinity, maxHeight: 1)
Button(action: {
}) {
HStack(spacing: 0) {
Text("Report")
.font(.body)
Spacer(minLength: 0)
Image(systemName: "exclamationmark.bubble")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 20)
}
}
.padding(.top, 16)
.padding(.bottom, 18)
.padding(.horizontal, 14)
}
.foregroundColor(.white)
.frame(width: 250)
.background(
Color.white.opacity(0.2)
.matchedGeometryEffect(id: "topMenuToggle", in: namespace, anchor: .bottomLeading)
)
.cornerRadius(12)
}
}
Upvotes: 0
Views: 1105
Reputation: 481
I was able to fix animation by adding MatchedGeometryEffect
for each row item and source view. Maybe it's not correct way but it works somehow.
Modified code:
Button(action: {
withAnimation(.easeInOut(duration: 1.2)) {
show.toggle()
}
}) {
Image(systemName: "ellipsis.circle")
.background( !show ?
Color.clear
.matchedGeometryEffect(id: "topMenuBg", in: namespace, properties: .position)
.matchedGeometryEffect(id: "topMenuRow0", in: namespace, properties: .position)
.matchedGeometryEffect(id: "topMenuRow1", in: namespace, properties: .position)
.matchedGeometryEffect(id: "topMenuRow2", in: namespace, properties: .position)
: nil
)
}
MenuView.swift
VStack(spacing: 0) {
Button(action: {}) {
HStack(spacing: 0) {}
}
.matchedGeometryEffect(id: "topMenuRow0", in: namespace)
Rectangle()
.fill(Color.white.opacity(0.1))//
.frame(maxWidth: .infinity, maxHeight: 1)
// Before frame() the height is broken weirdly
.matchedGeometryEffect(id: "topMenuRow1", in: namespace)
Button(action: {}) {
HStack(spacing: 0) {}
}
.matchedGeometryEffect(id: "topMenuRow2", in: namespace)
}
.frame(width: 250)
.background(
Color.white.opacity(0.2)
.matchedGeometryEffect(id: "topMenuBg", in: namespace, anchor: .bottomLeading)
)
Demo:
Upvotes: 1