CoreAnimation & UIView와 CALayer 관계


iOS Core Animation & CALayer


iOS의 Animation을 사용하는 방법은 크게 두가지로 나뉜다.

  • animate(withDuration:)메서드를 사용하여 제어
  • Core Animation을 사용하여 제어

첫번째 방식은 UIView의 메서드로써 직접 원하는 Animation 작업을 작성해주면 해당 옵션에 맞게 애니메이션이 작동한다. 그러나 이 방식은 간단하게 UILabel,UIButton,UIView등의 simple한 애니메이션에 주로 사용한다. 더 복잡하거나 다양한 animation을 주고 싶으면 Core Animation을 사용해야한다. CA(CoreAnimation)의 중요한 개념 몇가지를 다루어 보겠다.


iOS의 Graphic

우선 iOS에서 화면에으로 부터 보여주는 여러 그래픽 요소들을 그려내기 위해 어떤 계층들이 존재하는지 살펴보겠다.

image

iOS의 화면 뒤에는 이런 계층 구조를 이루고 있다. 우선 OpenGL의 경우 그래픽스하드웨어(GPU)와 가장 빠르고 직접적인 접근을 지원하기 때문에 작은 작업에도 상당히 많은 양의 코드가 필요로한다. 이런 문제점을 보완하기 위해 Core GraphicsCore Animation등이 추가가 되어 현재의 저런 형태가 되었다. 그 중 Core Animation은 CALayer을 제공한다.


CALayer

UIView의 layer 프로퍼티인 CALayer이다. UIView는 CALayer 형태의 Layer를 하나 가지고 있다. CALayer는 뷰의 구성요소로 UI적 기능을 당당한다. SubView들은 layer 위에 얹혀지고 layer는 여러개의 SubLayer를 가질수 있다. 이 layer 들은 View의 draw(_ rect:)메서드로 그려진 위에 표시가 된다.

CALayer는 위에서 언급한 것 처럼 UIView에 속하면서 UIView를 지원해주는 역할을 한다. UIView는 레이아웃과 터치 이벤트 처리 등의 많은 작업을 한다.

실제로 View에 컨텐츠나 애니메이션을 그리는 행위는 직접적으로 다루지 않고 UIKit가 이러한 작업들을 Core Animation에 위임한다.

즉 실질적으로 뷰 위에 Contents나 Animation을 그리는 작업은 CALayer가 담당한다.

image

UIView와 CALayer의 계층 구조는 위와 같이 되어있다. UIView는 단순하게 CALayer를 감싸고 있는 형태이고 bound가 변경이 되면(setBounds) UIView는 자신의 root layer의 bounds를 변경한다. UIView가 변경되면 자동으로 CALaye(root)의 레이아웃이 변경된다.

주의! SubLayer의 CALayer의 경우 자동으로 맞추어지지 않는다는 걸 명심해야한다.


UIView와 CALayer 추가

import UIKit

class ViewController: UIViewController {
    
    var isChanged = false
    
    var shadowView: UIView = {
        let view = UIView()
        view.backgroundColor = .darkGray
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    var subLayer: CALayer = {
        let layer =  CALayer()
        layer.backgroundColor = CGColor(srgbRed: 0.5, green: 0.3, blue: 0.1, alpha: 1.0)
        return layer
    }()
    
    var yellowLayer = CALayer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // subLayer를 shadowView에 추가한다.
        subLayer.addSublayer(yellowLayer)
        yellowLayer.backgroundColor = CGColor(srgbRed: 0.5, green: 0.5 , blue: 0, alpha: 1.0)
        shadowView.layer.addSublayer(subLayer)
        subLayer.frame = CGRect(x: shadowView.bounds.origin.x, y: shadowView.bounds.origin.y, width: 100, height: 100)
        subLayer.backgroundColor =  CGColor(srgbRed: 0, green: 0, blue: 0.5, alpha: 1.0)
        // shadowView를 rootView에 추가한다.
        self.view.addSubview(shadowView)
        shadowView.widthAnchor.constraint(equalToConstant: 300).isActive = true
        shadowView.heightAnchor.constraint(equalToConstant: 300).isActive = true
        shadowView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
        shadowView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
        shadowView.layer.shadowRadius = 20
        shadowView.layer.shadowOpacity = 1
        shadowView.layer.shadowColor = UIColor.black.cgColor
        shadowView.layer.cornerRadius = 20
        
    }
    
    @IBAction func changeButtonDidTouch(_ sender: UIButton) {
        if !isChanged {
            self.shadowView.widthAnchor.constraint(equalToConstant: 200).isActive = true
            self.shadowView.heightAnchor.constraint(equalToConstant: 200).isActive = true
            isChanged = true
        } else {
            shadowView.widthAnchor.constraint(equalToConstant: 300).isActive = true
            shadowView.heightAnchor.constraint(equalToConstant: 300).isActive = true
            isChanged = false
        }

        shadowView.layoutIfNeeded()
    }
}

위의 실습은 SuperView에 shadowView를 추가하고 그리고 shadowView내부에 CALayer형의 subLayer를 추가하는 실습이다. 우선 실습을 하면서 몇가지 안 사실들과 주의할점이 있는 거 같다.


addSubView() 메서드를 통해 우선 add를 한 뒤에 속성 값을 설정해야한다!

위의 말대로 self.view.addSubview(shadowView) 코드의 위치도 중요하다. 그렇지 않으면 아래와 같은 에러가 발생한다.

ecause they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That’s illegal.’

그렇게 되면 우선 CALayer의 subLayer의 경우도 마찬가지다 우선 shadowView에 addSubLayer()를 통해 하위 레이어를 설정한 뒤 속성값을 변경 시켜야한다.


UIView가 변경 되면 layer도 변경 된다. 하지만 SubLayer는 변경 되지 않는다.

image

change버튼을 터치시 changeButtonDidTouch 호출로 shadowView의 사이즈를 변경 시켜 보았다. 변경 시켰을때 layer로 주었던 속성들이 roowView인 shadowView의 변화에 맞추어서 변경이 되었음을 확인 할 수 있다.

주의! SubLayer의 CALayer의 경우 자동으로 맞추어지지 않는다는 걸 명심해야한다.


image

마찬가지로 yellowLayer를 blueLayer에 추가하였을 때도 위와 같이 자동으로 맞추어 지지 않고 따로 설정을 해주어야 한다.


clipsToBounds ? maskToBounds ?

우선 이 둘의 하는 역할은 똑같다. 하지만 이 둘이 속한 프로퍼티 영역이 다르다.

self.view.clipsToBounds = true
self.view.layer.masksToBounds = true

위에서 확인 할 수있듯이 clipsToBoundsUIView에 속하고 maskToBoundsCALayer에 속한다.
이 둘의 용도는 true로 설정이 되어있기 때문에 clipsToBounds의 경우 SuperView의 경계를 무시하고 자신을 보여주는 의미이고, maskToBounds의 경우는 RootLayer의 경계를 무시한다는 말이된다.



Reference





© 2020. by Gaki

Powered by gaki