250x250
반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

수니의 개발새발

[iOS/Swift] Custom Animation Popup 만들기 (with. Storyboard) 본문

iOS - Swift

[iOS/Swift] Custom Animation Popup 만들기 (with. Storyboard)

개발자 수니 2021. 12. 29. 14:32
728x90
반응형

📌 이번 글은

UIView.animate를 사용하여, Custom Animation Popup을 만드는 방법입니다.
해당 포스팅은 Storyboard intreface 기반 Swift 프로젝트입니다.

1. UIWindow+Ext.swift 준비

팝업 호출 뷰의 디폴트 값을 최상위 뷰로 하기 위해, 최상위 뷰 컨트롤러를 얻는 UIWindow Extension을 먼저 만들어 줍니다.
참고 포스팅 : [iOS/Swift] 최상위에 있는 뷰 컨트롤러 얻기

 

[iOS/Swift] 최상위에 있는 뷰컨트롤러 얻기

개발하면서 앱의 최상위 뷰 컨트롤러를 찾아야 할 일이 종종 있어요. 처음에는 최상위 뷰에 얼럿을 띄우기 위해 작업을 했지만, 한번 extension으로 빼놓으니 1) 웹뷰 통신(브릿지 호출)으로 뷰 이

sunidev.tistory.com

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(코코아팟) 사용하기

 

[iOS/Xcode] CocoaPods(코코아팟) 사용하기

CocoaPods는 Swift 및 Objective-C 코코아 프로젝트의 종속성 관리자입니다. 28,000개가 넘는 라이브러리를 가지고 있으며 170만 개가 넘는 응용 프로그램(앱)에서 사용되고 있습니다. CocoaPod은 프로젝트를

sunidev.tistory.com

 

3. BasePopVC.swift 생성

먼저, UIViewController를 상속받는 BasePopVC를 생성합니다.
BasePopVC.swift는 Custom Animation Popup 기능을 만들어, 필요한 팝업 클래스에 상속할 클래스입니다.

class BasePopVC: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.layoutIfNeeded()
    }
}



4. 애니메이션 관련 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 = ""
}



5. 상수 선언

애니메이션 시간도 선언합니다.

public let ANIMATION_DURATION: TimeInterval = 0.5



6. 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) { }

 

👩🏻‍💻 프로젝트 예제 소스

 

GitHub - SuniDev/CustomAnimationPopup-storyboard: Custom Animation Popup 만들기 (with. storyboard)

Custom Animation Popup 만들기 (with. storyboard). Contribute to SuniDev/CustomAnimationPopup-storyboard development by creating an account on GitHub.

github.com

 

728x90
반응형
Comments