티스토리 뷰

iOS에서 화면전환하는 것에 대해서 보고 있는중인데, 화면을 전환하는 효과에 대해서 한번 적어보려고 합니다.


일단 화면을 전환하려고 할 경우에, iOS에서는 기본으로 두가지 애니메이션이 들어가 있습니다.


Push와 Present 이렇게 기본적으로는 두가지 입니다.


이것을 아무것도 지정하지 않고 그냥 하게 해주면, 가장 기본적인 오른쪽에서 화면이 오는것과 아래에서 화면이 나오는것 두가지가 될 수 있습니다.

물론 기본 제공하여 주는것중에 Present에서 제공하는것이 있기는 합니다만. fade in의 느낌이 나는 crossDissolve 옵션이 있습니다.


이전에도 다른 방법으로 Push되는 화면을 처리하는 방법을 이용하였는데, 이전에는 그냥 단순하게 Push할 경우에 fade를 넣어주는 방법을 하였는데 이번에는 원이 휭하고 나오는 효과를 주려고 합니다.


이번에는 Present를 하고 내일쯤에 Push 하는 방법을 올리도록 하겠습니다.


클래스의 핵심 부분들만 먼저 설명할게요.


1. NSObject를 상속받은 Transition Class를 하나 만듭니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CircleTransition: NSObject {
    
    enum TransitionMode {
        case present, dismiss, pop
    }
 
    var circle: UIView!
    var circleColor: UIColor = .white
    var duration = 0.3
    var transitionMode: TransitionMode = .present
    var startPoint: CGPoint = .zero {
        didSet {
            circle?.center = startPoint
        }
    }
}
cs


2. 이 클래스에 UIViewControllerAnimatedTransitioning를 구현하여 줍니다.


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
extension CircleTransition: UIViewControllerAnimatedTransitioning {
    
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration // 애니메이션 duration
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView // 애니메이션이 발생하여야 할 경우에 호출됩니다. present, dismiss 등
        
        switch transitionMode {
        case .present:
            if let presentedView = transitionContext.view(forKey: .to) {
                let viewCenter = presentedView.center
                let viewSize = presentedView.frame.size
                
                circle = UIView()
                circle.frame = frameForCircle(center: viewCenter, size: viewSize, startPoint: startPoint)
                circle.layer.cornerRadius = circle.frame.size.height / 2
                circle.center = startPoint
                circle.backgroundColor = circleColor
                circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
                
                containerView.addSubview(circle)
                
                presentedView.center = startPoint
                presentedView.transform = CGAffineTransform.init(scaleX: 0.001, y: 0.001)
                presentedView.alpha = 0
                
                containerView.addSubview(presentedView)
                
                UIView.animate(withDuration: duration, animations: { [weak selfin
                    guard let `self= self else { return }
                    
                    self.circle.transform = CGAffineTransform.identity
                    
                    presentedView.center = viewCenter
                    presentedView.transform = CGAffineTransform.identity
                    presentedView.alpha = 1
                }) { success in
                    transitionContext.completeTransition(success)
                }
            }
        default:
            let transitionModeKey: UITransitionContextViewKey = (transitionMode == .pop) ? .to : .from
            
            if let returningView = transitionContext.view(forKey: transitionModeKey) {
                let viewCenter = returningView.center
                let viewSize = returningView.frame.size
                
                circle.frame = frameForCircle(center: viewCenter, size: viewSize, startPoint: startPoint)
                circle.layer.cornerRadius = circle.frame.size.height / 2
                circle.center = startPoint
                
                UIView.animate(withDuration: duration, animations: { [weak selfin
                    guard let `self= self else { return }
                    
                    self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
                    returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
                    returningView.center = self.startPoint
                    returningView.alpha = 0
                    
                    if self.transitionMode == .pop {
                        containerView.insertSubview(returningView, belowSubview: returningView)
                        containerView.insertSubview(self.circle, belowSubview: returningView)
                    }
                }) { [weak self] success in
                    guard let `self= self else { return }
                    
                    returningView.center = viewCenter
                    returningView.removeFromSuperview()
                    
                    self.circle.removeFromSuperview()
                    
                    transitionContext.completeTransition(success)
                }
            }
        }
    }
    
    private func frameForCircle(center: CGPoint, size: CGSize, startPoint: CGPoint) -> CGRect {
        let xLength = fmax(startPoint.x, size.width - startPoint.x)
        let yLength = fmax(startPoint.y, size.height - startPoint.y)
        
        let offsetVector = sqrt(xLength * xLength + yLength * yLength) * 2
        let size = CGSize(width: offsetVector, height: offsetVector)
        
        return CGRect(origin: CGPoint.zero, size: size)
    }
}
cs


ViewController의 Transition의 핵심은 위의 두개 function 입니다.

나머지는 뷰의 모양을 변경하기 위해서 넣어주는 부분이기 때문에, 다른 애니메이션을 만들때는 없어도 되는 부분입니다.


1) transitionDuration(using: UIViewControllerContextTransitioning?) -> TimeInterval

2) animateTransition(using: UIViewControllerContextTransitioning)


간략하게 위의 소스를 설명을 하면, transitionMode에 따라서, 자신이 동작하여야 하는 애니메이션을 지정을 하여 두고 애니메이션을 동작하게 하는것입니다.


중요. 애니메이션이 끝나면 completeTransition을 호출하여 주세요!!


이 경우에는 present나 이러한 경우에 대해서 enum 값으로 지정된것을 사용하는데 응용을 하는것은 이 글을 보는 분들의 몫이라 생각합니다.


3. 호출하는 ViewController에서 구현.


UIViewControllerTransitioningDelegate를 구현하여 줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
extension ViewController : UIViewControllerTransitioningDelegate {
    
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        transition.transitionMode = .present
        return transition
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        transition.transitionMode = .dismiss
        return transition
    }
}
cs


4. ViewController에 transitioning 할 수 있도록 코드를 넣어줍시다.


아마 위의 코드를 붙여 넣고 안된다고 하시는 분들을 끝까지 따라 하시면 될겁니다.


1
2
3
4
5
6
private lazy var transition: CircleTransition = {
    let transition = CircleTransition()
    transition.startPoint = nextButton.center
    transition.circleColor = nextButton.backgroundColor ?? UIColor.black
    return transition
}()
cs


위의 extension에서 사용하는 transition을 lazy var를 이용하여 이용되는 시점에 초기화하도록 합니다.


특히나 present시킬 화면을 사용자의 선택에 의해서 전환이 되는것인데, 이것을 궂이 먼저 선언할 필요는 없는것 같네요.


5. Present할 때 delegate를 지정하여 줍시다


1
2
3
4
5
6
7
@IBAction func didClickNext(_ sender: Any) {
    let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Present")
    viewController.transitioningDelegate = self
    viewController.modalPresentationStyle = .custom
        
    present(viewController, animated: true, completion: nil)
}
cs


저러면 transitioningDelegate를 지정하게 되고, 화면이 전환되면서 CircleTransition을 이용할 수 있게 됩니다.


소스는 github에 올려두었습니다.


소스 여러개 계속 올려둘 계획이니 한번씩 놀러가 보세요. 아직은 별로 없으니 뭐라고 하지 마세요.


https://github.com/outofcoding/TransitionAnimateTest



'Mobile > iOS' 카테고리의 다른 글

cocoapods. Library 등록 하기  (0) 2018.06.25
Swift. reflection.  (0) 2018.06.24
RxSwift. Observable combine  (0) 2018.06.19
RxSwift. Transforming Observable  (0) 2018.06.19
Swift. Codable Encoding (1)  (0) 2018.06.17
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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
글 보관함