1. Create drawing data for a badge view
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. Draw the badge background
Path 로 경로를 따라 도형을 그리고 그 안에 색을 채운다.
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.5,
y: height * 0.4
)
)
HexagonParameters.segments.forEach { segment in
path.addLine(
to: CGPoint(
x: width * segment.line.x,
y: height * segment.line.y
)
)
}
}
.fill(.black)
}
}
Path를 GeometryReader로 감싸 현재 view의 사이즈 값에 접근한다.
GeometryReader { geometry in
Path { path in
var width: CGFloat = min(geometry.size.width, geometry.size.height)
let height = width
scale 에 따라 x축을 변경해준다
struct BadgeBackground: View {
var body: some View {
GeometryReader { geometry in
Path { path in
var width: CGFloat = min(geometry.size.width, geometry.size.height)
let height = width
let xScale: CGFloat = 0.832
let xOffset = (width * (1.0 - xScale)) / 2.0
width *= xScale
path.move(
to: CGPoint(
x: width * 0.95 + xOffset,
y: height * (0.20 + HexagonParameters.adjustment)
)
)
HexagonParameters.segments.forEach { segment in
path.addLine(
to: CGPoint(
x: width * segment.line.x + xOffset,
y: height * segment.line.y
)
)
path.addQuadCurve(
to: CGPoint(
x: width * segment.curve.x + xOffset,
y: height * segment.curve.y
),
control: CGPoint(
x: width * segment.control.x + xOffset,
y: height * segment.control.y
)
)
}
}
.fill(.black)
}
}
}
Background에 linearGradient로 색을 입히고 aspectRatio 로 부모 view 와 상관없이 1:1로 비율 고정하고 가운데 위치시킨다
.fill(.linearGradient(
Gradient(colors: [Self.gradientStart, Self.gradientEnd]),
startPoint: UnitPoint(x: 0.5, y: 0),
endPoint: UnitPoint(x: 0.5, y: 0.6)
))
}
.aspectRatio(1, contentMode: .fit)
}
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)
}
3. Draw the badge symbol
위와 유사하게 Path API를 이용하여 도형을 그린다.
import SwiftUI
struct BadgeSymbol: View {
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)
}
}
}
그리고 위 뷰를 각도에 따라 그릴 수 있도록 RotatedBadgeSymbol veiw 를 작성한다.
struct RotatedBadgeSymbol: View {
let angle: Angle
var body: some View {
BadgeSymbol()
.padding(-60)
.rotationEffect(angle, anchor: .bottom)
}
}
4. Combine the badge foreground and background
이제 BadgeBackground 위에 BadgeSymbol들을 여러 각도로 그린다.
ZStack 은 하위 위젯이 나중에 선언될수록 앞에 위치시킨다.
import SwiftUI
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()
}
}
Path API를 쓸 때 x, y, offset, scale으로 도형을 유추하는 것이 여간 쉽지 않다...
'iOS > SwiftUI Tutorial' 카테고리의 다른 글
SwiftUI Tutorial 7 - Composing complex interfaces (0) | 2024.07.09 |
---|---|
SwiftUI Tutorial 6 - Animating views and transitions (1) | 2024.07.08 |
SwiftUI Tutorial 4 - Handling user input (0) | 2024.07.06 |
SwiftUI Tutorial 3 - Building lists and navigation (0) | 2024.07.05 |
SwiftUI Tutorial 2 - Creating and combining views 2 (0) | 2024.07.04 |