アイコンを作る
チュートリアルより。 背景に6角形を描き、その上に山のシンボルを角度をかえてぐるっと8個放射状に配置する。ZStackを使って重ねている。 放射状に配置するのはForEachで、サイズをハードコーディングを避けるにはGeometryReaderを用いている。 ポイントは様々なビューを作って組み合わせているところ。
まず、背景の6角形
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: xOffset + width * 0.95, y: height * (0.20 + HexagonParameters.adjustment))) HexagonParameters.points.forEach { path.addLine(to: .init(x:xOffset + width * $0.useWidth.0 * $0.xFactors.0, y:height * $0.useHeight.0 * $0.yFactors.0)) path.addQuadCurve(to: .init( x: xOffset + width * $0.useWidth.1 * $0.xFactors.1, y: height * $0.useHeight.1 * $0.yFactors.1), control: .init( x:xOffset + width * $0.useWidth.2 * $0.xFactors.2, y:height * $0.useHeight.2 * $0.yFactors.2)) } } .fill(LinearGradient(gradient: .init(colors: [Self.gradientStart, Self.gradientEnd]), startPoint: .init(x: 0.5, y: 0), endPoint: .init(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) }
次に山のシンボル
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 / 2 let topWidth = 0.226 * width let topHeight = 0.488 * height 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) } } }
山を回転させるのに一つビューを作っている。
struct RotatedBadgeSymbol: View { let angle:Angle var body: some View { BadgeSymbol() .padding(-60) .rotationEffect(angle, anchor: .bottom) } }
最後にzstackで組み合わせる。 全体サイズをGeometryReaderで調整し、バッジのサイズはscaleEffectで調整している点に注意。 ForEachを用いてる点も。
struct Badge: View { static let rotationCount = 8 var badgesymbols : some View { ForEach(0..<Badge.rotationCount) { i in RotatedBadgeSymbol(angle: .init(degrees: Double(i)/Double(Badge.rotationCount) * 360.0)) } .opacity(0.5) } var body: some View { ZStack { BadgeBackground() GeometryReader { geometry in self.badgesymbols .scaleEffect(1.0/4.0, anchor: .top) .position(x:geometry.size.width / 2.0,y:(3.0/4.0) * geometry.size.height) } } } }