View
가 이미 UI
에 들어가 있는 상태에서 ViewModifier 의 인자
가 바뀌었을 때Shape
이 바뀌었을 때UI
내부의 View
가 생기거나 사라질 때UI
상의 View container
에 추가되는 경우UI
상의 View container
에서 삭제되는 경우.animation(Animation)
View Modifier
를 이용한 implicit
한 방식duration
, delay
, repear
, curve
등을 직접 지정할 수 있음.animation
앞의 모든 ViewModifier
들에 대해 애니메이션이 적용된다container
에 적용 시 내부의 모든 View
에 분배되므로, 가장 최하단의 자식 View
혹은 독립적으로 작동하는 View
에 주로 적용Text("👻")
.opacity(scary? 1 : 0) // (O) animated
.animation(Animation.easeInOut(duration: 1) // easeInOut -> curve!
.rotationEffect(Angle.degrees(upsideDown ? 180 : 0)) // (X) animated
withAnimation(Animation) { }
함수를 이용한 explicit
한 방식explicit
애니메이션은 implicit
애니메이션을 오버라이딩 하지 않음!withAnimation(.linear(duration: 2)) {
// do something that will cause ViewModifier/Shape arguments to change somewhere
}
Container
에 View
를 새로 추가/삭제하는 transition
Viewmodifier
가 변화하는 것 modifier
과 변화 이후 modifier
한 쌍의 인자가 변화하는 것을 나타낸 게 애니메이션!ViewBuilder
내부의 ForEach
혹은 if-else
문을 통해 가능implicit
방식과 달리 container
에 적용 시 분산되지 않고 해당 container
자체에 적용됨 shape
혹은 ViewModifier
가 시스템에게 자신 내부의 어떤 컨텐츠가 애니메이션화되어야 하는 지 알려준다shape
혹은 ViewModifier
를 다시 호출해 단계에 따라 적절한 명령을 내린다. shape/ViewModifier
는 AnimatableModifier
의 animatableData
변수를 통해 animation 시스템
과 상호작용!animatableData
는 읽기와 쓰기가 모두 가능get
: 애니메이션 시스템이 애니메이션의 시작점/끝점을 호출set
: shape/ViewModifier
에게 애니메이션 시스템이 그려야할 조각을 알려주는 것content
를 받아서 카드화한 View
를 반환하는 커스텀 ViewModifier
만들기 var body: some View {
GeometryReader { geometry in
ZStack {
Pie(startAngle: Angle(degrees: 0 - 90), endAngle: Angle(degrees: 110 - 90))
.padding(DrawingConstants.circlePadding)
.opacity(0.4)
Text(card.content)
.font(Font.system(size: 32))
} // 카드화 할 content 를 담고 있는 View
.cardify(isFaceUp: card.isFaceUp)
}
}
struct Cardify: ViewModifier {
var isFaceUp: Bool
func body(content: Content) -> some View {
ZStack {
let shape = RoundedRectangle(cornerRadius: DrawingConstants.cornerRadius)
if isFaceUp {
shape.fill().foregroundColor(.white)
shape.strokeBorder(lineWidth: DrawingConstants.lineWidth)
content
} else {
shape.fill()
}
}
}
private struct DrawingConstants {
static let cornerRadius: CGFloat = 10
static let lineWidth: CGFloat = 2.5
}
}
// 호출을 쉽게 하기 위한 extension! syntax sugar:)
extension View {
func cardify(isFaceUp: Bool) -> some View {
self.modifier(Cardify(isFaceUp: isFaceUp))
}
}
content
가 카드를 뒤집음과 동시에 UI 상에 처음 나타나게 되므로 이미 isMatched
인 상태에서 처음 등장해 애니메이션 효과를 줄 변화가 없다...! 따라서 골든룰에 위배되어 애니메이션 효과가 발동하지 않는다!content
는 항상 존재하게 하되 opacity
를 조절하는 방식으로 바꾸어주었더니 해결되었다! struct Cardify: ViewModifier {
var isFaceUp: Bool
func body(content: Content) -> some View {
ZStack {
...
if isFaceUp {
shape.fill().foregroundColor(.white)
shape.strokeBorder(lineWidth: DrawingConstants.lineWidth)
} else {
shape.fill()
}
content.opacity(isFaceUp ? 1 : 0) // 수정 부분!
}
}
...
}
A change to a ViewModifier's arguments ahs to happen after the View is initially put in the UI
== only changes in a ViewModifier's arguments since it jointed the UI are animates
A View coming on-screen is only animated if it's joining a container that is already in the UI
A View going off-screen is only animated if it's leavinf a container that is staying in the UI
ForEach and if-else in ViewBuilders are common ways to make Views come and go
Text("👻")
.opacity(scary? 1 : 0) // (O) animated
.animation(Animation.easeInOut)
.rotationEffect(Angle.degrees(upsideDown ? 180 : 0)) // (X) animated
withAnimation(.linear(duration: 2)) {
// do something that will cause ViewModifier/Shape arguments to change somewhere
}
transform specify how to animate the arrival/departure of Views
Only works for Views that are inside CTAAOS(Containers That ARe Already On-Screen
Under the covers, a transition is nothing more than a pair of ViewModifiers. One of the modifiers is the "before" modification of teh View that's on the move. The other modifier is the "after" modification od the View that's on the move. Thus, a transition is just a version of a "changes in arguments to ViewModifiers" animation.
An asymmetric transition has 2 pairs of ViewModifiers
because it's communicationg both ways, this animatableData is a read-write var.
the setting of this var is the animation system telling the Shape/VM which 'piece' to draw : draw this draw that..
the getting of this var is the animation system getting the start/end points of an animation : when u set an argument it's gonna change that animatable data probably...
it's usally computed var..!