[iOS] UIBezierPath, CAShapeLayer로 꽃 그리기
UIBezierPath와 CAShapeLayer를 알아보고 간단한 꽃을 한번 그려보도록 하겠음
지디가 생각나는 데이지 꽃을 그려보도록 하겠음
UIBezierPath
UIBezierPath는 한마디로 말하면 경로를 그리는 거임
이 경로라는 것은 직선, 곡선을 사용해서 직사각형, 타원 등등 그리고 싶은 걸 만들 수 있다는 걸 의미함
이걸 사용해서 저 꽃잎부터 그려보도록 하겠음. 가장 쉬운 방법은 타원을 여러개 붙이는 것임
그래서 타원(oval)으로 패스를 생성해보겠음. 타원을 그릴때 인자가 CGRect인데 저 사각형 안에 꽉차게 원을 그리겠다는 것임
넙적한 꽃잎모양이 되도록 90x30으로 만들어줌
UIColor.white.setFill()을 통해서 현재 패스의 배경을 설정해줌. 색칠 하기 전에 물감 뭍히는 느낌이라고 생각하면 됨
꽃잎은 하얀색으로 설정하도록 하겠음
let center = CGPoint(x: rect.midX, y: 150)
let radius = 30.0
let length = 90.0
let petalPath = UIBezierPath(
ovalIn: CGRect(
x: center.x,
y: center.y - radius / 2,
width: length,
height: radius
)
)
UIColor.white.setFill()
꽃잎은 360도로 감싸고 있으니까 총 각도를 2 * .pi로 정의하겠음 (.pi = 180도 의미)
3시방향부터 진행되기 때문에 -90도 (.pi / 2)를 해주면 12시방향부터 진행되도록 할 수 있음
for문으로 9번을 돌면서 각도를 바꿔가면서 그려줄 것임
근데 우리가 연달아서 계속 그리고 각도를 바꿔주고 있지 않음?
이 연다른 동작을 하기 위해 UIGraphicsGetCurrentContext로 현재 그래픽 컨텍스트를 가져와주도록 하겠음
그리기 순서는 아래와 같음
1. 그리기 전에 상태를 저장
2. 중심 기준으로 해당하는 각도만큼 돌리기
3. 이 돌아간 꽃잎을 다시 원래 자리에 갖다 놓기
4. UIBezierPath.fill()로 색 채우기
let totalAngle = 2 * CGFloat.pi
guard let context = UIGraphicsGetCurrentContext() else { return }
for i in 0..<count {
// 3시 방향부터 그려지기 때문에 12시부터 그려지도록 바꿈
let angle = CGFloat(i) * (totalAngle / CGFloat(count)) - (.pi / 2)
// 도형 뒤에 그릴 내용이 있으면,
// 변환을 변경하기 전에 현재 상태를 저장
context.saveGState()
// 뷰의 원점을 조정
context.translateBy(x: center.x, y: center.y)
context.rotate(by: angle)
context.translateBy(x: -center.x, y: -center.y)
// 회전된 상태로 꽃잎을 그림
petalPath.fill()
// 다른 컨텐츠를 그리기 전 복원
context.restoreGState()
}
이런 형태가 되었음. 이제 가운데에 노란 원을 하나 그리면 오른쪽처럼 꽃을 완성할 수 있어짐
원그리는 함수는 뭘까하고 찾아보면 addArc라는 호를 그리는 함수가 나옴
이 호를 360도로 그려서 원을 만들어 줄 수 있음
let path = UIBezierPath()
path.addArc(
withCenter: center,
radius: 30,
startAngle: 0,
endAngle: .pi * 2,
clockwise: true
)
UIColor.systemYellow.setFill()
// 현재 그래픽 컨텍스에 색을 채움
path.fill()
* 번외로 공유하고 싶은 부분들은 아래에 표시함
다 그리고 나니까 뭔가 심심해서 애니메이션을 주고 싶어짐
그래서 사용하게 된게 CAShapeLayer임. 이걸 사용해서 패스를 이 레이어에 설정한 다음 이 레이어에 애니메이션을 넣어줄 것임
CALayer를 상속받는 이 CAShapeLayer는 주로 도형을 표시할 때 사용함. GradientLayer같은 다른 종류의 레이어도 존재함
우리가 뷰에 기본적으로 레이어가 있고, 이 레이어 위에 서브레이어가 여러개가 올라갈 수도 있음
CAShapeLayer
CAShapeLayer와 CABasicAnimation를 사용하는 것이 메인임
아까는 연달아서 컨텍스트를 저장해가며 그렸다면,
지금은 각각의 꽃잎을 레이어로 만들고 이 레이어에 투명도를 조절하는 애니메이션을 달 것임
1. UIBezierPath로 타원을 만듬
2. 이 UIBezierPath를 CAShapeLayer 패스로 설정
3. CGAffineTransform(2차원 좌표변환)을 사용해서 각도만큼 꽃잎 돌리기
4. 레이어에 CABasicAnimation을 적용
func drawPetalsWithLayer(_ rect: CGRect, _ count: Int) {
let center = CGPoint(x: rect.midX, y: 500)
let radius = 30.0
let length = 90.0
let totalAngle = 2 * CGFloat.pi
for i in 0..<count {
let angle = CGFloat(i) * (totalAngle / CGFloat(count)) - (.pi / 2)
let petalLayer = CAShapeLayer()
let petalPath = UIBezierPath(
ovalIn: CGRect(
x: 0,
y: -radius / 2,
width: length,
height: radius
)
)
petalLayer.path = petalPath.cgPath
petalLayer.fillColor = UIColor.white.cgColor
petalLayer.position = center
petalLayer.setAffineTransform(CGAffineTransform(rotationAngle: angle))
petalLayer.opacity = 0
layer.addSublayer(petalLayer)
// 애니메이션
let appear = CABasicAnimation(keyPath: "opacity")
appear.fromValue = 0
appear.toValue = 1
appear.duration = 0.3
appear.beginTime = CACurrentMediaTime() + Double(i) * 0.3 // 순차적으로
appear.fillMode = .forwards
appear.isRemovedOnCompletion = false
petalLayer.add(appear, forKey: "fadeIn")
}
// 가운데 노란 원 추가
let centerLayer = CAShapeLayer()
let centerPath = UIBezierPath(
arcCenter: .zero,
radius: 30,
startAngle: 0,
endAngle: .pi * 2,
clockwise: true
)
centerLayer.path = centerPath.cgPath
centerLayer.fillColor = UIColor.systemYellow.cgColor
centerLayer.position = center
centerLayer.opacity = 0
layer.addSublayer(centerLayer)
// 가운데 원 그리기
let centerAppear = CABasicAnimation(keyPath: "opacity")
centerAppear.fromValue = 0
centerAppear.toValue = 1
centerAppear.duration = 0.3
centerAppear.beginTime = CACurrentMediaTime() + Double(count) * 0.3
centerAppear.fillMode = .forwards
centerAppear.isRemovedOnCompletion = false
centerLayer.add(centerAppear, forKey: "fadeIn")
}
결과물
참고자료