Danny의 iOS 컨닝페이퍼
article thumbnail

[TIL #1] 23 / 03 / 15 ~ 23 / 03 / 16

커스텀으로 탭바의 모양을 만드는 중에 CAShapeLayer라는 것이 갑자기 튀어나왔습니다.

코드를 복붙해서 적용하면 모양이 바뀌긴 하지만... 사용방법을 한번 알아봅시다.

CAShapeLayer를 사용하려면 UIBezierPath를 생성하는 방법도 알아야 하네요... 전부 알아보도록 합시다. ㅠㅠ

만들고 싶은 모양!

 

 

UIBezierPath

들어가기 앞서 CAShapeLayer는 모양을 그리는 클래스가 아니기 때문에,

도형을 만들기 위해서는 UIBezierPath를 사용해야 합니다.

 

UIBezierPath는 여러 직선 및 곡선을 사용하여 복잡한 형상을 렌더링 할 수 있는 클래스입니다.

(커스텀 뷰를 그릴 때 자주 사용 됩니다.)

 

공식문서 1, 공식문서 2 에도 사용방법이 자세히 나와있지만, 한번 정리해 보겠습니다.

  1. UIBezierPath 객체 생성
    • 초기 생성값으로 여러 가지가 존재합니다. (커스텀, 사각형, 원, 둥근 사각형 등)
  2. Path의 시작점을 설정
    • move(to:)
  3. 직선 또는 곡선 등 추가
    • addLine(선), addArc(호), addCurve(3차원 곡선), addQuadCurve(2차원 곡선)
  4. 경로 닫기
    • close()
    • 라인이 2개 이상 있는 경우 경로를 닫게 되면, 마지막 점과 시작점을 이어서 도형을 만들어준다. 
  5. 그리는 속성들을 처리
    • 굵기(lineWidth), 테두리(stroke), 채우기(fill) 등

 

 

순서는 이와 같으니 한번 사용해 봅시다.

UIBezierPath 사용하기 위해서는 새로운 뷰를 만들어 줘야 되더라고요. 새로운 뷰를 만들어 줬습니다.

아래 이미지의 회색 영역이 UIBezierPath를 이용하여 그릴 부분이에요.

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let customView = CustomView(frame: CGRect(x: 0,
                                            y: 100,
                                            width: self.view.frame.width,
                                            height: 650))
        customView.backgroundColor = .lightGray
        self.view.addSubview(customView)
    }
}
class CustomView: UIView {
    
    override func draw(_ rect: CGRect) {
    
    }
}


일단 CustomView에 기본적인 선부터 그려봅시다.

 

addLine을 통해 선을 생성할 수 있습니다.

여기서 끝이 아니라 그려진 선을 눈으로 확인하기 위해 선의 굵기색상을 지정해줘야 합니다.

override func draw(_ rect: CGRect) {
    // UIBezierPath 객체 생성
    let path = UIBezierPath()
    
    // 경로 시작점 설정 (현재 원점)
    path.move(to: CGPoint(x: 0, y: 0))
    
    // 직선을 추가
    path.addLine(to: CGPoint(x: 100, y: 400))
    
    // UIBezierPath의 drawing 속성 설정
    path.lineWidth = 5       // 테두리 선의 굵기
    UIColor.red.setStroke()  // 테투리 색상 설정
    path.stroke()            // 경로의 테투리 생성
}


만약, 여기서 라인을 하나 더 추가하게 된다면, 첫 번째 그려준 경로의 마지막 지점이 다음 추가할 선의 시작점이 됩니다.

코드로 본다면 다음 선을 추가할 때 시작지점은 위치는 축 (100, 400)이 되겠죠?

 

즉, 다른 말로 하면 다음 선을 그릴 때 선이 연결되어 그려집니다.

override func draw(_ rect: CGRect) {
    // UIBezierPath 객체 생성
    let path = UIBezierPath()
    
    // 경로 시작점 설정 (현재 원점)
    path.move(to: CGPoint(x: 0, y: 0))
    
    // 처음 그려준 선
    path.addLine(to: CGPoint(x: 100, y: 400))  // 1
    // 👉 위의 마지막 지점이 다음 선의 시작점으로 된다. (100, 400)
    path.addLine(to: CGPoint(x: 300, y: 400))  // 2
    
    // UIBezierPath의 drawing 속성 설정
    path.lineWidth = 5       // 테두리 선의 굵기
    UIColor.red.setStroke()  // 테투리 색상 설정
    path.stroke()            // 경로의 테투리 생성
}


선을 추가해 봤으니 경로를 닫아봅시다. (close)

 

위의 설명에서와 같이 2개 이상의 선이 존재할 때 close를 사용해 경로를 닫게 되면, 시작점과 마지막 점 사이를 이어줍니다.

 

또한, close 사용은 선택사항입니다.

굳이 도형이 필요 없을 때는 생략도 가능하고 직접 시작지점과 이어 만들어 사용도 가능.

override func draw(_ rect: CGRect) {
    // UIBezierPath 객체 생성
    let path = UIBezierPath()
    
    // 경로 시작점 설정 (현재 원점)
    path.move(to: CGPoint(x: 0, y: 0))
    
    // 처음 그려준 선
    path.addLine(to: CGPoint(x: 100, y: 400))  // 1
    // 위의 마지막 지점이 다음 선의 시작점으로 된다. (100, 400)
    path.addLine(to: CGPoint(x: 300, y: 400))  // 2
    
    // 👉 경로를 닫는다. (도형을 만들어준다)
    path.close()
    
    // UIBezierPath의 drawing 속성 설정
    path.lineWidth = 5       // 테두리 선의 굵기
    UIColor.red.setStroke()  // 테투리 색상 설정
    path.stroke()            // 경로의 테투리 생성
}


마지막으로 fill 메서드를 통해서 만들어준 도형의 내부의 색도 변환이 가능합니다.

 

만약, 선을 닫지(close) 않고 fill을 사용하게 된다면? 그래도 같은 모양으로 채워지네요.

아마 선을 두 개 이상 만들게 되면 자동으로 도형으로 렌더링이 되는 것 같은데...

override func draw(_ rect: CGRect) {
    // UIBezierPath 객체 생성
    let path = UIBezierPath()
    
    // 경로 시작점 설정 (현재 원점)
    path.move(to: CGPoint(x: 0, y: 0))
    
    // 처음 그려준 선
    path.addLine(to: CGPoint(x: 100, y: 400))  // 1
    // 위의 마지막 지점이 다음 선의 시작점으로 된다. (100, 400)
    path.addLine(to: CGPoint(x: 300, y: 400))  // 2
    
    // 경로를 닫는다. (도형을 만들어준다)
    path.close()
    
    // UIBezierPath의 drawing 속성 설정
    path.lineWidth = 5       // 테두리 선의 굵기
    
    UIColor.red.setStroke()  // 테투리 색상 설정
    path.stroke()            // 경로의 테투리 생성

    // 👉 채우기
    UIColor.blue.setFill()   // 채울 색상 설정
    path.fill()              // 이어진 공간의 색을 채워준다.
}


또, 잘 쓰진 않을 것 같은데, addClip 메서드도 있습니다.

설명은 새로운 클리핑 영역을 정의하고 이후 그려지는 콘텐츠교차 영역에 있는 것만 실제 렌더링 된다고 하는데?

 

역시 말로는 어렵네요. 직접 사용해 봅시다.

 

적용하기 위해서 UIBezierPath로 사각형 만들기도 같이 살펴봅시다.

사각형이나 원은 기본 이니셜라이저정의돼 있습니다. 

// 사각형
UIBezierPath(rect: CGRect)

// 타원, 원
// 사용해보니 사각형의 크기에 따라 원, 타원형이 정해진다.
UIBezierPath(ovalIn: CGRect)

 

정사각형을 만들어주고, Y축으로 100만큼 아래쪽에 원을 만들어 줬어요.

또한, 구분을 위해 색을 설정해 줬어요.

override func draw(_ rect: CGRect) {
    // 정사각형 생성
    let squarePath = UIBezierPath(rect: CGRect(x: 100, y: 200, width: 200, height: 200))
    // 원 생성
    let circlePath = UIBezierPath(ovalIn: CGRect(x: 100, y: 300, width: 200, height: 200))
    
    // UIBezierPath의 drawing 속성 설정
    squarePath.lineWidth = 5
    circlePath.lineWidth = 5
    
    UIColor.red.setStroke()
    squarePath.stroke()
    
    UIColor.blue.setStroke()
    circlePath.stroke()
}


이제 addClip을 사용해 볼까요?

정사각형을 기준으로 addClip을 해주니 정사각형 안쪽 부분만 렌더링이 되네요!!!

override func draw(_ rect: CGRect) {

    let squarePath = UIBezierPath(rect: CGRect(x: 100, y: 200, width: 200, height: 200))
    
    squarePath.addClip()  // 👉 사각형을 기준으로 addClip 사용
    
    let circlePath = UIBezierPath(ovalIn: CGRect(x: 100, y: 300, width: 200, height: 200))

    // UIBezierPath의 drawing 속성 설정
    squarePath.lineWidth = 5
    circlePath.lineWidth = 5
    
    UIColor.red.setStroke()
    squarePath.stroke()
    
    UIColor.blue.setStroke()
    circlePath.stroke()
}


그러면 한번 원을 기준으로 addClip 해보겠습니다.

여기도 마찬가지로 원을 기준으로 안쪽만 렌더링이 됩니다.

override func draw(_ rect: CGRect) {

    let squarePath = UIBezierPath(rect: CGRect(x: 100, y: 200, width: 200, height: 200))
    
    let circlePath = UIBezierPath(ovalIn: CGRect(x: 100, y: 300, width: 200, height: 200))
        
    circlePath.addClip()  // 👉 원을 기준으로 addClip 사용

    // UIBezierPath의 drawing 속성 설정
    squarePath.lineWidth = 5
    circlePath.lineWidth = 5
    
    UIColor.red.setStroke()
    squarePath.stroke()
    
    UIColor.blue.setStroke()
    circlePath.stroke()
}

 

그런데 이상하게 addClip를 사용한 도형은 선 굵기가 줄어드네요. (일단 패스)

 

설명만으로는 어려웠지만 예제를 보면서 확실히 의미를 이해할 수 있었네요.

 

마무리로 간단히 addClip을 정리해보면,

UIBezierPath의 객체가 2개 이상일 때 addClip 한 path를 기준으로 나머지 path를 자르는 기능입니다.

 

 

탭바 모양을 구현해 보자

자 기본 맛보기를 해봤으니, 만드려고 했던 탭바 모양을 구현해 봅시다.

 

일단은 반원이 필요하고 직사각형이 필요하니까... 흠...

먼저 애플이 UIBezierPath의 기본 라운드 도형을 제공하고 있는지 이니셜라이져를 확인해 봅시다.

 

이름이 roundedRect 이게 알약모양을 만들어주는 거 같네요. (이 도형 이름이 뭐더라?)

두 가지 방법이 있는데, 일단 모두 모서리를 둥글게 만들어주는 거고 문서를 읽어보니... 이렇다고 하네요.

UIBezierPath(roundedRect: CGRect, cornerRadius: CGFloat)
// roundedRect: 기본 모양의 정의하는 사각형을 만들어준다. (일종의 틀)
// cornerRadius: 각 모서리의 둥근 정도를 설정합니다.(CGFloat)

UIBezierPath(roundedRect: CGRect, byRoundingCorners: UIRectCorner, cornerRadii: CGSize)
// roundedRect: 기본 모양의 정의하는 사각형을 만들어준다. (일종의 틀)
// byRoundingCorners: 라운드를 주고싶은 꼭지점을 선택할 수 있다. 
//                    (UIRectCorner에는 상, 하단 꼭지점 및 전체 꼭지점 선택가능)
// cornerRadii: CGSize로 모서리의 둥근 정도를 설정합니다. 
//              (원리는 이해가 안가지만 몇 테스트 결과 CGSize의 width만 변경해줘도 값에 알맞게 변경됨)

 

두 가지 방법 모두 사용해 봅시다. 

확실히 기본 사용방법을 배우고 사용하니 뭔가 뚝딱 만들어 버렸네요.

override func draw(_ rect: CGRect) {
    // cornerRadius를 높이의 반으로 줘서 알약 모양을 만듬
    let path = UIBezierPath(roundedRect: CGRect(x: 50, y: 200, width: 300, height: 100),
                            cornerRadius: CGFloat(50))
                            
    path.lineWidth = 5
    UIColor.red.setStroke()
    path.stroke()
}
override func draw(_ rect: CGRect) {
    // 라운드를 줄 때, 왜CGSize를 쓰는지 모르겠지만... width에 50을 줘서 얄약 형태를 만듬
    let path = UIBezierPath(roundedRect: CGRect(x: 50, y: 200, width: 300, height: 100),
                            byRoundingCorners: .allCorners,
                            cornerRadii: CGSize(width: 50, height: 50))
    
    path.lineWidth = 5
    UIColor.red.setStroke()
    path.stroke()
}


두 번째 방법에서 (byRoundingCorners: UIRectCorner)를 통해 선택한 모서리라운드를 줄 수 있습니다.

 

UIRectCorner 속성으로 topLeft, topRight, bottomLeft, bottomRight, allCorners가 있습니다.

라운드를 주고 싶은 꼭짓점을 배열로 묶어서 사용할 수 있습니다.

override func draw(_ rect: CGRect) {

    let path = UIBezierPath(roundedRect: CGRect(x: 50, y: 200, width: 300, height: 100),
                            // 👉 원하는 꼭지점을 배열로 묶어서 선택 가능
                            byRoundingCorners: [.topLeft, .bottomRight],
                            cornerRadii: CGSize(width: 50, height: 50))
    
    path.lineWidth = 5
    UIColor.red.setStroke()
    path.stroke()
}

 

 

CAShapeLayer

CAShapeLayerCALayer를 상속하고 있고 Core Animation 프레임워크에서 제공하는 클래스입니다.

도형을 그리고 애니메이션 효과를 주는 레이어라고 합니다.

 

그럼 간단히 Core Animation에 대해 알아봅시다.

자세한 내용은 공식문서 에서 확인하길...

 

시각적 요소를 렌더링, 구성 및 애니메이션 효과를 줄 수 있습니다.

 

UIView는 이런 구조를 갖고 있습니다.

UIView는 하나의  CALayer(root)만 갖고 있고,

CALayer(root)는 여러 개의 서브 Layer를 둘 수 있다고 하네요.

 

Core Animation의 특징으로

  1. GPU를 사용 (높은 성능)
    • GPU를 사용하므로 렌더링 하므로, 뷰의 변형 및 애니메이션 및 그래픽 렌더링 처리가 빠르다.
    • UIVIew의 경우, 렌더링 시 CPU(메인 스레드)를 사용하기 때문에 뷰의 구조가 복잡해질수록 처리에 많은 시간과 리소스가 들어간다.
      반면, Core Animation는 GPU를 활용하여 렌더링 하기 때문에 CPU의 부하를 줄일 수 있다. (성능 UP)
  2. Layer기반 인터페이스
    • 모든 콘텐츠는 CALayer 객체로 구성돼 있다. 애니메이션 효과를 레이어 단위(SubLayer 까지)로 적용가능.
  3. 애니메이션 처리가 간단하다
    • 다양한 애니메이션 효과를 쉽게? 적용가능하고 지속적으로 반복적으로 보여 줄 수 있다.

 

대략적인 특징을 알아봤으니, 이제 위에 만든 알약 모양을 만들어 봅시다.

 

사용방법은 UIBezierPath을 만드는 것과 비슷하네요.

class CustomView: UIView {
    
    override func draw(_ rect: CGRect) {
        // UIBezierPath 객체 생성
        let path = UIBezierPath(roundedRect: CGRect(x: 50, y: 200, width: 300, height: 100),
                                cornerRadius: CGFloat(50))
        // CAShapeLayer 객체 생성
        let layer = CAShapeLayer()
        layer.path = path.cgPath    // CGPath(Core Graphics) 타입으로 전달

        // 그리기 속성 설정
        layer.lineWidth = 5
        layer.strokeColor = UIColor.red.cgColor
        layer.fillColor = UIColor.blue.cgColor
        
        // View의 서브 Layer에 shapeLayer를 추가
        self.layer.addSublayer(layer)
    }
}


CALayer를 사용해 봤는데, 그럼 그렇게 성능이 좋다는 애니메이션 효과를 사용해 봐야겠죠?

 

찾아보니 옛날 공식문서 는 엄청 이론적인 내용들이 많고, 신규 공식문서 는 사용방법이 나와있네요!

애니메이션의 종류마다 사용방법이 정리가 돼있어, 쉽게 참고하려고 링크를 다 남기겠습니다.

더 자세한 내용은 공식문서 를 확인해 보세요.

그리고 내용 정리를 잘해주신 YungSoZedd 님의 블로그도 참고해 보세요.

 

보통의 경우엔 CABasicAnimation만 사용하면 될 것 같네요.

한번 공식 문서를 참고하면서 만들어 봅시다.

 

처음 애니메이션 객체를 만드는 부분부터 막혔습니다...

keyPath 값은 도대체 뭐고, 뭐 이리 종류는 많은지...  String 대신 쓰기 쉽게 enum으로 쫌 묶어주지...

CABasicAnimation(keyPath: String)

 

찾아보니 애니메이션 관련 keyPath의 종류가 엄청 많더라고요.

사용방법은 공식문서 1(CALayer 속성)  , 공식문서 2(하위 속성 접근, extenstion) 참고하세요.

(공식문서 1(CALayer)의 transform, position, bounds는 공식문서 2의 하위 속성에 접근이 가능합니다.)

keyPath 종류는 아래 더 보기 또는 출처 에서 확인하세요.

더보기

 

CALayer Animatable layer properties:

anchorPoint
backgroundColor
backgroundFilters
borderColor
borderWidth
bounds
compositingFilter
contents
contentsRect
cornerRadius
doubleSided
filters
frame
hidden
mask
masksToBounds
opacity
position
shadowColor
shadowOffset
shadowOpacity
shadowPath
shadowRadius
sublayers
sublayerTransform
transform
zPosition

 

CAEmitterLayer animatable properties:

emitterPosition
emitterZPosition
emitterSize

 

CAGradientLayer animatable properties:

colors
locations
endPoint
startPoint

 

CAReplicatorLayer animatable properties:

instanceDelay
instanceTransform
instanceRedOffset
instanceGreenOffset
instanceBlueOffset
instanceAlphaOffset

 

CAShapeLayer animatable properties:

fillColor
lineDashPhase
lineWidth
miterLimit
strokeColor
strokeStart
strokeEnd

 

CATextLayer animatable properties:

fontSize
foregroundColor

 

CATransform3D Key-Value Coding Extensions:

CALayer의 transform은 하위 속성의 접근이 가능하다. ex) transform.rotation.x

rotation.x
rotation.y
rotation.z
rotation
scale.x
scale.y
scale.z
scale
translation.x
translation.y
translation.z

 

CGPoint keyPaths:

CALayer의 transform은 하위 속성의 접근이 가능하다. ex) position.x

x
y

 

CGSize keyPaths:

width
height

 

CGRect keyPaths:

CALayer의 bounds은 하위 속성의 접근이 가능하다. ex) bounds.origin.x

origin
origin.x
origin.y
size
size.width
size.height

 

이제 사용하자고 하는 keyPath 값을 넣고 애니메이션 기능을 사용하면 됩니다.

 

그럼 한번 Y축으로 움직이는 keyPath를 사용해 볼까요?

// y축을 따라 이동하는 애니메이션 생성 
let animation = CABasicAnimation(keyPath: "position.y")

 

먼저, 애니메이션 효과를 주기 위해 변경시키는 값을 알아야 합니다.

(예를 들어 위치를 옮기기, 회전, 색 변경 등 변경하기 위해선 변경될 값을 알아야겠죠?)

 

값을 변경하는 데 사용하는 속성은 3가지가 있습니다.

// 애니메이션 속성
var fromValue: Any?  // 애니메이션의 시작 값
var toValue: Any?    // 애니메이션의 종료 값
var byValue: Any?    // 애니메이션의 상대적인 값

위의 속성들은 모두 옵셔널 값이고 2개 이상은 사용해야 한다고 합니다.

(한 개만 사용해도 되지만, 시작 순서의 혼동이 올 수 있으므로 명시적으로 써줍시다.)

 

속성의 각 조합마다 움직임을 표현해 봤습니다.

(참고. 공식문서 의 맨 마지막 부분)

 

그럼 가장 쉬운 fromValuetoValue를 통해서 이동을 시켜봅시다.

(그림으로 생각해 보면 시작 값(from)에서 종료 값(to)으로 이동하겠죠?)

 

원점에서 200만큼 이동을 시켜주고,

만들어준 애니메이션을 add 메서드를 통해 추가해 주면 애니메이션 생성 완료!

// y축을 따라 이동하는 애니메이션 생성 
let animation = CABasicAnimation(keyPath: "position.y")

animation.fromValue = 0   // 원점 (현재 도형의 위치)
animation.toValue = 200   // Y축으로 200만큼 이동

// 애니메이션 추가
layer.add(animation, forKey: nil)

이렇게 설정해 주면 순식간에 내려갔다 다시 처음지점으로 돌아오네요. (너무 빨라요...)


찾아보니 duration라는 속성이 있네요. 지속 속성의 값을 변경해 준다면?

 

설정한 시간만큼 애니메이션이 동작하게 됩니다.

하지만 여전히 애니메이션이 끝나면 처음 지점으로 돌아가네요...

// y축을 따라 이동하는 애니메이션 생성 
let animation = CABasicAnimation(keyPath: "position.y")

animation.fromValue = 0   // 원점 (현재 도형의 위치)
animation.toValue = 200   // Y축으로 200만큼 이동
animation.duration = 3.0  // 3초 동안 지속

// 애니메이션 추가
layer.add(animation, forKey: nil)

 


애니메이션이 끝나면 다시 원점으로 가는 것도 방지해 봅시다.

fillModeisRemovedOnCompletion를 사용해서 애니메이션 종료 지점의 상태를 유지할 수 있습니다.

 

간단히 설명하면 fillMode는 애니메이션의 시작과 끝이 어떻게 보일지 설정하는 것입니다. 

 

공식문서 에서는 Fill Mode의 종류를 이와 같이 나타냅니다.

  • removed : 애니메이션이 종료되면 사라집니다.
  • forwards : 애니메이션이 종료되면 종료시점 프레임 상태로 유지된다.
  • backwards : 애니메이션이 종료되면 시작 상태의 프레임 상태로 유지됩니다.
  • both : forwards와 backwards를 합친 효과

YungSo 이 분이 그림과 함께 잘 설명해 주셨어요. 필요하면 참고하시길...

 

또한, isRemovedOnCompletion는 애니메이션이 끝나고 사라질지 여부를 나타냅니다. (기본값은 true)

 

fillMode, isRemovedOnCompletion를 사용해 봅시다!

// 애니메이션 생성
let animation = CABasicAnimation(keyPath: "position.y")
animation.fromValue = 0                  // 원점
animation.toValue = 200                  // Y축으로 200만큼 이동
animation.duration = 3.0                 // 3초 동안 지속
animation.fillMode = .forwards           // forwards(종료 지점의 프레임 사용)
animation.isRemovedOnCompletion = false  // 애니메이션이 종료되더라도 fillMode에서 지정한 프레임으로 유지

// 애니메이션 추가
layer.add(animation, forKey: nil)

마지막 지점에서 이미지가 고정이 되네요.

 

 

마무리

프로젝트를 만드는 도중 탭바 모양 한번 바꾸려고 했다가 생전 처음 보는 Core Animation까지 만져 보았습니다. 

라이브러리를 쓰면 간편하게 만들 수 있겠지만, 그래도 한번 내 손으로 구현해보고 싶다는 생각에 공부를 시작을 했는데요.

반나절이면 공부를 마칠 줄 알았는데, 문서가 너무 어렵고... 정보들을 찾느냐고 이틀이나 걸렸네요.

 

원하는 모양을 그리기 위해서 처음 보는 프레임워크, 클래스, 메서드, 속성, 용어들이 많이 나왔는데요...

모양을 그릴 땐 UIBezierPath만 다룰 줄 알면 CALayer의 모양은 다 구현할 수 있더라고요.

다만, 애니메이션을 만들기 위해서는 사용할 애니메이션을 직접 찾아서 키패스의 String 값으로 전달해야 되므로 까다로웠고,

Core Animation 프레임워크의 양이 너무 방대해서 자료를 찾아서 사용하기가 생각보다 힘들었네요. ㅎ

 

그리고 개발자 소들이님 블로그 를 보면 그라디언트 색상을 CABasicAnimation을 통해 변하게 하던데, 나중에 저 기능도 사용해 봐야겠습니다.

 

TIL을 이렇게 쓰는 게 아닌 것 같은데... 이미 작성했으니 ㅎ

아무튼 CAShapeLayer에 대하여 배웠으니 아마 다음 글은 배운 걸 이용해 탭바에 적용해볼 거 같습니다.

 

의식의 흐름대로 쓰다 보니 글이 두서가 없긴 한데, 읽는 분들에게 도움이 됐으면 합니다.

 

다음 내용은 [Swift/TIL #2] CAShapeLayer 이용한 탭바 구현 입니다.

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!