본문 바로가기

iOS

[CoreAnimation] CAReplicatorLayer 알아보기

CAReplicatorLayer

위치, 색깔, 시간 등의 transformation 룰을 지키면서 하나의 레이어를 복제해서 복잡한 레이아웃을 구성해주는 객체입니다.

let replicatorLayer = CAReplicatorLayer()
    
let redSquare = CALayer()
redSquare.backgroundColor = NSColor.white.cgColor
redSquare.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    
let instanceCount = 5
    
replicatorLayer.instanceCount = instanceCount
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(60, 10, 0)
    
let offsetStep = -1 / Float(instanceCount)
replicatorLayer.instanceBlueOffset = offsetStep
replicatorLayer.instanceGreenOffset = offsetStep
   
replicatorLayer.addSublayer(redSquare)

빨간색 사각형 레이어를 5개 복사하는 리플리케이트 레이어에 추가했습니다. 각각의 복제된 레이어의 위치는 CATransform3DMakeTranslation을 이용해서 지정해줍니다. 이전의 인스턴스 위치를 기준으로 다음 인스턴스 위치를 정해줍니다. Blue와 Green의 오프셋을 조절해서 마지막엔 0이 될 수 있도록 합니다.

리플리케이트 레이어는 중첩될 수 있습니다.

let outerReplicatorLayer = CAReplicatorLayer()
​
outerReplicatorLayer.addSublayer(replicatorLayer)
​
outerReplicatorLayer.instanceCount = instanceCount
outerReplicatorLayer.instanceTransform = CATransform3DMakeTranslation(0, 60, 0)
outerReplicatorLayer.instanceRedOffset = offsetStep
​
view.layer.addSublayer(outerReplicatorLayer)

여기서 실험을 하나 해봤는데, CATransform3DMakeRotation을 적용하기 위해서는 원이 되는 중앙이 필요합니다. 이 원 중심을 어떻게 표시할까요?

let instanceCount = 5
replicatorLayer.frame = .init(x: 100, y: 400, width: 100, height: 100)
replicatorLayer.instanceCount = instanceCount
replicatorLayer.borderColor = UIColor.black.cgColor
replicatorLayer.borderWidth = 1
let angle = (2.0 * M_PI) / Double(replicatorLayer.instanceCount)
replicatorLayer.instanceTransform = CATransform3DMakeRotation(angle, 0, 0, 1)

원 중심은 리플리케이션 레이어의 프레임으로 잡아줄 수 있습니다. 아래 검은색 테두리가 리플리케이션 레이어입니다. 그리고 그 중심이 원의 중심이 됩니다. 이 중심을 기준으로 그려집니다.

만약 리플리케이션 레이어의 프레임을 잡아주지 않는다면 이 레이어를 가지고 있는 뷰의 origin이 원의 중심이 됩니다. 원하는 그림을 그리기 위해선 꼭 잡아주어야 합니다. 위 레이어는 리플리케이션의 프레임을 벗어나서 그림이 그려지고 있습니다. 만약 전부 내부에 위치하고 싶다면 빨간색 사각형의 레이어 프레임을 조절해야 합니다. 그런데 이 사각형 프레임을 조절하는것이 조금 까다롭습니다. 먼저 보기 쉽게 색상을 전부 빨강으로 바꾸고 가장 기본적으로 (0,0)으로 설정해보겠습니다.

let redSquare = CALayer()
redSquare.backgroundColor = UIColor.red.cgColor
redSquare.frame = CGRect(x: 0, y: 0, width: 25, height: 25)

첫번째 사각형의 시작이 리플리케이션 레이어의 왼쪽 상단에 붙어있습니다. 그리고 이를 시작으로 각도를 돌려가면서 원을 그리고 있습니다. 그렇다면 현재 그리고 있는 원의 지름은 (리플리케이션 레이어의 대각선 길이 - 사각형의 대각선 길이) / 2가 됩니다. 모든 사각형이 리플리케이션 레이어의 내부에 들어오게 하기 위해서, 사각형을 오른쪽 중앙부터 원을 그려보았습니다.

위와 같이 그려지는데 사각형의 모서리가 리플리케이션 레이어의 밖을 나가게 됩니다. 여기서, 사각형의 끝점이 그리는 원이 리플리케이션레이어의 내부 꽉차게 들어오면 된다는 사실을 알 수 있습니다. 따라서 원의 지름은 (리플리케이션 레이어의 한 변의 길이 - 사각형의 한 변의 길이)가 됩니다. 그렇다면 시작하는 사각형의 지점을 정할 수 있습니다. 이때, 여기서 사각형이기 때문에 이 계산이 조금 복잡해지는데 저것을 사각형이 아니라 원으로 바꿔보겠습니다

let replicatorLayer = CAReplicatorLayer()
let replicatorLayerWidth = 100
let replicatorLayerHeight = 100
let instanceCount = 5
let redSqureLength = 25
let redSquare = CALayer()
redSquare.backgroundColor = UIColor.red.cgColor
redSquare.cornerRadius = 12.5
redSquare.frame = CGRect(
   x: (replicatorLayerWidth - redSqureLength),
   y: replicatorLayerHeight / 2 - redSqureLength / 2,
   width: redSqureLength,
   height: redSqureLength
)
replicatorLayer.frame = .init(
   x: 100,
   y: 400,
   width: replicatorLayerWidth,
   height: replicatorLayerHeight
)

모두 내부에 들어온 것을 확인할 수 있습니다. 하지만 제가 의도한 방식대로 흘러가지는 않았습니다. 꽉 찬 원이 그려지지 않는 이유는 무엇일까요? CATransform3DMakeRotation를 조금 더 깊게 알아봐야 할 것 같습니다.

 

리플리케이션 레이어에서 instanceDelay 프로퍼티가 조금 헷갈릴 수 있습니다. 

instanceDelay는 기본값은 0이고 모든 복제된 인스턴스들은 동기화됩니다. 즉, 한번에 표시됩니다. 하지만 0이 아닌 값이라면 복제된 인스턴스들이 시간을 두어 복제됩니다. (여기서 복제된다고 표현했는데 언제복제되는지는 모릅니다. 미리 복제되어져있고 시간에 맞춰서 보여지는 것일 수 있습니다.)

replicator.instanceDelay = 1 / dotNumber

 

 

레퍼런스 

https://developer.apple.com/documentation/quartzcore/careplicatorlayer

 

Apple Developer Documentation

 

developer.apple.com

https://developer.apple.com/documentation/quartzcore/careplicatorlayer/1522391-instancedelay

 

Apple Developer Documentation

 

developer.apple.com