[iOS/Swift] Custom Animation Popup 만들기
이번 글은
UIView.animate를 사용하여, Custom Animation Popup을 만드는 방법입니다.
해당 포스팅은 Storyboard intreface 기반 Swift 프로젝트입니다.
1. UIWindow+Ext.swift 준비
팝업 호출 뷰의 디폴트 값을 최상위 뷰로 하기 위해, 최상위 뷰 컨트롤러를 얻는 UIWindow Extension을 먼저 만들어 줍니다.
참고 블로그 포스팅 : [iOS/Swift] 최상위에 있는 뷰컨트롤러 얻기
import UIKit
extension UIWindow {
public var visibleViewController: UIViewController? {
return self.visibleViewControllerFrom(vc: self.rootViewController)
}
/**
# visibleViewControllerFrom
- Author: suni
- Date:
- Parameters:
- vc: rootViewController 혹은 UITapViewController
- Returns: UIViewController?
- Note: vc내에서 가장 최상위에 있는 뷰컨트롤러 반환
*/
public func visibleViewControllerFrom(vc: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nc = vc as? UINavigationController {
return self.visibleViewControllerFrom(vc: nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return self.visibleViewControllerFrom(vc: tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return self.visibleViewControllerFrom(vc: pvc)
} else {
return vc
}
}
}
}
2. SnapKit 코코아팟 설치
스토리보드가 아닌 소스 코드로 Auto Layout을 컨트롤 할 때, 쉽게 하기 위해 SnapKit을 설치하여 사용합니다.
SnapKit은 iOS 및 OS X에서 Auto Layout을 쉽게 하기 위한 DSL(Domain-specific Language)입니다.
Podfile을 생성하여 SnapKit 을 추가하고 설치합니다.
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'SNPopup' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for SNPopup
######################## UI
# AutoLayout 관련
pod 'SnapKit'
end
참고 포스팅 : [iOS/Xcode] CocoaPods(코코아팟) 사용하기
2. BasePopVC.swift 생성
먼저, UIViewController를 상속받는 BasePopVC를 생성합니다.
BasePopVC.swift는 Custom Animation Popup 기능을 만들어, 필요한 팝업 클래스에 상속할 클래스입니다.
class BasePopVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.layoutIfNeeded()
}
}
3. 애니메이션 관련 enum 생성
BasePopVC.swift에 애니메이션에 필요한 enum을 선언합니다.
/**
# (E) PopupPosition
- Author: suni
- Date:
- Note: PopupVC에 애니메이션 시작 포지션을 정하는 enum
*/
enum PopupPosition: String {
case top = "Top"
case bottom = "Bottom"
case left = "Left"
case right = "Rigth"
case center = "Center"
case none = ""
}
/**
# (E) PopupType
- Author: suni
- Date:
- Note: PopupVC에 애니메이션 타입을 모아둔 enum
*/
enum PopupType: String {
case fadeInOut = "Fade In Out"
case move = "Move"
case none = ""
}
4. 상수 선언
애니메이션 시간도 선언합니다.
public let ANIMATION_DURATION: TimeInterval = 0.5
5. BasePopVC.swift
Custom Animation Popup 기능을 수행하는 BasePopVC 클래스 소스입니다.
class BasePopVC: UIViewController {
// popup dim 투명도
private final let DIM_ALPHA: CGFloat = 0.3
// popup dim view
@IBOutlet weak var vDim: UIView!
// popup content view
@IBOutlet weak var vPopup: UIView!
// popup animation type
private var type: PopupType = .none
// popup push start position
private var position: PopupPosition = .none
override func viewDidLoad() {
super.viewDidLoad()
self.view.layoutIfNeeded()
}
}
extension BasePopVC {
/**
# showAnim
- Author: suni
- Date:
- Parameters:
- vc : 팝업을 노출할 뷰컨트롤러
- type : 팝업의 애니메이션 타입
- position : 팝업 애니메이션 시작 포지션
- parentAddView : 해당 뷰컨트롤러의 뷰를 적용할 부모 뷰컨트롤러의 뷰
- completeion : 해당 화면 노출 애니메이션이 완료된 이후에 부모 뷰컨트롤러에서 처리할 클로저
- Returns:
- Note: 팝업 화면을 애니메이션을 넣어서 보이는 함수
*/
func showAnim(vc: UIViewController? = UIApplication.shared.keyWindow?.visibleViewController, type: PopupType = .fadeInOut, position: PopupPosition = .none, parentAddView: UIView?, _ completion: @escaping ()->()) {
guard let currentVC = vc else {
completion()
return
}
var pView = parentAddView
if pView == nil {
pView = vc?.view
}
guard let parentView = pView else {
completion()
return
}
self.type = type
self.position = position
currentVC.addChild(self)
self.view.translatesAutoresizingMaskIntoConstraints = false
parentView.addSubview(self.view)
self.view.snp.makeConstraints {
$0.edges.equalToSuperview()
}
switch type {
case .fadeInOut:
self.vDim.alpha = 0.0
self.vPopup.alpha = 0.0
self.view.layoutIfNeeded()
UIView.animate(withDuration: ANIMATION_DURATION/2) { [weak self] in
if let _self = self {
_self.vDim.alpha = _self.DIM_ALPHA
}
} completion: { (complete) in
UIView.animate(withDuration: ANIMATION_DURATION/2) { [weak self] in
if let _self = self {
_self.vPopup.alpha = 1.0
}
} completion: { (complete) in
completion()
}
}
case .move:
self.vDim.alpha = 0.0
let originalTransform = self.vPopup.transform
var moveX: CGFloat = 0.0
var moveY: CGFloat = 0.0
switch position {
case .top:
moveX = 0.0
moveY = -(self.vPopup.frame.maxY)
case .bottom:
moveX = 0.0
moveY = UIScreen.main.bounds.size.height - self.vPopup.frame.minY
case .left:
moveX = -(self.vPopup.frame.maxX)
moveY = 0.0
case .right:
moveX = UIScreen.main.bounds.size.width - self.vPopup.frame.minX
moveY = 0.0
case .center:
moveX = 0.0
moveY = 0.0
default:
break
}
let hideTransform = originalTransform.translatedBy(x: moveX, y: moveY)
self.vPopup.transform = hideTransform
self.vPopup.alpha = 0.0
UIView.animate(withDuration: ANIMATION_DURATION) { [weak self] in
if let _self = self {
_self.vPopup.transform = originalTransform
_self.vPopup.alpha = 1.0
_self.vDim.alpha = _self.DIM_ALPHA
}
} completion: { (complete) in
completion()
}
default:
completion()
}
}
/**
# hideAnim
- Author: suni
- Date: 20.08.19
- Parameters:
- type : 팝업의 애니메이션 타입
- position : 팝업 애니메이션 숨김 포지션
- completeion : 해당 화면 숨김 애니메이션이 완료된 이후에 부모 뷰컨트롤러에서 처리할 클로저
- Returns:
- Note: 팝업 화면을 애니메이션을 넣어서 숨기는 함수
*/
func hideAnim(type: PopupType = .none, position: PopupPosition = .none, _ completion: @escaping ()->()) {
DispatchQueue.main.async {
switch self.type {
case .fadeInOut:
UIView.animate(withDuration: ANIMATION_DURATION/2, animations: { [weak self] in
if let _self = self {
_self.vPopup.alpha = 0.0
}
}) { (complete) in
UIView.animate(withDuration: ANIMATION_DURATION/2, animations: { [weak self] in
self?.vDim.alpha = 0.0
}) { [weak self] complete in
if let _self = self {
_self.view.removeFromSuperview()
_self.removeFromParent()
}
}
}
break
case .move:
let originalTransform = self.vPopup.transform
var moveX: CGFloat = 0.0
var moveY: CGFloat = 0.0
switch position {
case .top:
moveX = 0.0
moveY = -(self.vPopup.frame.maxY)
case .bottom:
moveX = 0.0
moveY = UIScreen.main.bounds.size.height - self.vPopup.frame.minY
case .left:
moveX = -(self.vPopup.frame.maxX)
moveY = 0.0
case .right:
moveX = UIScreen.main.bounds.size.width - self.vPopup.frame.minX
moveY = 0.0
case .center:
moveX = 0.0
moveY = 0.0
default:
break
}
let hideTransform = originalTransform.translatedBy(x: moveX, y: moveY)
UIView.animate(withDuration: ANIMATION_DURATION, animations: { [weak self] in
if let _self = self {
_self.vDim.alpha = 0.0
_self.vPopup.alpha = 0.0
_self.vPopup.transform = hideTransform
}
}) { [weak self] complete in
if let _self = self {
_self.view.removeFromSuperview()
_self.removeFromParent()
completion()
}
}
break
default:
completion()
}
}
}
@IBAction func btnCancelPressed(_ sender: UIButton) {
self.hideAnim(type: self.type, position: self.position) {
}
}
@IBAction func btnCompletePressed(_ sender: UIButton) {
self.hideAnim(type: self.type, position: self.position) {
}
}
}
6. 사용 예제
BasePopVC를 상속받는 커스텀 팝업 클래스를 생성합니다.
import UIKit
class SNPopVC: BasePopVC {
override func viewDidLoad() {
super.viewDidLoad()
}
}
Storyboard에서 팝업을 커스텀하여 만든 뒤,
> Custom Class > Class 에 팝업의 클래스명을 입력합니다.
Identity > StoryboardID 에 클래스명을 입력한 뒤, Use Storyboard ID를 입력합니다.
BasePopVC에 vDim과 vPopup을 연결합니다.
BasePopVC에 btnCompleteProssed Action을 연결합니다.
이제 아래 코드로 팝업을 호출하면 됩니다.
let storyBoard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let popVC = storyBoard.instantiateViewController(withIdentifier: "SNPopVC") as! SNPopVC
popVC.showAnim(vc: self, type: .move, position: .bottom, parentAddView: self.view) { }
댓글남기기