관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] custom popup (커스텀 팝업), 팝업 애니메이션(스프링 애니메이션, usingSpringWithDamping, initialSpringVelocity) 본문

iOS 응용 (swift)

[iOS - swift] custom popup (커스텀 팝업), 팝업 애니메이션(스프링 애니메이션, usingSpringWithDamping, initialSpringVelocity)

jake-kim 2022. 5. 23. 23:52

팝업 구현(뒤 배경이 어두워지고, 밑에서 위로 올라오고 spring애니메익션 적용)

사용한 프레임워크

  • snapkit - 코드로 autolayout을 편리하게 구현하기 위해서 사용

구현 아이디어

  • UIView를 커스텀하여 팝업 뷰로 생성
  • 팝업이 뜰때 뒷 배경이 어두워져야하고, 덮혀지는 뷰의 interation을 막아야하므로 사용하는 쪽에서 autolayout으로 화면 전체로 잡아서 사용
  • 화면 전체로 잡혀야하므로, 커스텀 팝업 뷰의 UI구성은 아래처럼 구성
    • (처음부터 가지고 있는) UIView - 어두운 배경으로 사용
    • 그 위 팝업 contentView - 팝업 제목과 같은 팝업 내용이 들어갈 흰색 뷰
  • 애니메이션 구현
    • 애니메이션은 아래에서 위로 올라와야하므로, autolayout으로 처음에 흰색 뷰를 화면 밑에 놓고, show() 라는 메소드를 만들어 이 메소드를 호출할 때 위로 올라오게끔 autolayout 변경 및 애니메이션 적용

커스텀 팝업 뷰 구현

  • 클래스 준비
//  MyPopupView.swift

import UIKit
import SnapKit

final class MyPopupView: UIView {
}
  • UI 준비
  private let contentView: UIView = {
    let view = UIView()
    view.backgroundColor = .white
    view.layer.cornerRadius = 14
    view.clipsToBounds = true
    return view
  }()
  private let titleLabel: UILabel = {
    let label = UILabel()
    label.text = "팝업 제목"
    label.font = .systemFont(ofSize: 24, weight: .bold)
    label.textColor = .black
    label.textAlignment = .center
    return label
  }()
  private let button: UIButton = {
    let button = UIButton()
    button.setTitle("확인", for: .normal)
    button.setTitleColor(.systemBlue, for: .normal)
    button.setTitleColor(.blue, for: .highlighted)
    button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
    return button
  }()
  • 레이아웃
    • contentView가 흰색 팝업이 될 것이므로 레이아웃을 현재 화면의 바닥 아래로 지정
  override init(frame: CGRect) {
    super.init(frame: frame)
    
    self.addSubview(self.contentView)
    self.contentView.addSubview(self.titleLabel)
    self.contentView.addSubview(self.button)
    
    self.contentView.snp.makeConstraints {
      $0.left.right.equalToSuperview().inset(50)
      $0.top.equalTo(self.snp.bottom) // <-
    }
    self.titleLabel.snp.makeConstraints {
      $0.top.equalToSuperview().offset(30)
      $0.left.right.equalToSuperview()
    }
    self.button.snp.makeConstraints {
      $0.top.equalTo(self.titleLabel.snp.bottom).offset(50)
      $0.centerX.bottom.equalToSuperview()
    }
  }
  func show(completion: @escaping () -> Void = {}) {
    self.contentView.snp.remakeConstraints {
      $0.centerY.equalToSuperview()
      $0.left.right.equalToSuperview().inset(50)
    }
    UIView.animate(
      withDuration: 0.2,
      delay: 0,
      options: .curveEaseInOut,
      animations: { 
        self.backgroundColor = UIColor.black.withAlphaComponent(0.3) 
        self.layoutIfNeeded()
      },
      completion: nil
    )
  }
  func hide(completion: @escaping () -> Void = {}) {
    self.removeFromSuperview()
  }
  
  @objc private func didTapButton() {
    self.hide()
  }
  • 팝업이 지정한 레이아웃보다 조금 더 위로 올라갔다가 다시 아래로 내려가는 damping 애니메이션은 스프링 애니메이션을 사용

스프링 애니메이션 개념

  • 목표지점에 도착한 뒤, 살짝 튕기고 제자리로 돌아오는 스프링 애니메이션 구현
  • UIView.animate로 스프링 애니메이션 구현
  • 핵심 프로퍼티 
    • usingSpringWithDamping: 애니메이션이 끝나가는 (=정지) 상태에 근접할 때 damping 비율을 의미 (0에 가까울 수록 쎈 damping)
    • initialSpringVelocity: 0에 가까울수록 damping의 속도가 빠르고 1에 가까울 수록 속도가 느린 것

ex) damping 움직임이 크고, damping의 속도가 느리도록 하고 싶은 경우

* animations 블럭에 layoutIfNeeded()를 실행시키는 이유를 이해하고 싶은 경우, 이 포스팅 글 참고

@objc private func damping() {
  self.dampingAnimationButton.snp.remakeConstraints {
    $0.center.equalToSuperview()
  }
  UIView.animate(
    withDuration: 2.0,
    delay: 0,
    usingSpringWithDamping: 0.1,
    initialSpringVelocity: 0.9,
    options: [],
    animations: { self.view.layoutIfNeeded() },
    completion: nil
  )
}

ex) damping 움직임이 작고, damping의 속도가 느리도록 하고 싶은 경우, 

@objc private func damping() {
  self.dampingAnimationButton.snp.remakeConstraints {
    $0.center.equalToSuperview()
  }
  UIView.animate(
    withDuration: 2.0,
    delay: 0,
    usingSpringWithDamping: 0.9,
    initialSpringVelocity: 0.9,
    options: [],
    animations: { self.view.layoutIfNeeded() },
    completion: nil
  )
}
  • show() 메소드에 애니메이션을 적용
    • damping은 0.76으로 적당히 자연스러운 damping 으로 설정
    • 속도는 빠르게 설정
  func show(completion: @escaping () -> Void = {}) {
    self.contentView.snp.remakeConstraints {
      $0.centerY.equalToSuperview()
      $0.left.right.equalToSuperview().inset(50)
    }
    UIView.animate(
      withDuration: 0.2,
      delay: 0,
      options: .curveEaseInOut,
      animations: { self.backgroundColor = UIColor.black.withAlphaComponent(0.3) },
      completion: nil
    )
    UIView.animate(
      withDuration: 0.5,
      delay: 0,
      usingSpringWithDamping: 0.76,
      initialSpringVelocity: 0.0,
      options: [],
      animations: self.layoutIfNeeded,
      completion: { _ in completion() }
    )
  }

Damping 적용

* 전체 코드: https://github.com/JK0369/ExPopup

Comments