Path
와 Shape
에 대해 알아보도록 합시다.
Path
SwiftUI는 사용자가 원하는 Custom Shape
를 그릴 수 있도록 Path
를 제공하고 있습니다.
Color
, grandient
및 shape
와 마찬가지로 Path
는 그 자체로 View
입니다. 이것은 우리가 TextField
와 Image
처럼 사용할 수 있다는 소리죠.
Paths
는 위치 값을 가진 선, 곡선 및 기타 정보를 가진 목록입니다. 하지만 Shape
는 다른 정보를 미리 알 수 없죠. Shape
내부에 path(in:)
메서드가 호출이 끝나야 최종 적인 사이즈를 알 수 있습니다.
Paths
는 절 때 경로 안에서 좌표값에 맞춰 도형을 그리지만 Shape
는 path(in:)
에서 주어진 Rect
를 기반으로 상대 경로를 받아서 그리게 됩니다.
Path
를 만드는 방법에는 몇 가지 방법이 있습니다. 사각형, 원, 사다리꼴 등 모양을 자유롭게 만들 수 있죠.
기본 도형
SwiUI에서는 앱에서 사용할 수 있는 몇 가지 기본 모양을 제공하고 있습니다.
- Rectangle (직사각형)
- RoundedRectangle (둥근 모서리 직사각형)
- Ellipse (타원)
- Circle (원)
- Capsule (캡슐)
자 이제 Path
를 사용하여 몇 가지 도형을 만들어 보겠습니다. Path
인스턴스와 해당 클로저를 삽입해 보겠습니다. 기본적으로 SwiftUI는 검은색으로 색을 채웁니다. 일단 Path
에 대해 더 배우기 쉽게 바깥쪽 테두리만 보이게 하겠습니다.
struct PathView: View {
var body: some View {
Path { path in
}
.stroke()
}
}
사각형
Path
에 여러 개의 선을 추가하여 사각형을 그려보도록 하겠습니다. 좌표인 X
및 Y
를 사용하여 이것을 그릴 수 있습니다. 첫 번째 선을 그리기 전에 가상의 사각형의 오른쪽 상단 모서리로 커서를 이동하고 오른쪽 하단 모서리로 가는 선을 추가해야 합니다. 그런 다음 addLine
을 이용하여 실제 선을 그려줘야 합니다.
struct PathView: View {
var body: some View {
Path { path in
// 1. 커서 이동
path.move(to: CGPoint(x: 200, y: 0))
// 2.
path.addLine(to: CGPoint(x: 200, y: 200))
// 3.
path.addLine(to: CGPoint(x: 0, y: 200))
}
.stroke()
}
}
여기까지 작성하면 SwiftUI에 두 개의 선이 추가된 것을 확인할 수 있습니다. 왼쪽 상단 모서리를 가리키는 세 번째 선을 추가하여 사각형을 완성해야 합니다. 시작 위치를 가리키는 마지막 줄을 추가하여 사각형을 닫을 수 있지만 .closeSubPath
수정자를 사용하여 '자동으로' Path
를 닫을 수 도 있습니다.
struct PathView: View {
var body: some View {
Path { path in
// 1. 오른쪽 모서리로 커서 이동
path.move(to: CGPoint(x: 200, y: 0))
// 2.
path.addLine(to: CGPoint(x: 200, y: 200))
// 3.
path.addLine(to: CGPoint(x: 0, y: 200))
// 4. 왼쪽 모서리로 커서 이동
path.addLine(to: CGPoint(x: 0, y: 0))
// 5. 자동으로 경로를 닫음
path.closeSubpath()
}
.stroke()
}
}
자 이렇게 정사각형의 Path
를 정의 했으므로 .stroke
수정자를 삭제하여 결과를 확인해 보겠습니다.
var body: some View {
Path { path in
// ...
}
// .stroke 삭제
}
.fill()
수정자를 사용하여 도형의 색을 채울 수 있습니다.
var body: some View {
Path { path in
// ...
}
.fill(Color.blue)
}
결과
삼각형
삼각형을 만들어 보겠습니다.
struct PathView: View {
var body: some View {
Path { path in
// 1. 커서 이동
path.move(to: CGPoint(x: 200, y: 100))
// 2.
path.addLine(to: CGPoint(x: 100, y: 300))
// 3.
path.addLine(to: CGPoint(x: 300, y: 300))
// 4. 시작한 경로로 이동
path.addLine(to: CGPoint(x: 200, y: 100))
}
.stroke()
}
}
자 언듯 봐서는 정확히 삼각형이 만들어진 것처럼 보이지만 Stroke
를 사용해서 결과를 보겠습니다.
struct PathView: View {
var body: some View {
Path { path in
...
}
.stroke(Color.blue, linewidth: 20)
}
}
상단에 모서리 부분이 깨져있습니다. 이 문제를 해결하기 위해서는 두 가지 방법이 있는데, 첫 번째 선을 다시 그리거나 두 번째 colseSubpath()
수정자를 사용하면 됩니다.
struct PathView: View {
var body: some View {
Path { path in
// 1. 커서 이동
path.move(to: CGPoint(x: 200, y: 100))
// 2.
path.addLine(to: CGPoint(x: 100, y: 300))
// 3.
path.addLine(to: CGPoint(x: 300, y: 300))
// 4.
path.addLine(to: CGPoint(x: 200, y: 100))
// 5.
path.closeSubpath()
}
.stroke(Color.blue, lineWidth: 20)
//.stroke(Color.blue.opacity(0.3))
}
}
모서리 부분 둥글게
도형의 모서리 부분을 둥글게 만들어 줄 수 돼있습니다. 바로 StrokeStyle
구조체를 사용하는 것이죠.
struct PathView: View {
var body: some View {
Path { path in
...
}
.stroke(Color.blue, style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
}
}
Stroke StylelineJoin
은 선들이 만나는 모서리의 모양을 설정함. lineCap
은 선의 끝 모양을 설정함
결과
Shape
Shpae
도 Path
로 구성됩니다. Shape
프로토콜을 채택한 Struct
를 선언하여 Square
Path
를 아래와 같은 Shpae
로 간단히 변환할 수 있습니다.
struct MySquare: Shape {
}
struct MySquare: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 200, y: 0))
path.addLine(to: CGPoint(x:200, y: 200))
path.addLine(to: CGPoint(x: 0, y: 200))
path.addLine(to: CGPoint(x: 0, y: 0))
path.closeSubpath()
return path
}
}
struct MyShape: View {
var body: some View {
MySquare()
}
}
이런 식으로 함수를 만들어서 Shape
를 사용할 수 있습니다. 그런데 MySquare
의 Shape
는 이전에 정의했던 절대 좌표를 계속 사용하고 있습니다. 이거 대신에 SwiftUI View
내의 MySquare
인스턴스에 .frame
수정자를 추가하여 변환할 수 있도록 동적으로 만들어 줄 수 있습니다.
struct MySquare: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.size.width, y: 0))
path.addLine(to: CGPoint(x: rect.size.width, y: rect.size.width))
path.addLine(to: CGPoint(x: 0, y: rect.size.width))
path.addLine(to: CGPoint(x: 0, y: 0))
path.closeSubpath()
return path
}
}
자 이제 이 함수를 body
에 호출해보겠습니다.
struct MyShape: View {
var body: some View {
MySquare()
.frame(width: 250, height: 250)
}
}
위에 보이는 것처럼 왼쪽 MyShape
에 .frame
를 할당해서 오른쪽과 같은 결과를 생성했습니다.
우리는 커서를 보이지 않는 직사각형의 오른쪽 상단 가장자리로 이동했고, 그런 다음 X
및 Y
좌표 모두에 대해 rect
의 너비를 사용하여 path
를 다른 점으로 그리도록 지시했습니다. 그리고 다음 하위 path
를 닫기 전에 왼쪽 하단 모서리로 돌아갔습니다. 이렇게 동적인 MySquare
Shape
를 만들었고 그렇기 때문에 .frame
수정자를 사용하여 크기를 조정할 수 있는 거죠!
곡선 모양
이제 조금 더 복잡한 곡선 모양을 만드는 법에 대해 알아보겠습니다. Raindrop
이라는 struct
를 선언하고 Shape
프로토콜을 준수합니다.
struct Raindrop: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
}
}
}
일단 body
에서 Raindrop
을 호출하고 이곳에 .stroke
와 .frame
수정자를 적용해주겠습니다.
struct MyShape: View {
var body: some View {
Raindrop()
.stroke(lineWidth: 4)
.frame(width: 200, height: 200)
}
}
다시 Raindrop
경로 내에서 커서를 직사각형의 위쪽 가장자리 중간으로 이동하면서 시작하도록 하겠습니다.
Path { path in
path.move(to: CGPoint(x: rect.size.width/2, y: 0))
}
다음으로 오른쪽 곡선을 아래쪽으로 그려야 합니다. 이를 위해서 addQuadCurve
메서드를 사용해야 합니다. 이 기능은 path
에 곡선을 추가하는 기능입니다. 이 곡선의 경우에는 곡선의 끝점을 정의해야 하지만 실제로 곡선을 수행하려면 Control Point을 제공해 줘야 합니다. 이러한 Control Point은 곡선의 강도와 방향을 계산하는 데 사용됩니다.
곡선의 방향과 강도는 해당 Control Point을 배치하는 위치에 따라 달라집니다. Control Point을 더 멀리 배치할수록 곡선이 더 많이 구부러지게 되는 것이죠.
addQuadCurve
함수에 대한 지식을 살펴보도록 하겠습니다. 커브가 직사각형의 아래쪽 가장자리 중간에서 끝나야 한다는 path
를 지정해주고 Control Point를 직사각형의 오른쪽 아래 가장자리에 배치합니다.
Path { path in
path.move(to: CGPoint(x: rect.size.width/2, y: 0))
path.addQuadCurve(to: CGPoint(x: rect.size.width/2, y: rect.size.height), control: CGPoint(x: rect.size.width, y: rect.size.height))
}
이제 처음 시작했던 지점을 가리키는 다른 곡선을 그려서 빗방울 모양을 완성시켜 줍니다.
struct Raindrop: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.size.width/2, y: 0))
path.addQuadCurve(to: CGPoint(x: rect.size.width/2, y: rect.size.height), control: CGPoint(x: rect.size.width, y: rect.size.height))
path.addQuadCurve(to: CGPoint(x: rect.size.width/2, y: 0), control: CGPoint(x: 0, y: rect.size.height))
}
}
}
이렇게 해서 빗방울 모양이 만들어졌습니다! 이제 .stroke
수정자 대신 .fill
을 사용하여 색을 채울 수 있습니다. 저는 Gradient
로 채워주겠습니다.
struct MyShape: View {
var body: some View {
Raindrop()
.fill(LinearGradient(gradient: Gradient(colors: [Color.white, Color.blue]), startPoint: .topLeading, endPoint: .bottom))
.frame(width: 200, height: 200)
}
}
결과
읽어주셔서 감사합니다🤟
본 게시글의 전체 코드 GitHub 👇🏻
[SwiftUI 기초/Image] - SwiftUI : Shape (Rectangle, Circle, Capsule...)
[Swift/문법 및 이론] - CGSize와 CGRect의 차이점과 CGPoint
[SwiftUI 기초/Image] - SwiftUI : trim( ) - Shape의 일부 그리기 (Timer)
'SWIFTUI > Image' 카테고리의 다른 글
SwiftUI : AsyncImage [Placeholder,Extension,Phase,Transaction] (0) | 2022.01.07 |
---|---|
SwiftUI : AspectRatio / GeometryReader / GeometryProxy (3) | 2021.04.04 |
SwiftUI : ContainerRelativeShape (위젯에서만 사용가능) (0) | 2021.03.13 |
SwiftUI : trim( ) - Shape의 일부 그리기 (Timer) (0) | 2021.03.13 |
SwiftUI : Shape (Rectangle, Circle, Capsule...) (0) | 2021.01.22 |