[iOS]CALayer의 기본 개념

신용철·2020년 10월 5일
1

iOS_View

목록 보기
4/11

1. UIView와 CALayer(underlying layer)의 관계

  • UIView를 상속받은 view class가 인스턴스화될 때, CALayer가 함께 인스턴스화 됩니다. 그 이유는 UIView class가 CALayerDelegate를 채택하고 있기 때문입니다. 따라서 모든 view는 하나의 layer를 갖습니다.

  • 사실 UIView는 스스로를 drawing하는 것이 아니라 layer위에 drawing을 하는 것입니다. view.backgroundColor를 변경하는 것도 사실은 layer의 backgroundColor를 변경하는 것이고 반대로, layer의 backgroundColor를 변경하면 view에 반영이 됩니다. 마찬가지로 view의 frame은 사실 layer의 frame입니다. 즉, 모든 view의 drawing은 layer에서 이루어 집니다.

  • 실제로 view의 대부분의 속성은 layer의 속성에 접근하는 역할을 수행합니다. view가 layer의 '대리자(delegate)' 이기 때문이죠.

2. layer를 활용하는 방법

  • layer는 drawing에 있어 UIView의 속성보다 훨씬 더 많은 속성들을 제공합니다. 즉, layer의 속성에 접근 하면 view를 drawing할 수 있는 많은 옵션을 관리할 수 있습니다.

  • layer는 다른 layer(sublayers)를 포함할 수 있습니다. 이를 이용하여 UIView를 여러 조각으로 쪼갤 수 있고 각각은 objecet로 간주되기 때문에 더욱 효과적으로 drawing을 관리할 수 있습니다. 하나의 파트으로 관리하던 view를 여러 파트로 쪼개어 drawing하고 합치는 개념이라고 이해하시면 되겠습니다.

  • layer를 사용하면 애니메이션 효과를 컨트롤 할 수 있습니다. CALayer의 'CA'는 Core Animation의 약자입니다. 즉, animation을 정복하기 위해서는 CALayer를 정복해야 합니다.

3. Bitmap 캐쉬를 이용한 view drawing 관리 - On - demand drawing

  • view는 최대한 다시그려지는 것을 방지합니다. 작업의 효율성을 높이기 위함이죠. 이를 위해 사용하는 것이 bitmap 백업저장소 입니다. view가 layer위에 그려지면 layer는 이를 캐싱하여 bitmap 캐쉬에 저장하였다가 재사용합니다. view의 경계가 변경되면 캐쉬된 layer의 이미지를 확장시키거나 재배치하는 방식으로 재사용합니다.

  • 만약 view에 표시될 content를 수정하고 redraw를 하고 싶다면, setNeedsDisplay() method를 호출해줍니다. setNeedsDisplay()를 호출하면 caching되어있는 것을 삭제하고 redraw된 결과를 다시 caching합니다.

  • contentMode의 Scale to Fill, Aspecti Fit, Aspect Fill 등은 모두 bitmap 캐쉬를 이용한 방식입니다.

4. custom한 CALayer 사용방법

  • 기본적으로 UIView가 인스턴스화 될 때 함께 인스턴스화 되는 것은 vanila CALayer라고 합니다. 때로는 이것을 그대로 사용하지않고 custom화된 layer를 사용하고 싶은 경우가 있을 수 있는데요. 이를 위해서 아래와 같이 UIView의 subclass에서 layerClass 프로퍼티를 CALayer subclass로 리턴하는 방식을 사용하면 됩니다.
class SomeView: UIView {
   override class var layerClass: AnyClass {
        return CustomLayer.self
   }
}

5. subLayer와 Layer의 계층구조

  • layer는 여러개의 sublayer를 가질 수 있고, 하나의 superLayer를 가질 수 있습니다. 즉, 이 말은 view와 같이 layer역시 계층 구조를 생각해볼 수 있다는 말인데요. 사실, view와 layer의 관계가 매우 긴밀하게 짜여져있기 때문에 view의 계층구조와 layer의 계층구조는 사실상 같다고 보셔도 됩니다. 이를 좀더 구체적으로 설명드리자면, superView의 layer는 superLayer가 되며, 모든 subView들의 layer는 subLayer가 됩니다. 상식적으로 생각해 보면, view가 그려지는 것은 layer이기 때문에 view의 계층구조가 layer의 계층 구조가 될 수 밖에 없습니다. (그림출처: Matt Neuburg's Programming iOS, Dive Deep into Views, View Controllers, and Frameworks)
  • 하지만, 큰 뼈대는 같아도 세부적으로는 계층 구조가 달라질 수 있습니다. 사실 달라졌다기 보다는 좀 더 세분화 될 수 있다고 표현하는게 맞겠네요. view는 하나의 layer를 갖지만 layer자체는 어느 view에도 속하지 않은 subLayer를 가질 수 있기 때문입니다. 아래의 그림과 같이 말이죠.(그림출처: Matt Neuburg's Programming iOS, Dive Deep into Views, View Controllers, and Frameworks)
  • layer를 사용하면 하나의 view를 가지고 여러 개의 view가 구성되는 듯 한 화면을 구현을 해볼 수 있습니다.
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let firstLayer = CALayer()
             firstLayer.backgroundColor = UIColor.red.cgColor
             firstLayer.frame = CGRect(x: 111, y: 111, width: 132, height: 194)
        let secondLayer = CALayer()
             secondLayer.backgroundColor = UIColor.blue.cgColor
             secondLayer.frame = CGRect(x: 41, y: 56, width: 132, height: 194)
        let thirdLayer = CALayer()
             thirdLayer.backgroundColor = UIColor.green.cgColor
             thirdLayer.frame = CGRect(x: 43, y: 197, width: 132, height: 194)
            
            self.view.layer.addSublayer(firstLayer)
            firstLayer.addSublayer(secondLayer)
            self.view.layer.addSublayer(thirdLayer)   
    }
}    
  • 위의 예에서는 하나의 view와 3개의 layer를 이용하여 아래 그림과 같이 view가 3개인 듯 한 화면을 구현해보았습니다. 여기서 firstLayer와 thirdLayer는 sibling관계에 있고 secondLayer는 firstLayer의 sublayer입니다.

  • 만약에 여기서 새로운 view를 self.view에 addSubview() 하게 되면 주의할 점이 생깁니다. 이 경우 superView 혹은 superLayer 경계에서 sub객체를 자를 것인지 말 것일지 정할 때, layer.maskToBounds를 쓸지 view.clipsToBounds를 쓸지 구분해야한다는 점입니다. layer가 곧 View는 아니지만 View에는 무조건 layer가 있습니다. view의 기본 layer로 이미지절단이 가능하면 clipsToBounds를 사용해도 무방하지만 그렇지 않은 경우에는 반드시 layer.maskToBounds를 사용해야합니다. 즉, 정리하면 View가 있는 layer에서 작업한다면 view.clipsToBounds를 사용해도 무방하지만 View가 없는 layer라면 layer.maskToBounds를 사용해야합니다.

profile
iOS developer

0개의 댓글