Rodhos Soft

備忘録を兼ねた技術的なメモです。Rofhos SoftではiOSアプリ開発を中心としてAndroid, Webサービス等の開発を承っております。まずはご相談下さい。

アイコンを作る

チュートリアルより。 背景に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)
            }
        }
    }
}