SwiftUI.GroupをViewのbodyの直下に置いた時の挙動

開発環境

> xcodebuild -version
Xcode 14.0
Build version 14A309

モチベーション

SwiftUIのGroupを具体例を踏まえて理解しようと思って実験していたら意味わからんくなったので記事に残す。

SwiftUI.Group

Group | Apple Developer Documentation

  • HStack, VStack, Sectionのようにviewに影響を与えることなく、複数のviewを一つのインスタンスにまとめられる
  • Groupに適用したmodifierはGroupが持つ全ての子要素に対してそれぞれ影響を及ぼす

Use a group to collect multiple views into a single instance(A), without affecting the layout of those views(B), like an HStack, VStack, or Section would(C).

この文、個人的に手癖で翻訳に突っ込む前に英文読んだほうが良かった文章だった。ABに対してCがかかっているのではなく、Aに対してBCがかかっている。英語読もう。はい。Bの後のカンマがなければABに対してCがかかるようになるのかな。

これが、

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Group {
                Rectangle()
                    .frame(width: 50, height: 50)
                    .foregroundColor(.blue)
                Rectangle()
                    .frame(width: 50, height: 50)
                    .foregroundColor(.blue)
                Rectangle()
                    .frame(width: 50, height: 50)
                    .foregroundColor(.blue)
            }
        }
    }
}

こうまとめられる

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Group {
                Rectangle()
                Rectangle()
                Rectangle()
            }
            .frame(width: 50, height: 50)
            .foregroundColor(.blue)
        }
    }
}

VStackを剥がすと?

import SwiftUI

struct ContentView: View {
    var body: some View {
        Group {
            Rectangle()
            Rectangle()
            Rectangle()
        }
        .frame(width: 50, height: 50)
        .foregroundColor(.blue)
    }
}

上記を見ると、Rectangle3つの全体に対してframe modifilerがかかっているように見える。ということは必ずしもGroupに付与したmodifierはGroupの子要素に反映されるわけではない?また、上記のGroupをVStackに変えると同様の見た目になった。

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Rectangle()
            Rectangle()
            Rectangle()
        }
        .frame(width: 50, height: 50)
        .foregroundColor(.blue)
    }
}

シンプルなVStackの利用のため、この挙動は理解できる。

以上を踏まえると、公式ドキュメントの以下の記述が実情と異なる?

After creating a group, any modifier you apply to the group affects all of that group’s members.

公式ドキュメントのサンプルコード

GroupのドキュメントのOverview項にVStackを使わないGroupのサンプルコードがあるが、手元で試すとGroupの子要素に対してmodifierが効いている。

import SwiftUI


struct ContentView: View {
    var body: some View {
        Group {
            Text("SwiftUI")
            Text("Combine")
            Text("Swift System")
        }
        .font(.headline)
    }
}

また、実装中に気になったのがシミュレーターとプレビューの挙動の違い。シミュレーターはVStack風に垂直方向に要素が積み上がっているが、プレビューだとそれぞれ独立して存在する。

おわりに

本記事の内容だけで判断するとGroupをView.Bodyのルートで持った場合、そのGroupにframe modifierを設定するとGroupの子要素それぞれに対してではなく、Group全体にframe modifierがかかる。

Groupの子要素にframe modifierを適用したい場合はVStackなどで包んであげると良さそう。

参考

Swift Chartsを使ったグラフ構築 走行距離編

iOS 16が公開された! Swift Charts触っていくぞ!

本記事では実装の細部には触れず、WWDC22のセッション内容に沿ってグラフを構築する。

開発環境

> xcodebuild -version
Xcode 14.0
Build version 14A309

モチベーション

運動のモチベーションにムラがある。継続的に運動を続けたい。アプローチとして、iOS 16から利用できるようになったSwift Chartsを使って過去の運動のデータを可視化する。ただグラフを構築するのではなく、WWDC22のセッションにあるように目的を意識してグラフを構築する。今回は「月ごとのサイクリングとランニングの走行距離を確認して、月ごとの走行距離の推移を見たい」という目的を持ってグラフを構築する。

WWDC22のセッション

Swift Charts関連で参考にしたセッションは以下。本記事では基本的に以下の2つ目のセッションの内容に沿ってグラフを構築した。

つくったもの

グラフ構築の手順

  1. グラフの目的を考える
  2. グラフの表示方法を考える

1. グラフの目的を考える

モチベーションにも書いたが、"運動を継続的に行うために、月ごとのサイクリングとランニングの走行距離を確認して、月ごとの走行距離の推移を見たい" が目的。

コードに落とし込むために言い換えると、"過去一年間の月ごとのサイクリングとランニングの走行距離の合算をグラフで表示する"。

2. グラフの表示内容を考える

  • 月ごとの走行距離を表示し、浮き沈みを見てパターンがあるか調べる
  • 過去一年間の任意の月の走行距離を調べる
  • 最高走行距離を記録した月を調べる
  • 外れ値
  • 気温などの外的要因による走行距離の変動を調べる

などなど、色々考えられる。今回の題材だと、全体像として月ごとのパターンを見たい。また、特定の月の走行距離を調べたい。ということで、以下に着目してグラフを構築する。

  • 月ごとに走行距離を表示する
  • 何かしらのアクションで特定の月の走行距離を表示する

3. グラフの表示形式を考える

今回は並べてデータの変化具合を見たいので棒グラフを選択する。走行距離のような累積的な概念は棒グラフが適していそう。

棒グラフでグラフを構築した。横軸は時間、縦軸は走行距離(km)。

4. グラフの軸を考える

構築したグラフの軸の粒度を考える。

横軸は時間。今回は月ごとのデータを表示するので最も細かい粒度は一月(ひとつき)になる。最も荒い粒度は半月? 今回は月ごとの走行距離を表示し、浮き沈みを見てパターンがあるか調べたい目的がある。各データが何月かひと目で分かるようにしたいのでデータ毎に何月かを表示する。

縦軸は走行距離。上記の添付画像の通りで問題ないのでこのままでいく。

5. グラフの説明文を考える

グラフをより分かりやすくするために説明文を追加する。

要約文を表示することでグラフの全体像をユーザーに伝えられるようになった。

6. グラフのインタラクションを考える

インタラクションはタップやVoiceOverなど様々な観点があるが、今回はグラフをタップしたときの挙動に絞って考える。 が、公式のデモアプリ*1を見る感じ、便利プロパティがあるわけではなくGeometryReaderを使って素直に実装していく必要があり、面倒くさいので今回はスキップ。(え?)

7. グラフの色を考える

東京都カラーユニバーサルデザインガイドラインを参考にして多様な色覚に対してデータを区別できるようにグラフに色を加える。

青と緑はP型、D型のどちらの色覚でも区別ができそうだったので採用した。色を加えることでサイクリングとランニングの割合が可視化されるようになった。

今後の展望

参考

dotfiles盆栽記3: pecoを利用した曖昧git switch

github.com

モチベーション

git switchする際に、毎回事前にgit branchでswitch対象のbranchを調べている。手間なのと、曖昧検索ができるpecoを使って便利に自動化したい。

こうした

alias gsp='git switch `git branch | peco | sed -e "s/*//g"`'

解説

まず、git branchをpecoで曖昧検索したものをgit switchに渡すようにする。

> git switch `git branch | peco`

` ` はgrave accentと呼ばれ、zshの展開の文脈ではCommand Substitution*1に分類される。上記のコマンドだとCommand Substitution以外の展開項目はないため、git branch | peco が最も早く実行される。

先のコマンドで表題の目的はほとんど叶えられるが、現在のブランチを指定したときにエラーが出る。

> git switch `git branch | peco`
fatal: only one reference expected

これは、以下のようにgit branchでは現在のブランチ名の前に * が表示されるため、pecoで選択したときにこのアスタリスクも含まれてしまい、それがgit switchに渡ってしまうために起こる。

> git branch | peco  
* develop

素直にsedで置換してあげることで解決した。

# 現在のブランチが `develop` -> pecoで `develop` を選択
> git switch `git branch | peco | sed -e 's/*//g'`
Already on 'develop'
Your branch is up to date with 'origin/develop'.

参考