LoginSignup
6
1

SwiftUI でデジタル時計のUIを実装してみる

Last updated at Posted at 2023-12-19

SwiftUIの勉強がてらデジタルウォッチのUIを実装してみることにした。
チープな白黒液晶が懐かしくなり80〜90年代を席巻したカ⚪︎オ データバ⚪︎クなど所謂チープカシ⚪︎と呼ばれる腕時計のUIを参考に幾つかそのコンポーネントを再現することにした。

最終的にはそれをwatchOS上で動かせるようにしたいと思ったが、物理ボタンでの操作を想定したUIのはずなのでむずかしそう。
一種のアート作品として生暖かく見守っていただきたい。

📋 概要

どのようなアウトプットにすべきか考えたところ、懐かしいデジタルウォッチのUIコンポーネントを提供するレトロフューチャーなOSSにすることにした。需要はないだろうけど

現状のリポジトリは以下(まだPackage化は半端な状態)
https://github.com/JikeLab/DigitalClockKit

幾つか example として実装してみた画面はこの通り。

🔎 コンポーネントの説明

数字と一部のアルファベット

7セグメントディスプレイというらしい。
主に日付や時刻を表現する部分を表現するためのコンポーネントでデジタル時計によっては曜日を表す際に一部のアルファベットもこれを使って表現されている。

まずは描画パスを指定するためのOptionSetを定義する。

DigitView.swift#L87-L98
struct PathFlags: OptionSet {
    let rawValue: Int16
    static let top = PathFlags(rawValue: 1 << 0)    // 上辺
    static let rightTop = PathFlags(rawValue: 1 << 1)    // 右辺の上部
    static let rightBottom = PathFlags(rawValue: 1 << 2)    // 右辺の下部
    static let bottom = PathFlags(rawValue: 1 << 3)    // 下辺
    static let leftTop = PathFlags(rawValue: 1 << 4)    // 左辺の上部
    static let leftBottom = PathFlags(rawValue: 1 << 5)    // 左辺の下部
    static let middle = PathFlags(rawValue: 1 << 6)    // 真ん中の辺
    static let center = PathFlags(rawValue: 1 << 7)    // アルファベットのMやWを表現するための中央線
    static let outsideLeftTop = PathFlags(rawValue: 1 << 8)    // アルファベットのRを表現するためのパス
}

描画部分はこのOptionSetに従って指定されているPathを書く実装にする。
componentSize は数字一つの大きさ。

DigitView.swift#L111-L231
private struct DigitContentView: View {
    let type: DigitType
    let componentSize: CGSize
    let color: Color

    var body: some View {
        ZStack {
            let inset = componentSize.width / 18
            let margin = componentSize.width / 18
            let lineWidth = componentSize.width / 3.5
            if type.paths.contains(.top) {
                // top
                Path { path in
                    path.move(to: CGPoint(x: (inset + margin), y: inset))
                    path.addLine(to: CGPoint(x: (componentSize.width - inset - margin), y: inset))
                    path.addLine(to: CGPoint(x: (componentSize.width - lineWidth - margin), y: lineWidth))
                    path.addLine(to: CGPoint(x: (lineWidth + margin), y: lineWidth))
                }
                .fill(color)
            }
            if type.paths.contains(.rightTop) {
                // rightTop
                Path { path in
                    path.move(to: CGPoint(x: (componentSize.width - lineWidth), y: (lineWidth + margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width - inset), y: (inset + margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width - inset), y: (componentSize.height / 2 - margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width - lineWidth / 2), y: (componentSize.height / 2 - margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width - lineWidth), y: (componentSize.height / 2 - lineWidth / 2 - margin)))
                }
                .fill(color)
            }
            if type.paths.contains(.rightBottom) {
                // rightBottom
                Path { path in
                    path.move(to: CGPoint(x: (componentSize.width - lineWidth), y: (componentSize.height / 2 + lineWidth / 2 + margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width - lineWidth / 2), y: (componentSize.height / 2 + margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width - inset), y: (componentSize.height / 2 + margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width - inset), y: (componentSize.height - inset - margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width - lineWidth), y: (componentSize.height - lineWidth - margin)))
                }
                .fill(color)
            }
            if type.paths.contains(.bottom) {
                // bottom
                Path { path in
                    path.move(to: CGPoint(x: (lineWidth + margin), y: (componentSize.height - lineWidth)))
                    path.addLine(to: CGPoint(x: (componentSize.width - lineWidth - margin), y: (componentSize.height - lineWidth)))
                    path.addLine(to: CGPoint(x: (componentSize.width - inset - margin), y: (componentSize.height - inset)))
                    path.addLine(to: CGPoint(x: (inset + margin), y: (componentSize.height - inset)))
                }
                .fill(color)
            }
            if type.paths.contains(.leftTop) {
                // leftTop
                Path { path in
                    path.move(to: CGPoint(x: inset, y: (inset + margin)))
                    path.addLine(to: CGPoint(x: lineWidth, y: (lineWidth + margin)))
                    path.addLine(to: CGPoint(x: lineWidth, y: (componentSize.height / 2 - lineWidth / 2 - margin)))
                    path.addLine(to: CGPoint(x: (lineWidth / 2), y: (componentSize.height / 2 - margin)))
                    path.addLine(to: CGPoint(x: inset, y: (componentSize.height / 2 - margin)))
                }
                .fill(color)
            }
            if type.paths.contains(.leftBottom) {
                // leftBottom
                Path { path in
                    path.move(to: CGPoint(x: inset, y: (componentSize.height / 2 + margin)))
                    path.addLine(to: CGPoint(x: (lineWidth / 2), y: (componentSize.height / 2 + margin)))
                    path.addLine(to: CGPoint(x: lineWidth, y: (componentSize.height / 2 + lineWidth / 2 + margin)))
                    path.addLine(to: CGPoint(x: lineWidth, y: (componentSize.height - lineWidth - margin)))
                    path.addLine(to: CGPoint(x: inset, y: (componentSize.height - inset - margin)))
                }
                .fill(color)
            }
            if type.paths.contains(.middle) {
                // middle
                Path { path in
                    path.move(to: CGPoint(x: (lineWidth / 2 + margin), y: (componentSize.height / 2)))
                    path.addLine(to: CGPoint(x: (lineWidth + margin), y: (componentSize.height / 2 - lineWidth / 2)))
                    path.addLine(to: CGPoint(x: (componentSize.width - lineWidth - margin), y: (componentSize.height / 2 - lineWidth / 2)))
                    path.addLine(to: CGPoint(x: (componentSize.width - lineWidth / 2 - margin), y: (componentSize.height / 2)))
                    path.addLine(to: CGPoint(x: (componentSize.width - lineWidth - margin), y: (componentSize.height / 2 + lineWidth / 2)))
                    path.addLine(to: CGPoint(x: (lineWidth + margin), y: (componentSize.height / 2 + lineWidth / 2)))
                }
                .fill(color)
            }
            if type.paths.contains(.center) {
                // center
                Path { path in
                    path.move(to: CGPoint(x: (componentSize.width / 2 - lineWidth / 2), y: (lineWidth + margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width / 2 + lineWidth / 2), y: (lineWidth + margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width / 2 + lineWidth / 2), y: (componentSize.height / 2 - lineWidth / 2)))
                    path.addLine(to: CGPoint(x: (componentSize.width / 2 - lineWidth / 2), y: (componentSize.height / 2 - lineWidth / 2)))
                    path.addLine(to: CGPoint(x: (componentSize.width / 2 - lineWidth / 2), y: (lineWidth + margin)))
                    
                }
                .fill(color)
                Path { path in
                    path.move(to: CGPoint(x: (componentSize.width / 2 - lineWidth / 2), y: (componentSize.height / 2 + lineWidth / 2 + margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width / 2 + lineWidth / 2), y: (componentSize.height / 2 + lineWidth / 2 + margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width / 2 + lineWidth / 2), y: (componentSize.height - lineWidth - margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width / 2 - lineWidth / 2), y: (componentSize.height - lineWidth - margin)))
                    path.addLine(to: CGPoint(x: (componentSize.width / 2 - lineWidth / 2), y: (componentSize.height / 2 + lineWidth / 2 + margin)))
                    
                }
                .fill(color)
            }
            if type.paths.contains(.outsideLeftTop) {
                // outsideLeftTop
                Path { path in
                    path.move(to: CGPoint(x: 0, y: inset))
                    path.addLine(to: CGPoint(x: -(componentSize.width / 2), y: inset))
                    path.addLine(to: CGPoint(x: -(componentSize.width / 2 - lineWidth), y: (lineWidth + inset)))
                    path.addLine(to: CGPoint(x: 0, y: (lineWidth + inset)))
                }
                .fill(color)
            }
        }
        .frame(width: componentSize.width, height: componentSize.height)
    }
}

それぞれの描画結果はこんな感じ。描画結果は大体Previewに定義したつもり。

DigitView.swift#233-269
#Preview {
    VStack {
        HStack {
            DigitView(type: .zero, componentSize: defaultComponentSize)
            DigitView(type: .one, componentSize: defaultComponentSize)
            DigitView(type: .two, componentSize: defaultComponentSize)
            DigitView(type: .three, componentSize: defaultComponentSize)
            DigitView(type: .four, componentSize: defaultComponentSize)
        }
        HStack {
            DigitView(type: .five, componentSize: defaultComponentSize)
            DigitView(type: .six, componentSize: defaultComponentSize)
            DigitView(type: .seven, componentSize: defaultComponentSize)
            DigitView(type: .eight, componentSize: defaultComponentSize)
            DigitView(type: .nine, componentSize: defaultComponentSize)
        }
        HStack {
            DigitView(type: .hyphn, componentSize: defaultComponentSize)
            DigitView(type: .space, componentSize: defaultComponentSize)
        }
        HStack {
            DigitView(type: .a, componentSize: defaultComponentSize)
            DigitView(type: .e, componentSize: defaultComponentSize)
            DigitView(type: .f, componentSize: defaultComponentSize)
            DigitView(type: .h, componentSize: defaultComponentSize)
            DigitView(type: .m, componentSize: defaultComponentSize)
        }
        HStack {
            DigitView(type: .o, componentSize: defaultComponentSize)
            DigitView(type: .r, componentSize: defaultComponentSize)
            DigitView(type: .s, componentSize: defaultComponentSize)
            DigitView(type: .t, componentSize: defaultComponentSize)
            DigitView(type: .u, componentSize: defaultComponentSize)
            DigitView(type: .w, componentSize: defaultComponentSize)
        }
    }
}

アルファベット・絵文字

7セグメントディスプレイで表現できないものに関しては MatrixView という 5 x 5 のタイルで表現するようにした。所謂ドット絵みたいな形。

表現するViewはこんな感じ。

Coordinate.swift#16-23
struct Coordinate: Equatable {
    let x: Int
    let y: Int

    static func == (ldh: Coordinate, rdh: Coordinate) -> Bool {
        ldh.x == rdh.x && ldh.y == rdh.y
    }
}
MatrixView.swift#10-43
struct MatrixView: View {
    let coordinates: [Coordinate]
    let maxMatrix: Coordinate?
    let componentSize: CGSize
    let color: Color
    var matrix: Coordinate {
        let maxX = maxMatrix?.x ?? coordinates.max { $0.x < $1.x }.map { $0.x } ?? 0
        let maxY = maxMatrix?.y ?? coordinates.max { $0.y < $1.y }.map { $0.y } ?? 0
        return .init(x: maxX + 1, y: maxY + 1)
    }
    let margin: CGFloat

    var body: some View {
        ZStack {
            let blockSize: (CGFloat, CGFloat) = (componentSize.width / CGFloat(matrix.x), componentSize.height / CGFloat(matrix.y))
            ForEach(0..<matrix.x, id: \.self) { x in
                ForEach(0..<matrix.y, id: \.self) { y in
                    if coordinates.contains(Coordinate(x: x, y: y)) {
                        Path { path in
                            path.move(to: CGPoint(x: (CGFloat(x) * blockSize.0 + margin / 2), y: (CGFloat(y) * blockSize.1 + margin / 2)))
                            path.addLine(to: CGPoint(x: (CGFloat(x + 1) * blockSize.0 - margin / 2), y: (CGFloat(y) * blockSize.1 + margin / 2)))
                            path.addLine(to: CGPoint(x: (CGFloat(x + 1) * blockSize.0 - margin / 2), y: (CGFloat(y + 1) * blockSize.1 - margin / 2)))
                            path.addLine(to: CGPoint(x: (CGFloat(x) * blockSize.0 + margin / 2), y: (CGFloat(y + 1) * blockSize.1 - margin / 2)))
                            path.addLine(to: CGPoint(x: (CGFloat(x) * blockSize.0 + margin / 2), y: (CGFloat(y) * blockSize.1 + margin / 2)))
                        }
                        .fill(color)
                    }
                    
                }
            }
        }
        .frame(width: componentSize.width, height: componentSize.height)
    }
}

例えば、↑に以下のようなCoodinateの定義を与えると

WeekdayView.swift#19-85
    var coordinateGrid: [[Coordinate]] {
        switch self {
        case .sunday:
            return [
                // S
                [.init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 0), .init(x: 0, y: 1), .init(x: 1, y: 2), .init(x: 2, y: 2), .init(x: 3, y: 2), .init(x: 4, y: 3), .init(x: 3, y: 4), .init(x: 2, y: 4), .init(x: 1, y: 4), .init(x: 0, y: 4)],
                // U
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 1, y: 4), .init(x: 2, y: 4), .init(x: 3, y: 4), .init(x: 4, y: 3), .init(x: 4, y: 2), .init(x: 4, y: 1), .init(x: 4, y: 0)],
                // N
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 0, y: 4), .init(x: 1, y: 1), .init(x: 2, y: 2), .init(x: 3, y: 3), .init(x: 4, y: 4), .init(x: 4, y: 3), .init(x: 4, y: 2), .init(x: 4, y: 1), .init(x: 4, y: 0)],
            ]
        case .monday:
            return [
                // M
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 0, y: 4), .init(x: 1, y: 1), .init(x: 2, y: 2), .init(x: 3, y: 1), .init(x: 4, y: 0), .init(x: 4, y: 1), .init(x: 4, y: 2), .init(x: 4, y: 3), .init(x: 4, y: 4)],
                // O
                [.init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 1, y: 4), .init(x: 2, y: 4), .init(x: 3, y: 4), .init(x: 4, y: 3), .init(x: 4, y: 2), .init(x: 4, y: 1)],
                // N
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 0, y: 4), .init(x: 1, y: 1), .init(x: 2, y: 2), .init(x: 3, y: 3), .init(x: 4, y: 4), .init(x: 4, y: 3), .init(x: 4, y: 2), .init(x: 4, y: 1), .init(x: 4, y: 0)],
            ]
        case .tuesday:
            return [
                // T
                [.init(x: 0, y: 0), .init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 0), .init(x: 2, y: 1), .init(x: 2, y: 2), .init(x: 2, y: 3), .init(x: 2, y: 4)],
                // U
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 1, y: 4), .init(x: 2, y: 4), .init(x: 3, y: 4), .init(x: 4, y: 3), .init(x: 4, y: 2), .init(x: 4, y: 1), .init(x: 4, y: 0)],
                // E
                [.init(x: 0, y: 0), .init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 0, y: 4), .init(x: 1, y: 2), .init(x: 2, y: 2), .init(x: 3, y: 2), .init(x: 1, y: 4), .init(x: 2, y: 4), .init(x: 3, y: 4), .init(x: 4, y: 4)],
            ]
        case .wednesday:
            return [
                // W
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 1, y: 4), .init(x: 2, y: 1), .init(x: 2, y: 2), .init(x: 2, y: 3), .init(x: 3, y: 4), .init(x: 4, y: 0), .init(x: 4, y: 1), .init(x: 4, y: 2), .init(x: 4, y: 3)],
                // E
                [.init(x: 0, y: 0), .init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 0, y: 4), .init(x: 1, y: 2), .init(x: 2, y: 2), .init(x: 3, y: 2), .init(x: 1, y: 4), .init(x: 2, y: 4), .init(x: 3, y: 4), .init(x: 4, y: 4)],
                // D
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 0, y: 4), .init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 1), .init(x: 4, y: 2), .init(x: 4, y: 3), .init(x: 3, y: 4), .init(x: 2, y: 4), .init(x: 1, y: 4)],
            ]
        case .thursday:
            return [
                // T
                [.init(x: 0, y: 0), .init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 0), .init(x: 2, y: 1), .init(x: 2, y: 2), .init(x: 2, y: 3), .init(x: 2, y: 4)],
                // H
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 0, y: 4), .init(x: 1, y: 2), .init(x: 2, y: 2), .init(x: 3, y: 2), .init(x: 4, y: 0), .init(x: 4, y: 1), .init(x: 4, y: 2), .init(x: 4, y: 3), .init(x: 4, y: 4)],
                // U
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 1, y: 4), .init(x: 2, y: 4), .init(x: 3, y: 4), .init(x: 4, y: 3), .init(x: 4, y: 2), .init(x: 4, y: 1), .init(x: 4, y: 0)],
            ]
        case .friday:
            return [
                // F
                [.init(x: 0, y: 0), .init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 0, y: 4), .init(x: 1, y: 2), .init(x: 2, y: 2), .init(x: 3, y: 2)],
                // R
                [.init(x: 0, y: 0), .init(x: 0, y: 1), .init(x: 0, y: 2), .init(x: 0, y: 3), .init(x: 0, y: 4), .init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 1), .init(x: 3, y: 2), .init(x: 2, y: 2), .init(x: 1, y: 2), .init(x: 4, y: 3), .init(x: 4, y: 4)],
                // I
                [.init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 2, y: 1), .init(x: 2, y: 2), .init(x: 2, y: 3), .init(x: 1, y: 4), .init(x: 2, y: 4), .init(x: 3, y: 4)],
            ]
        case .saturday:
            return [
                // S
                [.init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 0), .init(x: 0, y: 1), .init(x: 1, y: 2), .init(x: 2, y: 2), .init(x: 3, y: 2), .init(x: 4, y: 3), .init(x: 3, y: 4), .init(x: 2, y: 4), .init(x: 1, y: 4), .init(x: 0, y: 4)],
                // A
                [.init(x: 2, y: 0), .init(x: 1, y: 1), .init(x: 3, y: 1), .init(x: 1, y: 2), .init(x: 3, y: 2), .init(x: 0, y: 3), .init(x: 4, y: 3), .init(x: 2, y: 2), .init(x: 0, y: 4), .init(x: 4, y: 4)],
                // T
                [.init(x: 0, y: 0), .init(x: 1, y: 0), .init(x: 2, y: 0), .init(x: 3, y: 0), .init(x: 4, y: 0), .init(x: 2, y: 1), .init(x: 2, y: 2), .init(x: 2, y: 3), .init(x: 2, y: 4)],
            ]
        }
    }

このように描画される。

絵文字だとこのようになる。

    VStack {
        MatrixView(
            coordinates: Mode.dataBank.iconCoordinates,
            maxMatrix: .init(x: 5, y: 5),
            componentSize: CGSize(width: 24, height: 24), color: Color.black, margin: 1
        )
        MatrixView(
            coordinates: Mode.calculator.iconCoordinates,
            maxMatrix: .init(x: 5, y: 5),
            componentSize: CGSize(width: 24, height: 24), color: Color.black, margin: 1
        )
        MatrixView(
            coordinates: Mode.alarm.iconCoordinates,
            maxMatrix: .init(x: 5, y: 5),
            componentSize: CGSize(width: 24, height: 24), color: Color.black, margin: 1
        )
        MatrixView(
            coordinates: Mode.stopWatch.iconCoordinates,
            maxMatrix: .init(x: 5, y: 5),
            componentSize: CGSize(width: 24, height: 24), color: Color.black, margin: 1
        )
        MatrixView(
            coordinates: Mode.dualTime.iconCoordinates,
            maxMatrix: .init(x: 5, y: 5),
            componentSize: CGSize(width: 24, height: 24), color: Color.black, margin: 1
        )
    }

使い方

リポジトリの DigitalClockKitDemoApp 以下にある。

ContentView.swift#10-115
struct ContentView: View {
    let timer = Timer.publish(every: 0.01, on: .current, in: .common).autoconnect()
    @State var currentDate:Date = Date()

    var body: some View {
        NavigationView {
            List {
                let year = Calendar.current.component(.year, from: currentDate)
                let month = Calendar.current.component(.month, from: currentDate)
                let day = Calendar.current.component(.day, from: currentDate)
                let weekday = Calendar.current.component(.weekday, from: currentDate)
                let hour = Calendar.current.component(.hour, from: currentDate)
                let minute = Calendar.current.component(.minute, from: currentDate)
                let second = Calendar.current.component(.second, from: currentDate)
                let miliSecond = Calendar.current.component(.nanosecond, from: currentDate) / 10000000

                // TODO: Components のプレゼン方法を変える
                Section {
                    HStack {
                        Text("Year")
                        Spacer()
                        AgeView(componentSize: mediumComponentSize, year: year)
                    }
                    HStack {
                        Text("Day")
                        Spacer()
                        DayView(componentSize: mediumComponentSize, month: month, day: day)
                    }
                    HStack {
                        Text("Time")
                        Spacer()
                        TimeView(componentSize: mediumComponentSize, timeComponentSize: nil, hasSecondDivider: true, hasSecond: true, hasMiliSecond: true, hour: hour, minute: minute, second: second, miliSecond: miliSecond, is24Hour: false)
                    }
                    HStack {
                        Text("Weekday")
                        Spacer()
                        HStack {
                            WeekdayView(edition: .matrix, componentSize: mediumComponentSize, weekday: weekday)
                                .padding(.trailing, 10)
                            WeekdayView(edition: .digit, componentSize: mediumComponentSize, weekday: weekday)
                        }
                    }
                    HStack {
                        Text("Telephone")
                        Spacer()
                        DigitContainerView(value: "012-3456-7890", componentSize: mediumComponentSize, spacing: 0.4)
                    }
                    HStack {
                        Text("Number")
                        Spacer()
                        DigitContainerView(value: "123.45", componentSize: defaultComponentSize, spacing: 1)
                    }
                    HStack {
                        Text("Icons")
                        Spacer()
                        HStack {
                            MatrixView(coordinates: Mode.dataBank.iconCoordinates, maxMatrix: nil, componentSize: CGSize(width:18, height: 18), color: Color.black, margin: 0)
                            MatrixView(coordinates: Mode.calculator.iconCoordinates, maxMatrix: nil, componentSize: CGSize(width:18, height: 18), color: Color.black, margin: 0)
                            MatrixView(coordinates: Mode.alarm.iconCoordinates, maxMatrix: nil, componentSize: CGSize(width:18, height: 18), color: Color.black, margin: 0)
                            MatrixView(coordinates: Mode.stopWatch.iconCoordinates, maxMatrix: nil, componentSize: CGSize(width:18, height: 18), color: Color.black, margin: 0)
                            MatrixView(coordinates: Mode.dualTime.iconCoordinates, maxMatrix: nil, componentSize: CGSize(width:18, height: 18), color: Color.black, margin: 0)
                        }
                    }
                } header: {
                    Text("COMPONENTS")
                }
                Section {
                    HStack {
                        Spacer()
                        ScrollView(.horizontal) {
                            LazyHStack(spacing: 0) {
                                ForEach(Mode.allCases, id: \.self) { mode in
                                    Watch1View(date: $currentDate, mode: mode, is24Hour: false)
                                        .frame(width: 220)
                                }
                            }
                            .scrollTargetLayout()
                        }
                        .scrollTargetBehavior(.paging)
                        .frame(width: 220)
                        Spacer()
                    }
                    HStack {
                        Spacer()
                        ScrollView(.horizontal) {
                            LazyHStack(spacing: 0) {
                                Watch2View(date: $currentDate, is24Hour: true)
                                    .frame(width: 250)
                            }
                            .scrollTargetLayout()
                        }
                        .scrollTargetBehavior(.paging)
                        .frame(width: 250)
                        Spacer()
                    }
                } header: {
                    Text("SAMPLES")
                }
            }
            .listStyle(InsetGroupedListStyle())
            .navigationTitle("DigitalClockKit")
        }.onReceive(timer){ value in
            currentDate = value
        }
    }
}

↑は画像だが本当は時計部分やストップウォッチが動いている

📝 あとがき

SwiftUIの勉強というよりただPath使った描画集になってしまった感。。
パッケージ公開についても中途半端なので時間作ってちゃんとやりたい

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1