Skip to content

UIKit: Button animation #3

Description

@dodo849

문제

UIKit에서 버튼(DSButton) press시 color, scale 애니메이션 적용


UIKit에서 애니메이션을 다룰 수 있는 방법은 3개 UIView.animation, UIView.transition, CALayer-CABasicAnimiation.
이중 UIView.animation, UIView.transition은 하이레벨 CALayer-CABasicAnimiation은 로우레벨로, CALayer 사용시 애니메이션 지정이 복잡하다.

Try 1 - UIView.animation

  UIView.animate(
      withDuration: 0.35,
      delay: 0,
      usingSpringWithDamping: 0.8,
      initialSpringVelocity: 0.8,
      options: []
  ) {
       self.transform = .init(scaleX: 0.9, y: 0.9)
      self.backgroundColor = colorTheme.backgroundColor(state: .pressed).uiColor
  }
Simulator.Screen.Recording.-.iPhone.15.-.2024-06-18.at.15.01.10.mp4

scale애니메이션은 적용되나 컬러 애니메이션은 적용되지 않아 색상 변경이 끊어져서 진행됨.

Try2 - CALayer-CABasicAnimation

    let caLayer = CALayer()
    caLayer.backgroundColor = self.backgroundColor?.cgColor
    caLayer.frame = self.bounds
    self.layer.addSublayer(caLayer)
  
    let colorAnimation = CABasicAnimation(keyPath: "backgroundColor")
    colorAnimation.duration = 0.35
    colorAnimation.fromValue = fromColor
    colorAnimation.toValue = toColor
    colorAnimation.isRemovedOnCompletion = false
    colorAnimation.fillMode = .forwards
    colorAnimation.repeatCount = 1
    colorAnimation.delegate = LayerRemover(for: caLayer)
    caLayer.add(colorAnimation, forKey: "backgroundColorChange")

// - support
class LayerRemover: NSObject, CAAnimationDelegate {
    private weak var layer: CALayer?

    init(for layer: CALayer) {
        self.layer = layer
        super.init()
    }
    
    func animationDidStart(_ anim: CAAnimation) {
        print("start")
    }

    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        print("stop")
        layer?.removeFromSuperlayer()
    }
}
Simulator.Screen.Recording.-.iPhone.15.-.2024-06-18.at.15.04.26.mp4

CALayer로 추가 시 컬러 애니메이션은 잘 되나, label의 텍스트 색상까지 변경시키는 문제 발생.
이에 더해서 translucent의 경우 opacity 컬러를 사용하기 때문에 CALayer가 계속 추가되면서 색상이 진해짐.
따라서 애니메이션 종료 후 레이어를 지워주기 위해 LayerRemover를 도입했으나 아직 press중인 상태임에도 애니메이션 완료로 calayer가 해제되면서 깜빡임 발생.

이후 포기..press와 animation complete시점을 어떻게 조정해야할지 모르겠음. 타이머 넣어서 하면 어떻게 될 것 같긴한데 너무 복잡.

Try3 - UIView.transition

// - beginTracking
UIView.transition(with: self, duration: 0.2, options: [.transitionCrossDissolve], animations: {
    self.backgroundColor = colorTheme.backgroundColor(state: .pressed).uiColor
    self.transform = .init(scaleX: 0.9, y: 0.9)
}) { (finish) in
}
// - endTracking
UIView.transition(with: self, duration: 0.2, options: [.curveEaseInOut], animations: {
   self.backgroundColor = colorTheme.backgroundColor(state: .enabled).uiColor
    self.transform = .identity
   })

[최종]

Simulator.Screen.Recording.-.iPhone.15.-.2024-06-18.at.15.33.00.mp4

transition에서만 사용 가능한 .transitionCrossDissolve옵션을 이용했더니 자연스럽게 동작.


endTracking에 curveEaseInOut을 사용한 이유는 scale이 커질땐 외부까지 겹쳐보이게됨. 따라서 컬러 변경은 포기하고 모양만 애니메이션 적용.

Simulator.Screen.Recording.-.iPhone.15.-.2024-06-18.at.15.22.59.mp4

결론

try3로 완성.
하지만 SwiftUI에 비해 부드러움이 아쉬운 것은 사실이다... spring같은 보간 애니메이션을 사용해야할듯..

[SwiftUI Button]

Simulator.Screen.Recording.-.iPhone.15.-.2024-06-18.at.15.32.32.mp4

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions