궁금한 내용을 검색해보세요!
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
서근 개발노트
티스토리에 팔로잉
SWIFTUI/Image

SwiftUI : Path / Shape

서근
QUOTE THE DAY

-
Written by SeogunSEOGUN

반응형

 

PathShape 에 대해 알아보도록 합시다.

Path

SwiftUI는 사용자가 원하는 Custom Shape를 그릴 수 있도록 Path를 제공하고 있습니다. 

 

Color, grandientshape와 마찬가지로 Path는 그 자체로 View입니다. 이것은 우리가 TextFieldImage처럼 사용할 수 있다는 소리죠.

 

Paths는 위치 값을 가진 선, 곡선 및 기타 정보를 가진 목록입니다. 하지만 Shape는 다른 정보를 미리 알 수 없죠. Shape 내부에 path(in:) 메서드가 호출이 끝나야 최종 적인 사이즈를 알 수 있습니다.

 

Paths는 절 때 경로 안에서 좌표값에 맞춰 도형을 그리지만 Shapepath(in:)에서 주어진 Rect를 기반으로 상대 경로를 받아서 그리게 됩니다.

 

Path를 만드는 방법에는 몇 가지 방법이 있습니다. 사각형, 원, 사다리꼴 등 모양을 자유롭게 만들 수 있죠. 

기본 도형

SwiUI에서는 앱에서 사용할 수 있는 몇 가지 기본 모양을 제공하고 있습니다.

  1. Rectangle (직사각형)
  2. RoundedRectangle (둥근 모서리 직사각형)
  3. Ellipse (타원)
  4. Circle (원)
  5. Capsule (캡슐)

 

 

자 이제 Path를 사용하여 몇 가지 도형을 만들어 보겠습니다. Path인스턴스와 해당 클로저를 삽입해 보겠습니다. 기본적으로 SwiftUI는 검은색으로 색을 채웁니다. 일단 Path에 대해 더 배우기 쉽게 바깥쪽 테두리만 보이게 하겠습니다.

struct PathView: View {
    var body: some View {
        Path { path in
            
        }
        .stroke()
    }
}

사각형

Path에 여러 개의 선을 추가하여 사각형을 그려보도록 하겠습니다. 좌표인 XY를 사용하여 이것을 그릴 수 있습니다. 첫 번째 선을 그리기 전에 가상의 사각형의 오른쪽 상단 모서리로 커서를 이동하고 오른쪽 하단 모서리로 가는 선을 추가해야 합니다. 그런 다음 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))
    }
}

TIP
 
 

Stroke Style
lineJoin은 선들이 만나는 모서리의 모양을 설정함.
lineCap은 선의 끝 모양을 설정함

결과

Shape

ShpaePath로 구성됩니다. 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를 사용할 수 있습니다. 그런데 MySquareShape는 이전에 정의했던 절대 좌표를 계속 사용하고 있습니다. 이거 대신에 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를 할당해서 오른쪽과 같은 결과를 생성했습니다. 

 

우리는 커서를 보이지 않는 직사각형의 오른쪽 상단 가장자리로 이동했고, 그런 다음 XY 좌표 모두에 대해 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 👇🏻

 

Seogun95/SwiftUI_Path_TUT

SwiftUI에서 Path를 사용하여 도형만들기. Contribute to Seogun95/SwiftUI_Path_TUT development by creating an account on GitHub.

github.com

 

 


잘못된 내용이 있으면 언제든 피드백 부탁드립니다.


서근


위처럼 이미지 와 함께 댓글을 작성할 수 있습니다.