안녕하세요! 주니오스 입니다. ✋✋✋
최근에 저는 개인프로젝트를 진행하고 있습니다. 탭뷰를 사용하여 네비게이팅을 하려고합니다.
하지만 단순하고 각진 기본 탭뷰에 만족을하지 못했던 저는 단조로운 직선에 곡률을 더하고자 하였습니다.😎
하지만 이 과정이 생각보다 순탄하지 않았던 터라 포스팅을 통해 정리하기 위해 글을 적습니다. 😅
우선 최종적으로 완성한 저의 탭뷰를 먼저 보고 출발해봅시다~! 🤗
탭뷰의 바가 살짝휘어져 있는 것을 확인할 수 있습니다.
그리고 특정 탭 아이템을 누를 때 휘어져있는 탭바 위로 움직이는 막대가 있습니다. 🤗
위 뷰에서 사용된 곡선을 만들기위해 저는 Shape프로토콜을 사용하였습니다.
Path인스턴스는 addArc라는 매서드를 가지는데 해당 매서드를 사용하여 구현하였습니다.
Path.addArc
addArc매서드는 2가지 오버로딩이 있습니다.
그전에 SwiftUI뷰의 좌표공간과 원점을 이해하여야 합니다.
아래 그림을 통해 설명하겠습니다.
기본적으로 SwiftUI의 뷰들은 위 좌표공간을 따릅니다.
정확히는 각도증가에 따른 회전 방향과 X, Y좌표값 증가방향이 위 좌표공간과 일치합니다.
위 그림을 보시면 시계방향(clock wise)이 현실과 다른 것을 알 수 있스니 주의해야 합니다.
회전각의 시작은 Y = 0 그패프 입니다.
다음으로는 원점에 대해 인지하여야 합니다.
SwiftUI의 뷰의 원점은 2가지가 있습니다.
- 부모에 의해 인식되는 자식뷰의 원점
- 자식뷰에게 전달되는 부모 좌표공간의 원점
예를들어 아래코드를 보겠습니다.
GeometryReader { geo in
Rectangle()
.fill(.purple)
.frame(width: 150, height: 150)
.position(x: geo.size.width/2, y: geo.size.height/2)
}
.frame(width: 300, height: 300)
.border(.black)
GeometryReader는 Rectangle뷰의 부모로써 왼쪽 상단을 (0, 0)으로 하는 좌표공간을 자식뷰에게 제공합니다.
position수정자를 사용하여 자식뷰에게 부모 좌표공간에 중심을 전달하였습니다.
이때 부모뷰의 정확히 중심에 자식뷰가 위치되는 것을 볼 수 있습니다.
부모뷰는 자식뷰의 중심을 기준으로 자식뷰를 이동시킵니다.
자, 그럼 addArc매서드에 대해 알아보겠습니다.
addArc(withCenter:radius:startAngle:endAngle:clockwise:)
위 매서드는 특정 좌표를 기준으로 시작Angle부터 끝Angle까지 원하는 방향으로 곡선을 그리는 매서드입니다.
Shape프로토콜의 경우 path라는 매서드를 요구합니다.
해당 매서드는 Shape가 확보가능한 공간(CGRect)을 전달받게 됩니다.
여기서 해당 공간은 그려지는 모양의 부모뷰로 인식됩니다.
따라서 좌표공간이 왼쪽 상단에서 시작됩니다.
struct SomeShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
return path
}
}
그럼 전달받은 공간(CGRect)의 중앙에 그릴수 있는 가장큰 원을 그려보겠습니다.
Angle타입의 경우 라디안 혹은 각도를 전달할 수 있도록 구현되어 있습니다. 아래는 각도를 사용하였습니다.
struct SomeShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let centerOfCircle = CGPoint(x: rect.midX, y: rect.midY)
path.addArc(center: centerOfCircle,
radius: rect.maxX/2,
startAngle: .degrees(0),
endAngle: .degrees(-270),
clockwise: true)
path.addLine(to: centerOfCircle)
return path
}
}
addArc(tangent1End:tangent2End:radius:transform:)
해당 매서드의 tanget1End, tangent2End 매개변수는 CGPoint값을 전달받습니다.
아래 그림은 해당 위치가 어떤의미를 가지는지 보여줍니다.
start좌표는 addArc사용전 마지막 path의 위치입니다.
3개의 점을 이은 직선에 접하는 원을 그리게 됩니다.
해당원과 직선의 접저은 start좌표와 tanget2End좌표와 일치하게 됩니다.
이매서드를 사용하면 강력한 장점이 있습니다.
바로 곡선이 차지하는 영역을 지정할 수 있다는 겁니다.
저는 이 매서드를 사용하여 탭뷰를 구현하였는데요 설계는 다음과 같습니다.
A값은 곡선이 차지하는 높이를 의미합니다.
B는 tangent1End의 Y좌표 절대값을 의미합니다.
삼각형의 닮음을 이용하여 B값을 쉽게 구할 수 있습니다.
그리고 그림에 보이는 큰 원의 반지름은 start - tangent2End선분의 길이와 A값을 이용하여 구할 수 있습니다.
주니오스의 커스텀 탭바 세부설명
제가 만든 탭바를 보시면 탭바 아이템들이 오른쪽 왼쪽으로 조금씩 돌아가 있는 것을 확인할 수 있습니다.
저는 이것을 구현하기 위해 아래와 같은 방법을 사용했습니다.
위 그림처럼 각각이 다른 5개의 뷰를 ZStack으로 합쳐 구현하였습니다.
아래는 해당 뷰의 코드입니다.
각각의 뷰를 index값에 따라 일정하게 회전(rotationEffect)시켜 탭바를 구성했습니다.
ZStack {
ForEach(0..<5) { index in
ZStack {
RoundedTopRectanglePart(...)
.foregroundColor(colors[index])
Text("\(index)")
}
✋✋✋✋✋
.rotationEffect(.degrees(degreeOfPart * CGFloat(index-2)), ...)
.onTapGesture {
print(index)
}
}
}
.frame(height: 50)
.clipped()
이렇게 하면 index에 따라 탭바 아이템에 표시되는 텍스트도 함께 회전하여 원하는 모습을 만들 수 있습니다.
제가만든 탭뷰의 전체코드는 아래와 같습니다.
이상으로 글을 마치겠습니다. 혹시 궁금한 사항은 댓글로 남겨주세요! ✋✋✋
'SwiftUI' 카테고리의 다른 글
[SwifUI] IgnoreSafeArea수정자 정확하게 이해하기 (2) | 2023.11.28 |
---|---|
[SwiftUI] 뷰(View)가 여러개인 경우 효율적인 애니메이션 스케쥴링 방법 (0) | 2023.08.03 |
[SwiftUI] Transition사용법 심층정리 - 주니오스의 iOS어드벤쳐 (0) | 2023.06.27 |
[SwiftUI] UIViewRepresentable사용 (0) | 2023.06.22 |