suvera-dev ๐Ÿฅฆ

SwiftUI) Drawing and Animation 1 - Drawing Paths and Shapes ๋ณธ๋ฌธ

iOS/SwiftUI

SwiftUI) Drawing and Animation 1 - Drawing Paths and Shapes

suvera 2023. 4. 2. 23:23

๋“œ๋””์–ด ์ƒˆ๋กœ์šด ์ฑ•ํ„ฐ ์ž…๋‹ˆ๋‹ค !

 

Drawing Paths and Shapes | Apple Developer Documentation

Users receive a badge whenever they visit a landmark in their list. Of course, for a user to receive a badge, you’ll need to create one. This tutorial takes you through the process of creating a badge by combining paths and shapes, which you then overlay

developer.apple.com

๋“œ๋กœ์ž‰ & ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ฑ•ํ„ฐ์˜ ์ฒซ๋ฒˆ์งธ ๋‚ด์šฉ์€

Drawing Path And Shapes ์ธ๋ฐ์š” !

 

๊ฐ‘์ž๊ธฐ ์™œ ๋“œ๋กœ์ž‰์ด๋ƒ ? ์ €๋ฒˆ๊นŒ์ง€ ๋งŒ๋“ค์—ˆ๋˜ ๋žœ๋“œ๋งˆํฌ ์•ฑ์— ๋ฑƒ์ง€๋ฅผ ์ถ”๊ฐ€ํ•  ์˜ˆ์ •์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค ^^.

 

์œ„์˜ ๋‚ด์šฉ์„ ๋ฒˆ์—ญํ•ด๋ณด๋ฉด,

์‚ฌ์šฉ์ž๋“ค์ด ์ž์‹ ์˜ ๋ชฉ๋ก์— ์žˆ๋Š” ๋žœ๋“œ๋งˆํฌ๋ฅผ ๋ฐฉ๋ฌธํ•  ๋•Œ๋งˆ๋‹ค ๋ฑƒ์ง€๋ฅผ ๋ฐ›๋Š”๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ๋ฑƒ์ง€๋ฅผ ๋ฐ›์œผ๋ ค๋ฉด ๋ฑƒ์ง€๋ฅผ ๋งŒ๋“ค์–ด์•ผ๊ฒ ์ฃ  ? 

 

Path์™€ Shape๋ฅผ ๊ฒฐํ•ฉํ•ด์„œ ๋ฑƒ์ง€๋ฅผ ๋งŒ๋“ค๊ณ , ๊ทธ ์œ„์— ์œ„์น˜๋ฅผ ๋‚˜ํƒ€๋Œ€๋Š” ๋‹ค๋ฅธ Shape์„ ๊ฒน์ณ๋†“๋Š” ๊ณผ์ •์„ ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์–‘ํ•œ ์ข…๋ฅ˜์˜ ๋žœ๋“œ๋งˆํฌ๋ฅผ ์œ„ํ•ด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฑƒ์ง€๋ฅผ ๋งŒ๋“ค๊ณ , ๋ฐ˜๋ณต์˜ ์–‘์„ ๋‹ค๋ฅด๊ฒŒ ํ•˜๊ฑฐ๋‚˜ ๊ฐ๋„๋ž‘ ํฌ๊ธฐ๋ฅผ ๋ณ€๊ฒฝํ•ด ๋ณด์„ธ์š”.

๋ผ๊ณ  ํ•˜๋„ค์š” ! ๋ฐ”๋กœ ์‹œ์ž‘ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฑƒ์ง€ ๊ทธ๋ฆฌ๋Ÿฌ ๊ณ ๊ณ ์‹ฑ.

 

 

1. ๋ฑƒ์ง€ ๋ทฐ๋ฅผ ์œ„ํ•œ ๋“œ๋กœ์ž‰ Data ์ƒ์„ฑํ•˜๊ธฐ

๋ฑƒ์ง€๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ, ๋ฑƒ์ง€์˜ ๋ฐฐ๊ฒฝ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์œก๊ฐํ˜• ๋ชจ์–‘์„ ๊ทธ๋ฆฌ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

 

 

1. HexagonParameters๋ผ๋Š” ์ƒˆ๋กœ์šด ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ๊ตฌ์กฐ์ฒด๋ฅผ ์„ ์–ธํ•ด์ฃผ์„ธ์š”.

2. ๊ทธ ์•ˆ์— ์œก๊ฐํ˜•์˜ ํ•œ ๋ณ€์„ ๋‚˜ํƒ€๋‚ด๋Š” 3๊ฐœ์˜ Point๋“ค์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด Segment ๊ตฌ์กฐ์ฒด๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค 

- CGPoint๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด CoreGrapiccs๋ฅผ import.

3. ์ƒˆ๋กœ์šด ๋ฐฐ์—ด์„ ์ถ”๊ฐ€ํ•˜๊ณ , ์œก๊ฐํ˜•์˜ ๊ฐ ๋ณ€๋งˆ๋‹ค ํ•˜๋‚˜์”ฉ 6๊ฐœ์˜ ์„ธ๊ทธ๋จผํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

- ๊ฐ’๋“ค์€ ์™ผ์ชฝ ์ƒ๋‹จ์„ ์›์ ์œผ๋กœ ํ•˜๋Š” ๋‹จ์œ„ ์‚ฌ๊ฐํ˜€์˜ ๋น„์œจ๋กœ ์ €์žฅ.

- x์ถ•์ด ์˜ค๋ฅธ์ชฝ ๋ฐฉํ–ฅ์œผ๋กœ, y์ถ•์ด ์•„๋ž˜์ชฝ ๋ฐฉํ–ฅ์œผ๋กœ ์–‘์ˆ˜.

- ๋‚˜์ค‘์—๋Š” ์ด๋Ÿฌํ•œ ๋น„์œจ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ง€์ •๋œ ํฌ๊ธฐ์˜ ์œก๊ฐํ˜€์˜ ์‹ค์ œ ์ขŒํ‘œ๋ฅผ ์ฐพ๋Š”๋‹ค.

4. ์œก๊ฐํ˜•์˜ ๋ชจ์–‘์„ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

 

import CoreGraphics

struct HexagonParameters {
    struct Segment {
        let line: CGPoint
        let curve: CGPoint
        let control: CGPoint
    }

    static let adjustment: CGFloat = 0.085

    static let segments = [
        Segment(
            line:    CGPoint(x: 0.60, y: 0.05),
            curve:   CGPoint(x: 0.40, y: 0.05),
            control: CGPoint(x: 0.50, y: 0.00)
        ),
        Segment(
            line:    CGPoint(x: 0.05, y: 0.20 + adjustment),
            curve:   CGPoint(x: 0.00, y: 0.30 + adjustment),
            control: CGPoint(x: 0.00, y: 0.25 + adjustment)
        ),
        Segment(
            line:    CGPoint(x: 0.00, y: 0.70 - adjustment),
            curve:   CGPoint(x: 0.05, y: 0.80 - adjustment),
            control: CGPoint(x: 0.00, y: 0.75 - adjustment)
        ),
        Segment(
            line:    CGPoint(x: 0.40, y: 0.95),
            curve:   CGPoint(x: 0.60, y: 0.95),
            control: CGPoint(x: 0.50, y: 1.00)
        ),
        Segment(
            line:    CGPoint(x: 0.95, y: 0.80 - adjustment),
            curve:   CGPoint(x: 1.00, y: 0.70 - adjustment),
            control: CGPoint(x: 1.00, y: 0.75 - adjustment)
        ),
        Segment(
            line:    CGPoint(x: 1.00, y: 0.30 + adjustment),
            curve:   CGPoint(x: 0.95, y: 0.20 + adjustment),
            control: CGPoint(x: 1.00, y: 0.25 + adjustment)
        )
    ]
}

 

2. ๋ฑƒ์ง€์˜ ๋ฐฐ๊ฒฝ ๊ทธ๋ฆฌ๊ธฐ

SwiftUI์˜ ๊ทธ๋ž˜ํ”ฝ API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปค์Šคํ…€ ๋ฑƒ์ง€ ๋ชจ์–‘์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.

 

1. BadgeBackground๋ผ๋Š” ๋ทฐ๋ฅผ ํ•˜๋‚˜ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

2. Path ๋ชจ์–‘์„ ๋ฑƒ์ง€์— ์ถ”๊ฐ€ํ•˜๊ณ , fill() ์ˆ˜์ •์ž๋ฅผ ์ ์šฉํ•˜์—ฌ ๋ชจ์–‘์„ ๋ทฐ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

- Path๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ , ๊ณก์„  ๋ฐ ๊ธฐํƒ€ ๊ทธ๋ฆฌ๊ธฐ ๊ธฐ๋ณธ ์š”์†Œ๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ๋ฑƒ์ง€์˜ ์œก๊ฐํ˜• ๋ฐฐ๊ฒฝ๊ณผ ๊ฐ™์ด ๋ณต์žกํ•œ ๋ชจ์–‘์„ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค.

 

3. Path์— ์‹œ์ž‘์ ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ, ํฌ๊ธฐ๊ฐ€ 100 x 100 ํ”ฝ์…€์ธ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.

- move(to:) ๋ฉ”์„œ๋“œ๋Š” ์ƒ์ƒ ์†์˜ ํŽœ์ด๋‚˜ ์—ฐํ•„์ด ํ•ด๋‹น ์˜์—ญ ์œ„๋ฅผ ๋งด๋Œ๋ฉฐ ๊ทธ๋ฆฌ๊ธฐ๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ชจ์–‘ ๋‚ด๋ถ€์—์„œ ๊ทธ๋ฆฌ๊ธฐ ์ปค์„œ๋ฅผ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค.

struct BadgeBackground: View {
    var body: some View {
        Path { path in
            var width: CGFloat = 100.0
            let height = width
            path.move(
                to: CGPoint(
                    x: width * 0.95,
                    y: height * 0.20
                )
            )
        }
        .fill(.black)
    }
}

 

4. ๋ชจ์–‘ ๋ฐ์ดํ„ฐ์˜ ๊ฐ ์ ์— ๋Œ€ํ•œ ์„ ์„ ๊ทธ๋ ค์„œ ๋Œ€๋žต์ ์ธ ์œก๊ฐํ˜• ๋ชจ์–‘์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

- addLine(to:) ๋ฉ”์„œ๋“œ๋Š” ํ•˜๋‚˜์˜ ์ ์„ ๊ฐ€์ ธ์™€์„œ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค. addLine(to:)์˜ ์—ฐ์† ํ˜ธ์ถœ์€ ์ด์ „ ์ ์—์„œ ์„ ์„ ์‹œ์ž‘ํ•˜์—ฌ ์ƒˆ ์ ๊นŒ์ง€ ์ด์–ด์ง‘๋‹ˆ๋‹ค.

 

HexagonParameters.segments.forEach { segment in
	path.addLine(
		to: CGPoint(
			x: width * segment.line.x,
			y: height * segment.line.y
		)
	)
}

์œก๊ฐํ˜•์ด ์•ฝ๊ฐ„ ์ด์ƒํ•˜๊ฒŒ ๋ณด์ธ๋‹ค๋ฉด ๊ฑฑ์ •ํ•˜์ง€ ๋งˆ์„ธ์š”.

๋ชจ์–‘์˜ ๋ชจ์„œ๋ฆฌ์— ์žˆ๋Š” ๊ณก์„  ๋ถ€๋ถ„์„ ๋ฌด์‹œํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ ๋‹ค์Œ์— ๊ณ„์‚ฐํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

 

5. addQuadCurve(to:control:) ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฑƒ์ง€์˜ ๋ชจ์„œ๋ฆฌ์— ๋Œ€ํ•œ ๋ฒ ์ง€์–ด ๊ณก์„ ์„ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.

- addLine ์•„๋ž˜์— ๋„ฃ์–ด์คŒ.

                path.addQuadCurve(
                    to: CGPoint(
                        x: width * segment.curve.x,
                        y: height * segment.curve.y
                    ),
                    control: CGPoint(
                        x: width * segment.control.x,
                        y: height * segment.control.y
                    )
                )

6. GeometryReader๋กœ Path๋ฅผ ๊ฐ์‹ธ๋ฉด, containing view์˜ ํฌ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

- ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ’์„ ํ•˜๋“œ์ฝ”๋”ฉํ•˜๋Š” ๋Œ€์‹  ์ปจํ…Œ์ด๋„ˆ์˜ ํฌ๊ธฐ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

- width์™€ height ์ค‘ ๋” ์ž‘์€ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋ฉด, ์ปจํ…Œ์ด๋„ˆ ๋ทฐ๊ฐ€ ์ •์‚ฌ๊ฐํ˜•์ด ์•„๋‹ˆ๋”๋ผ๋„ aspect ratio?๋ฅผ ๋ณด์กด?ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

GeometryReader { geometry in
	Path {
	...
    var width: CGFloat = min(geometry.size.width, geometry.size.height)
}

- Path๋ฅผ GeometryReader๋กœ ๊ฐ์‹ธ๊ณ  ๋‚ด๋ถ€์˜ width ๊ฐ’์„ ์„ค์ •ํ•ด์คฌ์Šต๋‹ˆ๋‹ค.

 

7. xScale์„ ์‚ฌ์šฉํ•˜์—ฌ x-์ถ•์„ ๋”ฐ๋ผ ๋ชจ์–‘์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•œ ๋‹ค์Œ xOffset์„ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ชจ์–‘์„ ํ•ด๋‹น geometry ์•ˆ์—์„œ ๋‹ค์‹œ ์ค‘์•™์— ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.

let xScale: CGFloat = 0.832
let xOffset = (width * (1.0 - xScale)) / 2.0
width *= xScale

- ์ด ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ , x ์ขŒํ‘œ ๊ฐ’์— ๋ชจ๋‘ + xOffset์„ ํ•ด์คฌ๋‹ค.

 

8. ๋””์ž์ธ์— ๋งž๋Š” ๊ทธ๋ผ๋ฐ์ด์…˜์œผ๋กœ ๋‹จ์ƒ‰ ๊ฒ€์ • ๋ฐฐ๊ฒฝ์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

    static let gradientStart = Color(red: 239.0 / 255, green: 120.0 / 255, blue: 221.0 / 255)
    static let gradientEnd = Color(red: 239.0 / 255, green: 172.0 / 255, blue: 120.0 / 255)
.fill(.linearGradient(
	Gradient(colors: [Self.gradientStart, Self.gradientEnd]),
	startPoint: UnitPoint(x: 0.5, y: 0),
	endPoint: UnitPoint(x: 0.5, y: 0.6)
))

9. gradient ์ฑ„์šฐ๊ธฐ์— aspectRatio(_:contentMode:) ์ˆ˜์ •์ž๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

1:1 aspect ratio๋ฅผ ๋ณด์กดํ•˜๋ฉด, ๋ทฐ์˜ ๋ถ€๋ชจ ๋ทฐ๊ฐ€ ์ •์‚ฌ๊ฐํ˜•์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋„ ๋ฐฐ์ง€๊ฐ€ ๋ทฐ ์ค‘์•™์— ์œ„์น˜๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

.aspectRatio(1, contentMode: .fit)

 

 

3. ๋ฑƒ์ง€์˜ ์‹ฌ๋ณผ ๊ทธ๋ฆฌ๊ธฐ

Landmarks ๋ฐฐ์ง€์—๋Š” Landmarks ์•ฑ ์•„์ด์ฝ˜์— ๋‚˜ํƒ€๋‚˜๋Š” ์‚ฐ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ์ปค์Šคํ…€ ์•„์ด์ฝ˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฐ ๊ธฐํ˜ธ๋Š” ๊ผญ๋Œ€๊ธฐ์˜ ๋ˆˆ๋ฎ์ธ ๋ถ€๋ถ„์„ ๋‚˜ํƒ€๋‚ด๋Š” ํ•˜๋‚˜์˜ ๋ชจ์–‘๊ณผ, ๋‹ค๊ฐ€์˜ค๋Š” ๊ธธ ๊ฐ€์žฅ์ž๋ฆฌ์˜ ์‹๋ฌผ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋‹ค๋ฅธ ๋ชจ์–‘์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

์ด๋“ค์„ ์ž‘์€ ๊ฐ„๊ฒฉ์œผ๋กœ ๋ถ„๋ฆฌ๋œ ๋‘ ๋ถ€๋ถ„์ ์œผ๋กœ ์‚ผ๊ฐํ˜• ๋ชจ์–‘์œผ๋กœ ๊ทธ๋ ค๋ƒ…๋‹ˆ๋‹ค.

 

 

1. ํ”„๋กœ์ ํŠธ์˜ ์—์…‹ ์นดํƒˆ๋กœ๊ทธ์—์„œ ๋นˆ AppIcon ํ•ญ๋ชฉ์„ ์‚ญ์ œํ•œ ๋‹ค์Œ, ๋‹ค์šด๋กœ๋“œํ•œ ํ”„๋กœ์ ํŠธ์˜ Resources ํด๋”์—์„œ AppIcon.appiconset ํด๋”๋ฅผ ๋“œ๋ž˜๊ทธํ•ฉ๋‹ˆ๋‹ค.

 

2. BadgeSymbol์ด๋ผ๋Š” ์ƒˆ๋กœ์šด ๋ทฐ๋ฅผ ๋งŒ๋“œ์„ธ์š”. ์ด ๋ทฐ๋Š” ๋ฐฐ์ง€ ๋””์ž์ธ์—์„œ ํšŒ์ „ ํŒจํ„ด์œผ๋กœ ์ฐํžˆ๋Š” ์‚ฐ ๋ชจ์–‘์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

 

3. ์‹ฌ๋ณผ์˜ ์ƒ๋‹จ๋ถ€๋ถ„ ๊ทธ๋ฆฌ๊ธฐ - Path API ์‚ฌ์šฉ.

- ์ถ”๊ฐ€์ ์œผ๋กœ, spacing, topWidth ๋ฐ topHeight ์ƒ์ˆ˜์™€ ๊ด€๋ จ๋œ ์ˆซ์ž์˜ ๊ณฑ์„ ์กฐ์ •ํ•˜์—ฌ ์ „๋ฐ˜์ ์ธ ๋ชจ์–‘์ด ์–ด๋–ป๊ฒŒ ๋ณ€ํ™”ํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด๋Š” ์‹คํ—˜์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค.

 

4. ํ•˜๋‹จ๋ถ€๋ถ„ ๊ทธ๋ฆฌ๊ธฐ 

- ๋™์ผํ•œ ๊ฒฝ๋กœ ๋‚ด์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ชจ์–‘์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด move(to:) ์ˆ˜์ •์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๊ฒฉ์„ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค.

 

5. ์‹ฌ๋ณผ ์ปฌ๋Ÿฌ ์ฑ„์›Œ์ฃผ๊ธฐ

 

   static let symbolColor = Color(red: 79.0 / 255, green: 79.0 / 255, blue: 191.0 / 255)
   
   var body: some View {
        GeometryReader { geometry in
            Path { path in
                let width = min(geometry.size.width, geometry.size.height)
                let height = width * 0.75
                let spacing = width * 0.030
                let middle = width * 0.5
                let topWidth = width * 0.226
                let topHeight = height * 0.488

                path.addLines([
                    CGPoint(x: middle, y: spacing),
                    CGPoint(x: middle - topWidth, y: topHeight - spacing),
                    CGPoint(x: middle, y: topHeight / 2 + spacing),
                    CGPoint(x: middle + topWidth, y: topHeight - spacing),
                    CGPoint(x: middle, y: spacing)
                ])

                path.move(to: CGPoint(x: middle, y: topHeight / 2 + spacing * 3))
                path.addLines([
                    CGPoint(x: middle - topWidth, y: topHeight + spacing),
                    CGPoint(x: spacing, y: height - spacing),
                    CGPoint(x: width - spacing, y: height - spacing),
                    CGPoint(x: middle + topWidth, y: topHeight + spacing),
                    CGPoint(x: middle, y: topHeight / 2 + spacing * 3)
                ])
            }
            .fill(Self.symbolColor)
        }
    }

 

6. ๋‹ค์Œ์œผ๋กœ, ํšŒ์ „๋œ ์‹ฌ๋ณผ์„ ์บก์Šํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ๋กœ์šด RotatedBadgeSymbol ๋ทฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

- ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ ๊ฐ๋„๋ฅผ ์กฐ์ •ํ•˜์—ฌ ํšŒ์ „ ํšจ๊ณผ๋ฅผ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.

struct RotatedBadgeSymbol: View {
    let angle: Angle

    var body: some View {
        BadgeSymbol()
            .padding(-60)
            .rotationEffect(angle, anchor: .bottom)
    }
}

struct RotatedBadgeSymbol_Previews: PreviewProvider {
    static var previews: some View {
        RotatedBadgeSymbol(angle: Angle(degrees: 5))
    }
}

๊ฒฐ๊ณผ๋Š” ์š”๋ ‡๊ฒŒ ๋ฉ๋‹ˆ๋‹ค !

 

 

4. ๋ฑƒ์ง€์˜ ์ „๋ฉด๊ณผ ๋ฐฐ๊ฒฝ์„ ๊ฒฐํ•ฉํ•˜๊ธฐ

์ด ๋ฐฐ์ง€ ๋””์ž์ธ์—์„œ๋Š” ์‚ฐ ๋ชจ์–‘์ด ํšŒ์ „๋˜์–ด ๋ฐฐ์ง€ ๋ฐฐ๊ฒฝ ์œ„์— ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐ˜๋ณต๋˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํšŒ์ „์„ ์œ„ํ•œ ์ƒˆ๋กœ์šด ํƒ€์ž…์„ ์ •์˜ํ•˜๊ณ  ForEach ๋ทฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‚ฐ ๋ชจ์–‘์˜ ์—ฌ๋Ÿฌ ๋ณต์‚ฌ๋ณธ์— ๋™์ผํ•œ ์กฐ์ •(adjustments)์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

1. Badge๋ผ๋Š” ์ด๋ฆ„์˜ ์ƒˆ๋กœ์šด ๋ทฐ๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

2. Badge๋ทฐ์˜ body์— BadgeBackground ๋ฅผ ๋ฐฐ์น˜ํ•ด์ค๋‹ˆ๋‹ค.

3. ๋ฐฐ์ง€์˜ ์‹ฌ๋ณผ์„ ๋ฐฐ์ง€ ๋ฐฐ๊ฒฝ ์œ„์— ZStack์— ๋†“์•„ ๋ฐฐ์น˜ํ•˜์„ธ์š”.

struct Badge: View {
    var badgeSymbols: some View {
        RotatedBadgeSymbol(angle: Angle(degrees: 0))
            .opacity(0.5)
    }

    var body: some View {
        ZStack {
            BadgeBackground()

            badgeSymbols
        }
    }
}

4. ์ฃผ๋ณ€์˜ ๊ธฐํ•˜ํ•™์ ์ธ ์š”์†Œ๋ฅผ ์ฝ๊ณ  ์‹ฌ๋ณผ์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ •ํ•˜์—ฌ ๋ฐฐ์ง€ ์‹ฌ๋ณผ์˜ ํฌ๊ธฐ๋ฅผ ์ˆ˜์ •ํ•˜์„ธ์š”.

struct Badge: View {
    var badgeSymbols: some View {
        RotatedBadgeSymbol(angle: Angle(degrees: 0))
            .opacity(0.5)
    }

    var body: some View {
        ZStack {
            BadgeBackground()

            GeometryReader { geometry in
                badgeSymbols
                    .scaleEffect(1.0 / 4.0, anchor: .top)
                    .position(x: geometry.size.width / 2.0, y: (3.0 / 4.0) * geometry.size.height)
            }
        }
    }
}

 

5. ๋ฐฐ์ง€ ์‹ฌ๋ณผ์˜ ๋ณต์‚ฌ๋ณธ์„ ํšŒ์ „ํ•˜์—ฌ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ForEach ๋ทฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.
- 360๋„์˜ ์™„์ „ํ•œ ํšŒ์ „์„ 8๊ฐœ ์„ธ๊ทธ๋จผํŠธ๋กœ ๋ถ„ํ• ํ•˜์—ฌ ์‚ฐ ๋ชจ์–‘ ์‹ฌ๋ณผ์„ ๋ฐ˜๋ณตํ•จ์œผ๋กœ์จ ํƒœ์–‘๊ณผ ๊ฐ™์€ ํŒจํ„ด์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

struct Badge: View {
    var badgeSymbols: some View {
        ForEach(0..<8) { index in
            RotatedBadgeSymbol(
                angle: .degrees(Double(index) / Double(8)) * 360.0
            )
        }
        .opacity(0.5)
    }

    var body: some View {
        ZStack {
            BadgeBackground()

            GeometryReader { geometry in
                badgeSymbols
                    .scaleEffect(1.0 / 4.0, anchor: .top)
                    .position(x: geometry.size.width / 2.0, y: (3.0 / 4.0) * geometry.size.height)
            }
        }
        .scaledToFit()
    }
}

์ตœ์ข… ๊ฒฐ๊ณผ๋ฌผ์€ ์ด๋ ‡๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

ํ›„๊ธฐ

 

์‚ฌ์‹ค ์ด๋ฒˆ ํŠœํ† ๋ฆฌ์–ผ์€ ๊ทธ๋ƒฅ ๋”ฐ๋ผํ•ด๋ณด๋Š” ์žฌ๋ฏธ์˜€๋˜ ๊ฒƒ ๊ฐ™์•„์š” !! ๋งˆ์น˜ ๋””์ž์ด๋„ˆ๊ฐ€ ๋œ ๊ธฐ๋ถ„์ด๋ž„๊นŒ.. 

์‹ค์ œ๋กœ ์ €๋Ÿฐ ํƒœ์–‘๋ชจ์–‘์„ ์ง์ ‘ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ๋„ ์—†์„ ๊ฒƒ ๊ฐ™๊ณ .. (?) ํ˜น์‹œ๋ชฐ๋ผ..? 

์ˆซ์ž๋‚˜ ๊ณ„์‚ฐ์ด ๋งŽ์ด ๋‚˜์˜ค๊ณ  ์ข€ ๊ท€์ฐฎ๊ธด ํ•˜๋„ค์š”...ใ…Ž

๊ทธ๋ž˜๋„ ํฌ์ŠคํŒ… ํ•ด๋‘๊ณ  ํ˜น์‹œ ๋‚˜์ค‘์— ํ•„์š”ํ•œ ์š”์†Œ๋“ค์ด ๋– ์˜ค๋ฅธ๋‹ค๋ฉด ์–ธ์  ๊ฐ„ ๋‹ค์‹œ ์ฐพ์„ ์ˆ˜๋„ ์žˆ๊ฒ ์ฃ ..?

Containing View์˜ ํฌ๊ธฐ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” GeometryReader์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ ์œ ์šฉํ–ˆ๋˜ ๊ฒƒ ๊ฐ™์•„์š”.

Drawing์— ๋Œ€ํ•œ modifier๋‚˜ ๋‹ค์–‘ํ•œ ์š”์†Œ๋“ค์„ ์•Œ๊ณ  ์žˆ์œผ๋ฉด ๋‚˜์ค‘์— ์ปค์Šคํ…€ํ•œ ๋ทฐ๋ฅผ ๋งŒ๋“ค ๋•Œ๋„ ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™๊ธดํ•˜๋„ค์š” !

 

 

'iOS > SwiftUI' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

SwiftUI) SwiftUI Essentials 3 - Handling User Input  (4) 2023.03.31
SwiftUI) SwiftUI Essentials - 2 Building Lists and Navigation  (0) 2023.03.29
SwiftUI) SwiftUI Essentials  (8) 2023.03.29
Comments