Reputation: 18204
Edit: With help of Asperi I decided to rewrite the description to better clarify the question with easy copy+paste code.
Expected behavior on all tests: The red rectangle will animate it's size from zero to the size of the parent view when the Present
button in the top right corner is tapped. When Present
is tapped again, the red rectangle will shrink from the size of the parent view to zero.
TEST #1 PROPERTY STATE CHANGE
Actual behavior:
Works as expected.
Code:
struct ContentView: View {
@State private var presentRedBox = false
var body: some View {
NavigationView {
GeometryReader { proxy in
ZStack {
// ------
Rectangle().fill(Color.red)
.frame(
width: self.presentRedBox ? proxy.size.width : 0.0,
height: self.presentRedBox ? proxy.size.height : 0.0
)
// ------
}
}.animation(.default)
.navigationBarItems(trailing: Button("Present") { self.presentRedBox.toggle() })
.navigationBarTitle(Text(""), displayMode: .inline)
}
}
}
TEST #2 ANIMATABLE/VIEW MODIFIER USING PROPERTY STATE CHANGE
Actual behavior:
Works as expected.
Code:
extension AnyTransition {
static func sizeTransition(from: CGSize, to: CGSize) -> AnyTransition {
.modifier(
active: SizeTransition(size: from),
identity: SizeTransition(size: to)
)
}
}
struct SizeTransition: AnimatableModifier {
var size: CGSize
var animatableData: AnimatablePair<cgfloat, cgfloat=""> {
get { AnimatablePair(size.width, size.height) }
set {
size.width = newValue.first
size.height = newValue.second
}
}
func body(content: Content) -> some View {
print(size)
return content.frame(
width: size.width,
height: size.height
)
}
}
struct ContentView: View {
@State private var presentRedBox = false
var body: some View {
NavigationView {
GeometryReader { proxy in
ZStack {
// ------
Rectangle().fill(Color.red)
.modifier(
SizeTransition(
size: self.presentRedBox ? proxy.size : .zero
)
)
// ------
}
}.animation(.default)
.navigationBarItems(trailing: Button("Present") { self.presentRedBox.toggle() })
.navigationBarTitle(Text(""), displayMode: .inline)
}
}
}
TEST #3 ANIMATABLE/VIEW MODIFIER WITH TRANSITION
Actual behavior:
The red rectanble will animate in as expected. However(!) it will NOT animate out but disappear immediately, although the log shows the correct values.
Log Animating In
(0.0, 0.0)
(1.8118343353271484, 3.3873424530029297)
(7.392631530761719, 13.821006774902344)
(16.9350643157959, 31.66120719909668)
(30.5800838470459, 57.17146110534668)
(48.38059616088867, 90.45067977905273)
(70.25803184509277, 131.35197257995605)
(95.95654678344727, 179.39702224731445)
(124.99998664855957, 233.6956272125244)
(156.67254066467285, 292.90953254699707)
(190.03098106384277, 355.27531242370605)
(223.97296714782715, 418.73206901550293)
(257.33140754699707, 481.0978488922119)
(289.00356674194336, 540.3110160827637)
(318.04700660705566, 594.6096210479736)
(343.7447319030762, 642.6531944274902)
(365.6217727661133, 683.5537490844727)
(383.42189025878906, 716.8322296142578)
(397.06651496887207, 742.3417453765869)
(406.60855293273926, 760.1812076568604)
(412.18856048583984, 770.613395690918)
(414.0, 774.0)
Log Animating Out
(413.61268043518066, 773.2758808135986)
(410.07547760009766, 766.6628494262695)
(402.6749496459961, 752.8270797729492)
(391.2381649017334, 731.4452648162842)
(375.6612854003906, 702.3232727050781)
(355.94628524780273, 665.4647941589355)
(332.24832916259766, 621.1599197387695)
(304.9215717315674, 570.070764541626)
(274.5523223876953, 513.2934722900391)
(241.9665470123291, 452.3722400665283)
(208.19354438781738, 389.231409072876)
(174.37908554077148, 326.0130729675293)
(141.67486381530762, 264.870397567749)
(111.12004852294922, 207.74617767333984)
(83.55758285522461, 156.21635055541992)
(59.59075355529785, 111.40880012512207)
(39.58871841430664, 74.01369094848633)
(23.71967124938965, 44.34547233581543)
(11.994667053222656, 22.42481231689453)
(4.315790176391602, 8.06865119934082)
(0.5136623382568359, 0.9603252410888672)
(0.0, 0.0)
Code:
extension AnyTransition {
static func sizeTransition(from: CGSize, to: CGSize) -> AnyTransition {
.modifier(
active: SizeTransition(size: from),
identity: SizeTransition(size: to)
)
}
}
struct SizeTransition: AnimatableModifier {
var size: CGSize
var animatableData: AnimatablePair<cgfloat, cgfloat=""> {
get { AnimatablePair(size.width, size.height) }
set {
size.width = newValue.first
size.height = newValue.second
}
}
func body(content: Content) -> some View {
print(size)
return content.frame(
width: size.width,
height: size.height
)
}
}
struct ContentView: View {
@State private var presentRedBox = false
var body: some View {
NavigationView {
GeometryReader { proxy in
ZStack {
// ------
if self.presentRedBox {
Rectangle().fill(Color.red)
.transition(
.modifier(
active: SizeTransition(size: .zero),
identity: SizeTransition(size: proxy.size)
)
)
}
// ------
}
}.animation(.default)
.navigationBarItems(trailing: Button("Present") { self.presentRedBox.toggle() })
.navigationBarTitle(Text(""), displayMode: .inline)
}
}
}
TEST #4 ANIMATABLE/VIEW MODIFIER WITH TRANSITION FOR OPACITY
Expected behavior:
The red rectangle will animate it's opacity from zero (hidden) to one (visible) when the Present
button in the top right corner is tapped. When Present
is tapped again, the red rectangle will hide from one (visible) to zero (hidden).
Actual behavior:
Works as expected.
Code:
extension AnyTransition {
static func sizeTransition(from: CGSize, to: CGSize) -> AnyTransition {
.modifier(
active: SizeTransition(size: from),
identity: SizeTransition(size: to)
)
}
}
struct SizeTransition: AnimatableModifier {
var size: CGSize
var animatableData: AnimatablePair<CGFloat, CGFloat> {
get { AnimatablePair(size.width, size.height) }
set {
size.width = newValue.first
size.height = newValue.second
}
}
func body(content: Content) -> some View {
print(size)
return content.opacity(Double(size.width))
}
}
struct ContentView: View {
@State private var presentRedBox = false
var body: some View {
NavigationView {
GeometryReader { proxy in
ZStack {
// ------
if self.presentRedBox {
Rectangle().fill(Color.red)
.transition(
.modifier(
active: SizeTransition(size: .zero),
identity: SizeTransition(size: CGSize(width: 1.0, height: 1.0))
)
)
}
// ------
}
}.animation(.default)
.navigationBarItems(trailing: Button("Present") { self.presentRedBox.toggle() })
.navigationBarTitle(Text(""), displayMode: .inline)
}
}
}
Upvotes: 1
Views: 1928
Reputation: 257603
It is not exactly a solution for what you requested, I know, but under some circumstances can be useful, so I decided to post it.
Note: I started with your previous post code, so might be a bit not aligned with this post.
At first proposed alternate - only animation based.
struct TestReverseTransitions: View {
@State private var showRedBox = false
var body: some View {
VStack {
Button("Tap") { self.showRedBox.toggle() }
RedBox()
.modifier(SizeAnimation(size: showRedBox ?
CGSize(width: 200.0, height: 200.0) : .zero))
}.animation(.default)
}
}
The animatable modifier is used the same from investigation provided below.
Transitions investigation:
Actually I think this might be a bug, but can be a transition engine limitation, because transitions based on effects, but here is just change of physical view frame, while view is in fact already removed. So 50/50... maybe worth reporting feedback to Apple.
Here is why...
I use animatable modifier to make frame change explicitly via animatable data and as it seen on Demo2 debug log, frame is really animatable on box removed, but appearance is not, however button is moved as it should. Bug? Maybe.
Code for this case:
extension AnyTransition {
static func size(from: CGSize, to: CGSize) -> AnyTransition {
AnyTransition.modifier(
active: SizeAnimation(size: from),
identity: SizeAnimation(size: to)
)
}
}
struct SizeAnimation: AnimatableModifier {
var size: CGSize
var animatableData: AnimatablePair<CGFloat, CGFloat> {
get { AnimatablePair(size.width, size.height) }
set {
size.width = newValue.first
size.height = newValue.second
}
}
func body(content: Content) -> some View {
// print(size) // << uncomment for log sizes !!!
return content.frame(width: size.width, height: size.height)
}
}
struct TestReverseTransitions: View {
@State private var showRedBox = false
var body: some View {
VStack {
Button("Tap") { self.showRedBox.toggle() }
if self.showRedBox {
RedBox()
.transition(
.size(from: CGSize.zero, to: CGSize(width: 200.0, height: 200.0))
)
}
}.animation(.default)
}
}
struct RedBox: View {
var body: some View {
Rectangle().fill(Color.red)
}
}
Upvotes: 1