Reputation: 20158
I am trying to replicate what I used to do in UIKit when presenting a ViewController using UIViewControllerTransitioningDelegate
and UIViewControllerAnimatedTransitioning
.
So, for example, from a view that looks like this:
I want to present a view (I would say a modal view but I am not sure if that is the correct way to go about it in SwiftUI) that grows from the view A into this:
So, I need view B to fade in growing from a frame matching view A into almost full screen. The idea is the user taps on A as if it wanted to expand it into its details (view B).
I looked into SwiftUI transitions, things like this:
extension AnyTransition {
static var moveAndFade: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
.combined(with: .opacity)
let removal = AnyTransition.scale
.combined(with: .opacity)
return .asymmetric(insertion: insertion, removal: removal)
}
}
So, I think I need to build a custom transition. But, I am not sure how to go about it yet being new to this.
How would I build a transition to handle the case as described? Being able to have a from frame
and a to frame
...?
Is this the right way of thinking about it in SwiftUI?
New information:
I have tested matchedGeometryEffect
.
Example:
struct TestParentView: View {
@State private var expand = false
@Namespace private var shapeTransition
var body: some View {
VStack {
if expand {
// Rounded Rectangle
Spacer()
RoundedRectangle(cornerRadius: 50.0)
.matchedGeometryEffect(id: "circle", in: shapeTransition)
.frame(minWidth: 0, maxWidth: .infinity, maxHeight: 300)
.padding()
.foregroundColor(Color(.systemGreen))
.animation(.easeIn)
.onTapGesture {
expand.toggle()
}
} else {
// Circle
RoundedRectangle(cornerRadius: 50.0)
.matchedGeometryEffect(id: "circle", in: shapeTransition)
.frame(width: 100, height: 100)
.foregroundColor(Color(.systemOrange))
.animation(.easeIn)
.onTapGesture {
expand.toggle()
}
Spacer()
}
}
}
}
It looks like matchedGeometryEffect
could be the tool for the job.
However, even when using matchedGeometryEffect
, I still can't solve these two things:
matchedGeometryEffect
, when I "close" view B, view B disappears immediately and what we see animating is view A from where B was back to view A's original frame. I actually want view B to scale down to where A is as it fades out.Upvotes: 2
Views: 2176
Reputation: 162
You would have to use the .matchedGeometryEffect
modifier on the two Views that you would like to transition.
Here is an example:
struct MatchedGeometryEffect: View {
@Namespace var nspace
@State private var toggle: Bool = false
var body: some View {
HStack {
if toggle {
VStack {
Rectangle()
.foregroundColor(Color.green)
.matchedGeometryEffect(id: "animation", in: nspace)
.frame(width: 300, height: 300)
Spacer()
}
}
if !toggle {
VStack {
Spacer()
Rectangle()
.foregroundColor(Color.blue)
.matchedGeometryEffect(id: "animation", in: nspace)
.frame(width: 50, height: 50)
}
}
}
.padding()
.overlay(
Button("Switch") { withAnimation(.easeIn(duration: 2)) { toggle.toggle() } }
)
}
}
Image should be a GIF
The main two parts of using this modifier are the id
and the namespace
.
The id
of the two Views you are trying to match have to be the same. They then also have to be in the same namespace. The namespace is declared at the top using the @Namespace
property wrapper. In my example I used "animation", but it can really be anything, preferably something that can uniquely identify the Views from other types of animations.
Another important piece of information is that the '''@State''' variable controlling the showing/hiding of Views is animated. This is done through the use of withAnimation { toggle.toggle() }
.
I'm also quite new to this, so for some more information you can read this article I found from the Swift-UI Lab:
https://swiftui-lab.com/matchedgeometryeffect-part1/
Upvotes: 1