Clubhouseのスーパー楕円をSwiftUIで作る

近年流行りだした音声SNS, Clubhouse

デザインの特徴の1つにふわっとした丸みを帯びた楕円のアイコンがあります。
これは「スーパー楕円」と言われているそうです。

丸よりも丸みを感じる!? スーパー楕円の魅力とデザイン | Spinners Inc.

この楕円を@tokoromさんがUIKitで実装してました。

スーパー楕円UIをiOS+Swiftで実装する | Spinners Inc.

この記事を読んでSwiftUIでも作りたくなったので、作ってみます。

実行環境

  • Xcode 12.2
  • iOS 14.2

実装方針

tokoromさんと同じようにもと記事の「簡易的な描き方(Vector Draw Tool)」で書かれていたアンカーポイントを移動する方法で実装します。

clipShape修飾子

SwiftUIではViewを任意の二次元図形でクリップするときはclipShape修飾子を使います。
引数にShapeプロトコルに準拠した型を指定します。

今回はSuperEllipseShapeという型を作ってスーパー楕円を表現します。

まずは全体像のコードです。

struct SuperEllipse: View {
    var body: some View {
        Image(systemName: "moon")
            .resizable()
            .frame(width: 100, height: 100)
            .background(Color.yellow)
            .clipShape(SuperEllipseShape(rate: 0.75))
    }
}

画像に対して、clipShapeをすることでクリッピングしてます。
SuperEllipseShapeの実装をみていきます。

SuperEllipseShape

実装はこちらです。

struct SuperEllipseShape: Shape {
    let rate: CGFloat
    func path(in rect: CGRect) -> Path {

        let handleX: CGFloat = rect.size.width * rate / 2
        let handleY: CGFloat = rect.size.height * rate / 2

        let left = CGPoint(x: rect.minX, y: rect.midY)
        let top = CGPoint(x: rect.midX, y: rect.minY)
        let right = CGPoint(x: rect.maxX, y: rect.midY)
        let bottom = CGPoint(x: rect.midX, y: rect.maxY)

        var path = Path()

        path.move(to: left) // 
        path.addCurve(to: top,
                      control1: CGPoint(x: left.x, y: left.y - handleY),
                      control2: CGPoint(x: top.x - handleX, y: top.y))

        path.addCurve(
          to: right,
            control1: CGPoint(x: top.x + handleX, y: top.y),
            control2: CGPoint(x: right.x, y: right.y - handleY)
        )
        path.addCurve(
          to: bottom,
            control1: CGPoint(x: right.x, y: right.y + handleY),
            control2: CGPoint(x: bottom.x + handleX, y: bottom.y)
        )
        path.addCurve(
          to: left,
            control1: CGPoint(x: bottom.x - handleX, y: bottom.y),
            control2: CGPoint(x: left.x, y: left.y + handleY)
        )

        return path
    }
}

Shapeプロトコルにはpathメソッドの準拠が必要です。
実装内容はtokoromさんの記事Superellipseの実装とほぼほぼ同じです。

handleXhandleYでベジエ曲線の調整数値を割り出しています。
left,top,right,bottomは矩形の辺のそれぞれ真ん中の座標です。

addCurveメソッドはパスに3次ベジエ曲線を追加するメソッドです。

Endpointの座標とControl point1, 2を使って曲線を作成します。

Appleドキュメントより引用

SwiftUIのaddcurveメソッドのドキュメントよりUIBezierPathのほうが情報が詳しいです。

path.move(to: left)
path.addCurve(to: top,
              control1: CGPoint(x: left.x, y: left.y - handleY),
              control2: CGPoint(x: top.x - handleX, y: top.y))

このように書くことで
leftの座標からtopの座標までの直線でhandleXhandleY分ずらしたcontrol pointで曲線を作ります。

addCurveを実行したあとは現在位置はto引数の座標、すなわちtopに移動します。

これをtopからright, rightからbottom, bottomからleftにaddCurveメソッドを一周させるとpathの完成です。

実行するとこのようなViewが表示されます。

まとめ

clipShape修飾子とShapeプロトコルを使ってClubhouseのスーパー楕円をSwiftUIで実装してみました。
思ったよりも簡単でびっくりしています。
柔らかな印象のアイコンを作れるのでアプリで取り入れていきたいですね。

コード

本記事のコードは以下のgistに載せています。

https://gist.github.com/SatoTakeshiX/c7ac3d6c82258aae693a997b86036b69

参考文系

宣伝

インプレスR&D社より、「1人でアプリを作る人を支えるSwiftUI開発レシピ」発売中です。
「SwiftUIでアプリを作る!」をコンセプトにSwiftUI自体の解説とそれを組み合わせた豊富なサンプルアプリでどんな風にアプリ実装すればいいかが理解できる本となっています。
iOS 14対応、Widgetの作成も一章まるまるハンズオンで解説しています。
SwiftUIを学びたい方、ぜひこちらのリンクをチェックしてください!



https://nextpublishing.jp/book/12491.html