iyOmSd/Title: Swift

[Swift] UIViewPropertyAnimator

냄수 2021. 10. 26. 17:37
반응형

오랜만에 글을 쓰게되네요 ㅎ..

 

 

UIViewPropertyAnimator가 뭔가아...?!

는 wwdc를 보면서 대충 이렇게 하는구나 알겠는데 직접사용하려다보니

헷갈려서 정리해보려고해요

 

우선 UIView.animate를 많이사용했었는데 

차이점을 보고가야겠죠

가장큰차이로는

버전이 제일크죠

최신API! 라는점

그리고 애플에서도 UIViewPropertyAnimator사용을 권장하구요

 

A UIViewPropertyAnimator object lets you animate changes to views and dynamically modify your animations before they finish. With a property animator, you can run your animations from start to finish normally or you can turn them into interactive animations and control the timing yourself. The animator operates on animatable properties of views, such as the frame, center, alpha, and transform properties, creating the needed animations from the blocks you provide.

애니메이션이 완료되기전에 동적으로 수정할 수 있다

처음부터 끝까지 동작시키던가 도중에 인터렉션애니메이션으로 전환해서 타이밍을 조절할 수 있다

프레임, 중심, 알파 및 변환 특성과 같은 뷰의 애니메이터 속성에 대해 작동한다

 

애니메이션 객체를 만들고 startAnimation()을 통해서 애니메이션을 시작할 수도 있고

runningPropertyAnimator 를 이용해서 즉시애니메이션이 실행 될 수 있도록 할 수도 있다

 

애니메이션 일시정지, 다시시작, 정지, 시작 이되고

애니메이션 정도를 fractionComplete 를 이용해서 조절할 수 있고

isReversed 사용해서 반대로 재생도 되는 기능도 있어요

이렇듯 UIView.animate와는 다르게 애니메이션을 컨트롤하는데 많은 기능들이 담긴 클래스에요 

 

func uiViewAnimate() {
    UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseInOut]) {
        self.sampleView1.frame = .init(x: 100, y: 0, width: 150, height: 200)
    }
}

func uiViewPropertyAnimator() {
    // 방법1: 애니메이션 즉시실행
    UIViewPropertyAnimator.runningPropertyAnimator(withDuration: duration, delay: 0, options: [.curveEaseInOut]) {
        self.sampleView2.frame = .init(x: 100, y: 300, width: 150, height: 200)
    }
    
    
    // 방법2: startAnimation() 호출
    let animation = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
        self.sampleView2.frame = .init(x: 100, y: 300, width: 150, height: 200)
    }
    animation.startAnimation()
}
// 방법1 혹은 방법2 사용하시면 됩니다 이코드에선 같은 동작입니다

UIView.animate(위)와 UIViewPropertyAnimator(아래)를 이용해서 같이 구현해봤어요

똑같이 움직이죠? 다를게없어요 ㅎㅎ

속성이나 부가적인 기능이 많은거죠!

 

UIViewPropertyAnimator에 대해 더 알아봅시다~

 

Animation State는 아래와같아요

start, pause를하면 active상태가 되요

inactive상태가되면

애니메이션 객체가 해제될수도 있죠

 

 

addAnimations

위의함수를 통해서

정의해준 애니메이션에 추가적으로 애니메이션을 혼합해서 사용할 수 있어요

let animation = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
    self.sampleView2.frame = .init(x: 100, y: 300, width: 150, height: 200)
}
animation.addAnimations {
    self.sampleView2.frame = .init(x: 100, y: 500, width: 150, height: 200)
}
animation.startAnimation()

가로이동과 세로이동이 혼합돼서

대각선으로 이동하네요

 

 

addAnimations(delayFactor: )

animation.addAnimations({
    self.sampleView2.backgroundColor = .blue
}, delayFactor: 0.5)

처음부터 색이 변경되지않고 중간 부터 변경되는걸 볼 수 있어요

 

delayFactor은 0.0~1.0사이의 값을 넣을 수 있고

총 애니메이션 시간에 비례한 값이에요

예를들어

애니메이션이 5초동안 동작한다고했을때 delayFactor에 0.5를 넣어주면

2.5초부터 실행되는 애니메이션임을 뜻해요

 

 

애니메이션을 사용하면서 주의해야할 점으로

함수안에 

func someFunc() {
    let animation = UIViewPropertyAnimator(...)
    animation.startAnimation()
}

이런식으로 클래스를 생성하고 실행하면

애니메이션 동작이 완료되면

자동으로 해제가되기때문에

에러가 날 수도 있어요

 

1회용이 아니라 재사용하거나 타이밍을 조정하고싶다면!

함수밖에 정의하고 사용하는걸 권장드려요!

class SomeVC {
    var animation: UIViewPropertyAnimator?
    
    func animation() {
    	animation = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
            self.sampleView2.frame = .init(x: 100, y: 300, width: 150, height: 200)
        }
    }
}

위처럼 말이죠

 

 

pauseAnimation

이함수를 호출하면 inactive에 있던 애니메이션이 active상태가 되구요

애니메이션을 일시정지하는 코드에요

다시 재생하려면 startAnimation()을 호출하면돼요

 

stopAnimation(withoutFinishing: )

withoutFinishing에는 Bool값을 넣어줄 수가있는데

true인경우 finishAnimation을 사용하지 않겠다는 뜻이죠

inactive상태가 되고 현제 위치에 멈춰있는 상태가되요

 

반대로 false를 넣어준경우

stopAnimation이후

finishAnimation을 사용해서 원하는 포지션으로 움직일 수가 있어요

UIViewAnimatingPosition타입을 넣어줄 수 있고

옵션으로 .start, .current, .end가 있어요

.start를 넣어준경우 stop를 호출하면 멈추고 애니메이션이 최초시작된 상태로 변해요

.current면 그자리에 그대로 있겟죠?

.end면 원래 재생하려던 애니메이션끝까지 쭉실행되요

 

이때 넣어준 start, current는 addCompletion

에 들어올 position값으로 이용할 수 있어요

 

DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
    animation.pauseAnimation()
    DispatchQueue.main.asyncAfter(deadline: .now()+0.7) {
        animation.startAnimation()
        DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
            animation.stopAnimation(false)
            animation.finishAnimation(at: .start)
        }
    }
}

0.5초후 일시정지

0.7초후 다시시작

0.5초후 정지

끝나는 애니메이션은 처음으로이동! 해서

끝날때 애니메이션이 처음위치로 돌아오는게 보이죠

 

 

 

addCompletion

completion을 정의해줄 수 있어요

animation.addCompletion { position in
    switch position {
    case .start:
        print("start")
    case .current:
        print("current")
    case .end:
        print("end")
    @unknown default: // 나중에 새로운 케이스가 생기면 실행됨
        fatalError()
    }
}

위에서 말했듯이

start, current는 finishAnimation할때 넣어주면 실행되고

end는 애니메이션이 끝나면 호출되요

stop한경우, pause한경우는 끝이안나서 end호출이 안돼요

 

 

 

pausesOnCompletion

 

애니메이션이 끝나면 해제가 된다고 앞서 말씀드렷죠?

하지만 이프로퍼티를 True로 설정하면

active상태로 유지되고 위에서 completion에있는 end타입이 불리지않아요!

completion 핸들러를 호출하지않기때문이죠

대신에 isRunning 프로퍼티를 이용해서 끝나는걸 확인 할 수 있어요

 

언제쓰냐면 애니메이션이 끝나면 해제가 되기때문에 또사용할 수가 없기때문에

해제시키지않도록 active상태로 유지시키고

reverse애니메이션이나 추후에 애니메이션을 끝나도록 하기위해서 사용할 수 있어요

 

예외로

animation.stopAnimation(false)
animation.finishAnimation(at: .end)

이렇게 종료시켜버리면

핸들러가 불리긴하더라구요

 

 

fractionComplete

처음이 0 끝이 1로

애니메이션의 진행정도를 수정할 수 있어요

 

 

isReversed

애니메이션을 반대로 진행해요

active상태에서만 동작함에 유의하세요!

animation = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
    self.sampleView2.frame = .init(x: 100, y: 300, width: 150, height: 200)
}
guard let animation = animation else { return }

animation.addAnimations {
    self.sampleView2.frame = .init(x: 100, y: 500, width: 150, height: 200)
}
animation.startAnimation()

DispatchQueue.main.asyncAfter(deadline: .now()+1) {
    animation.isReversed = true
}

 

이 경우에는 중간에 reverse를해서 잘동작하지만

완료후 reverse를 하려고한다면 동작하지않을거에요

 

 

autoReverse만들기

func animate() {
    animation.addObserver(self, forKeyPath: "running", options: .new, context: nil)
    animation.pausesOnCompletion = true
    animation.startAnimation()
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == #keyPath(UIViewPropertyAnimator.isRunning){
        if animation?.fractionComplete == 1.0 {
            animation?.isReversed = true
            animation?.startAnimation()
            animation?.removeObserver(self, forKeyPath: #keyPath(UIViewPropertyAnimator.isRunning))
        }
    }
}

먼저 애니메이션이 해제되면 리버스할수 없으므로 유지되도록

pausesOnCompletion = true

로 설정해야해요

그리고

addObserver를 해줘야하구요 

위에서 언급했듯이 completion을 사용할수없어서 끝난 경우를 체크할 수 없을때 isRunning을 옵저빙한다!

드디어 이렇게 사용하네요 ㅎ

keyPath로 "running" 혹은 #keyPath(UIViewPropertyAnimator.isRunning) 둘다가능해요

 

애니메이션이 끝나면

animation.fractionComplete 이부분은 1.0이 될거에요 마지막이니까

그때 리버스를 하고싶으니까 if를 이용해서 분기처리하고

isReverse = true 해주면 반대로 가야겠죠 

하지만 지금 fractionComplete가 1.0이렛죠? 애니메이션은 동작을 다한겁니다!

따라서 다시 startAnimation()을 이용해서 다시 실행시켜줘야 반대로 동작해요

그리고

removeObserver를 꼭해줘야합니다

자동으로 해제해주지 않는다고하네요!

isRunning값이 변경될때마다 호출되서 중복 연산될 수도 있구요

 

 

 

 

 

 

 

 

 

 

 

 

반응형