【SwiftUI】動く破線の枠線で囲まれたViewを作ってみた

2022.04.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ペイントアプリでよく見かける動く破線の枠線を作ってみたかったので作ってみることにしました。

作ったもの

waku-waku-sen-1

この記事ではこの作り方について説明していきます。

環境

  • Xcode 13.3

点線の枠線を作る

まずは普通の破線の枠線で囲まれたViewを作ります。

var body: some View {
    Rectangle()
        .stroke(style: StrokeStyle(dash: [4, 2]))
        .frame(width: 200, height: 200)
}

stroke(style:)で、Rectangle()のストロークをコピーして、そのストロークのStrokeStyleを定義してあげるだけで破線で囲まれたViewが作れます。

StrokeStyledashの配列[4, 2]ですが、4の箇所が破線部分の長さを示しており、2の箇所が破線の空白の部分を示しています。

プレビュー

StrokeStyle

StrokeStyledashについては説明しましたが、他のプロパティもあります。

@frozen public struct StrokeStyle : Equatable {

    /// ストロークされたパスの長さ
    public var lineWidth: CGFloat

        /// 線の終点のスタイル
    public var lineCap: CGLineCap

        /// 線の結合部分のスタイル
    public var lineJoin: CGLineJoin

        /// 結合箇所をmiterにするかbevelにするかを決めるためのしきい値
    public var miterLimit: CGFloat

        ///  破線を形成する為の色付け部分と空白部分の長さ
    public var dash: [CGFloat]

    /// 破線のスタート地点までの距離
    public var dashPhase: CGFloat

miterbevelについてはこちらのmiterLimitの説明が分かりやすかったです。

今回は上記のプロパティの内dashPhaseの値を変更することで移動しているような破線を表現していきます。

動く破線を表現する

コード

import SwiftUI

struct ContentView: View {

    /// 破線のスタート地点を変更する為のプロパティ
    @State private var dashPhase: CGFloat = 0
    /// Timerのカウントを保持するプロパティ
    @State private var timerCount: CGFloat = 0
        /// 0.1秒毎に`dashPhase`を変更処理を実行する為のTimer
    private let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()

    var body: some View {
        Rectangle()
            .stroke(style: StrokeStyle(dash: [6, 6],
                                       dashPhase: dashPhase))
            .frame(width: 200, height: 200)
            .onReceive(timer) { _ in
                timerCount = timerCount > 10 ? 0 : timerCount + 1
                dashPhase = timerCount
            }
    }
}

プロパティ

dashPhaseでは、変更された破線のスタート地点の値が代入されるので、その値をStrokeStyledashPhaseに設定しています。

0.1秒毎に処理を実行できるtimerを用意し、そのTimerのカウントに応じて、dashPhaseを変更する為、timerCountでカウントを保持します。

onReceive(timer)

onReceiveの第一引数にはtimerを渡すことでクロージャーの処理が0.1秒毎に呼ばれます。

.onReceive(timer) { _ in
    timerCount = timerCount > 10 ? 0 : timerCount + 1
    dashPhase = timerCount
}

実行している処理としては、timerCount10より大きいなら0に初期化して元の状態に戻して、timerCount10以下なら1を加算しています。そのtimerCountの値をdashPhaseに代入することで破線が移動しているような動きを表現しています。

dashPhase

最初の説明でdashPhase破線のスタート地点までの距離を示すプロパティと記載したのですが、つまりはオフセットなので、この値が増える毎に最初の破線の位置が移動していきます。今回は10という値をしきい値にしましたが、この値は破線の長さによって変更してきます。

以上で動く破線の枠線の四角いViewの完成です。

おわりに

思ったより少ないコードで実現できてびっくりしました。頭に描いたものが作れると楽しいですね。

これからも作って遊ぼうしていきたいと思います。

参考