|
Hi devs so i was studying custom transitions. After some hurdles I am able to make custom transition work. But now I have a question.
how does insertion and removal work really in .asymmetric function?
extension AnyTransition {
static func pivot(withDelay delay: Double = 0.0) -> AnyTransition {
let insertion =
AnyTransition.modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),
identity: CornerRotateModifier(amount: 0, anchor: .topLeading)
)
.animation( Animation.easeInOut(duration: 0.5)
.delay(delay)
)
let removal =
AnyTransition.modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),
identity: CornerRotateModifier(amount: 0, anchor: .topLeading)
)
.animation( Animation.easeInOut(duration: 0.5)
.delay(delay)
)
return .asymmetric(insertion: insertion, removal: removal)
}
}
according to this code initially what I understood was that
(in case of insertion) active is the state of UI from where we want to start
and identity is the state of UI to where we want to reach.
which seems understandable.
but then in case of removal this doesn't seem to match.
I would assume that values of insertion and removal would be totally opposite to make exit animation work. but it breaks things. but if , we add same values as insertion or identity is preserved, then it works fine.
This relation is something I am not able to wrap my head around.
so is this like this?
Link to Image
|
|
struct CornerRotateModifier: ViewModifier {
let amount: Double;
let anchor: UnitPoint;
func body(content: Content) -> some View {
content
.rotationEffect(.degrees(amount), anchor: anchor)
.clipped()
}
}
extension AnyTransition {
static func pivot() -> AnyTransition {
.asymmetric(
insertion: .modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),
identity: CornerRotateModifier(amount: 0, anchor: .topLeading)
),
removal: .modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),
identity: CornerRotateModifier(amount: 0, anchor: .topLeading)
)
)
}
}
struct ContentView: View {
@State private var isShowingRed = false
@State private var delay: Double = 1
var body: some View {
ZStack {
Rectangle()
.fill(.blue)
.frame(width: 200, height: 200)
if isShowingRed {
Rectangle()
.fill(.red)
.frame(width: 200, height: 200)
.transition(.pivot())
.zIndex(1) // You need zIndex to help keep the view on top during removal
}
}
.animation(Animation.easeInOut(duration: 0.5).delay(delay), value: isShowingRed)
.onTapGesture {
isShowingRed.toggle()
}
}
}
or ContentView like so:
struct ContentView: View {
@State private var isShowingRed = false
var body: some View {
ZStack {
Rectangle()
.fill(.blue)
.frame(width: 200, height: 200)
if isShowingRed {
Rectangle()
.fill(.red)
.frame(width: 200, height: 200)
.transition(.pivot())
.zIndex(1) // You need zIndex to help keep the view on top during removal
}
}
.onTapGesture {
withAnimation(Animation.easeInOut(duration: 0.5).delay(1)) {
isShowingRed.toggle()
}
}
}
}
below will work without zIndex, make sure to run on simulator
extension AnyTransition {
static func pivot(withDelay delay: Double = 0.0) -> AnyTransition {
.asymmetric(
insertion: .modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),
identity: CornerRotateModifier(amount: 0, anchor: .topLeading)
)
.animation(Animation.easeInOut(duration: 0.5).delay(delay))
,
removal: .modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),
identity: CornerRotateModifier(amount: 0, anchor: .topLeading)
)
.animation(Animation.easeInOut(duration: 0.5).delay(delay))
)
}
}
struct ContentView: View {
@State private var isShowingRed = false
var body: some View {
ZStack {
Rectangle()
.fill(.blue)
.frame(width: 200, height: 200)
.overlay {
if isShowingRed {
Rectangle()
.fill(.red)
.frame(width: 200, height: 200)
.transition(.pivot(withDelay: 1))
}
}
}
.onTapGesture {
isShowingRed.toggle()
}
}
}
|
|
Hi @ygeras
Thanks for such detailed answer, but it looks like this answer is for another question I posted yesterday. Which still answer's that and I got to learn more about it.
but can you please help me understand this code.
.asymmetric(
insertion: .modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),
identity: CornerRotateModifier(amount: 0, anchor: .topLeading)
)
.animation(Animation.easeInOut(duration: 0.5).delay(delay))
,
removal: .modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),
identity: CornerRotateModifier(amount: 0, anchor: .topLeading)
)
.animation(Animation.easeInOut(duration: 0.5).delay(delay))
)
how does insertion and removal behave really? I would think identity of insertion and removal woud be different.
|
|
I would advise to add border to frames or comment out clipped shapes to see the whole picture what is going on on the screen. Hope this sample will shed some light on how it moves around so that you have better understanding. Compare it with built-in transition as well.
RUN ON SIMULATOR.
struct CornerRotateModifier: ViewModifier {
let amount: Double;
let anchor: UnitPoint;
func body(content: Content) -> some View {
content
.rotationEffect(.degrees(amount), anchor: anchor)
// .clipped()
}
}
extension AnyTransition {
static func pivot(withDelay delay: Double = 0.0) -> AnyTransition {
.asymmetric(
insertion: .modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading), // Appears on screen
identity: CornerRotateModifier(amount: 0, anchor: .topLeading) // Moves to its identity location
)
.animation(Animation.easeInOut(duration: 3).delay(delay))
,
removal: .modifier(
active: CornerRotateModifier(amount: -90, anchor: .bottomTrailing),// Appears on screen - already here
identity: CornerRotateModifier(amount: 0, anchor: .bottomTrailing) // Moves to its identity location
)
.animation(Animation.easeInOut(duration: 3).delay(delay))
)
}
}
struct ContentView: View {
@State private var isShowingRed = false
@State private var isShowingPurple = false
var body: some View {
VStack {
Text("Pivot Transition")
ZStack {
Rectangle()
.fill(.blue)
.frame(width: 200, height: 200)
.overlay {
if isShowingRed {
Rectangle()
.fill(.red)
.frame(width: 200, height: 200)
.transition(.pivot(withDelay: 1))
}
}
}
.onTapGesture {
isShowingRed.toggle()
}
Text("Assymetric Transition")
ZStack {
Rectangle()
.fill(.blue)
.frame(width: 200, height: 200)
.overlay {
if isShowingPurple {
Rectangle()
.fill(.purple)
.frame(width: 200, height: 200)
.transition(.asymmetric(insertion: .scale, removal: .slide))
// if you place .scale to removal
// it will "reverse" upon disappear
}
}
}
.onTapGesture {
withAnimation(.easeInOut(duration: 3).delay(0.5)) {
isShowingPurple.toggle()
}
}
}
}
}
|
|
@ygeras I ran the code and was playing around with it to understand how insertion and removal works.
extension AnyTransition {
static func pivot(withDelay delay: Double = 0.0) -> AnyTransition {
.asymmetric(
insertion: .modifier(
active: CornerRotateModifier(amount: 90, anchor: .topLeading), // Appears on screen
identity: CornerRotateModifier(amount: 0, anchor: .topLeading) // Moves to its identity location
)
.animation(Animation.easeInOut(duration: 3).delay(delay))
,
removal:
.modifier(
active: CornerRotateModifier(amount: -90, anchor: .topLeading),// Appears on screen - already here
identity: CornerRotateModifier(amount: 45, anchor: .topLeading) // Moves to its identity location
)
.animation(Animation.easeInOut(duration: 1).delay(delay))
)
}
}
I changed this code to have different identity for insertion and removal.
what i am seeing is the both identity are kind of combined ?!
it starts off at 135 degrees and stops at 45 degrees (I had to measure that using protractor 😅).
Am I wrong?
edit: and yes, on removal red box goes to -90
|
|
Seems like so, I cannot tell with all those rotation effects, did not dig so deep in documentation for that. Usually, I try to use different scenarios to get the effect I need. That's the beauty of high level code ))) we usually do not dig into e.g. sort option in swift, we can understand the general logic and efficiency of the code and in most cases that suffice.
|