【SwiftUI】色を付ける記述、ややこしすぎませんか?

目指すのはこれ。

緑色の四角の中に、

青色の文字と

オレンジ色の白文字ボタンを置くだけ。

どう書いてますか、色付けの記述。


.foregroundColor()
.foregroundStyle()
.foreground()
.backgroundStyle()
.background() 
.tint() 
// ...

意外と整理できてない私。

 

🤔 四角

VStack を使うとして。


VStack {
}
.frame(width: 100, height: 200)
//.backgroundStyle(.green) // no effect
.background(.green)

シンプルな単色であれば .background()

 

🤔 文字


Text("Hello")
  //.foregroundColor(.blue) // deprecated
  .foregroundStyle(.blue)
  //.tint(.blue) // no effect

将来 deprecated となるのは避けます。

.foreground() は使えないし suggestion にも表示されない。

公式サンプルをみても、

Text の色付けはすべて .foregroundStyle() となっていました。

 

🤔 ボタン

意外と悩みます。

文字とボタン表面の色、

押したり離したりしたときの色の変化、

角の形、

と要素が多いです。

それぞれに使えそうな modifier も複数あります。

多く試して、使いやすものを定型として覚えておきたいです。

まずは、角が丸いボタンなので .buttonStyle(.bordered) を使います。


Button("World") {
}
//.foregroundColor(.white) // deprecated
.foregroundStyle(.white)
//.backgroundStyle(.orange) // no effect
.buttonStyle(.bordered)
//.background(.orange) // break border
//.backgroundStyle(.orange) // no effect
//.tint(.orange) // only border

色と形とエフェクトを同時に満たすことができませんでした。

.tint() でいけそうに思えましたが、

きっちりとしたオレンジが表面に付きません。

次は .buttonStyle(.borderedProminent) を使います。

「prominent」の意味は、

prominent 形
1.〔周囲より〕高くなった、突き出した
2. 人目を引く、目立つ、派手な
3. 有名な、著名な、優れた、卓越した

なので、「目立つ突き出たボタン」ということですね。


Button("World") {
}
.buttonStyle(.borderedProminent)
//.foregroundStyle(.white) // no need default color
//.background(.orange) // wrong background
//.backgroundStyle(.orange) // no effect
.tint(.orange)

これですね !

ボタンを押したときの変化もいいかんじです。

label や makebody などを使おうとも思いましたが、

不必要に長くなりそうなのでやめておきました。

 

🤔 まとめ

最終的にこう書けました。


VStack {
  Text("Hello")
    .foregroundStyle(.blue)
  Button("World") {
  }
  .buttonStyle(.borderedProminent)
  .tint(.orange)
}
.frame(width: 100, height: 100)
.background(.green)

色付けのざっくりシンプルイメージとして、


// テキストやシンボル
.foregroundStyle()

// ボタン表面
.buttonStyle(.borderedProminent)
.tint()

// 固定の背景色
.background()

ぐらいから書き始めるのが良さげに思います。

それで意図通りにいかないときはさらに考えるかんじで。

すべてを覚えられないので簡単なものから順番に。


【SwiftUI】Search TextField を作る

これ。

NavigationStack なしでも、

自在にどこでも置きたいのだが。

👉 SwiftUIでSearchBarが使いたいので自作する | DevelopersIO hatena-bookmark

私も作ってみます、

リストが表示されるやつ。

とりあえず、横に並べて開始します。


HStack {
  Button {
  } label : {
    Image(systemName: "magnifyingglass")
  }

  TextField("Search", text: .constant(""))

  Button {
  } label : {
    Image(systemName: "xmark.circle.fill")
  }
}

 

🧑🏻‍💻 フォーカスの位置の付け替え

デフォルトの TextField のフォーカスの状態を取得して、

エフェクトは無効化する。


TextField("Search", text: .constant(""))
  .focused($focused) 
  .textFieldStyle(.plain)

それらの外にある HStack の border をフォーカス状態によって色を切り替える。


}  // HStack
.border(focused ? Color.accentColor : .gray, width: 1)

 

🧑🏻‍💻 リストのオーバーラップ表示

フォーカスの状態によって、

選択肢のリスト表示を overlay します。

offset 値は入力エリアの高さです。


}  // HStack
.border(focused ? Color.accentColor : .gray, width: 1)
.overlay {
  if focused {
    VStack {
      ForEach(0...3, id: \.self) { i in
        HStack {
          Text("\(i)")
            Spacer()
        }
      }
    }
    .background()
    .offset(y: -45)
  }
}

あとは、レイアウトを調整していけば OK!



 

🧑🏻‍💻 フォーカスのアニメーション

よく見てみると、フォーカスってアニメーションだったんですか !

フォーカス処理部分をバックグラウンドでアニメ化しました。


.background {
  RoundedRectangle(cornerRadius: 8)
    .stroke(.gray, lineWidth: 0.5)
  RoundedRectangle(cornerRadius: 8)
    .stroke(
      focused ? .orange : .clear,
      lineWidth: focused ? 5 : 16
    )
}
.animation(.default, value: focused)

 

🧑🏻‍💻 まとめ



もうググってもしんどいですね。

ChatGPT や copilot などのAI系でも時間がかかるし。

👉 【SwiftUI】#Preview with @Binding arguments hatena-bookmark



【SwiftUI】 スクレイピングで GitHub Contribution Graph をつくる

WEB-API もあるようですが。


❯ curl -s https://github.com/users/benigumocom/contributions

で、HTMLが取得できるというのでやってみる。

わーい。

guard ってなんとなく嫌いだなあ。

👉 GitHub Gist のコードをスクリプトから更新する方法 hatena-bookmark


【SwiftUI】 onChange() の 書き方

iOS17でクロージャ引数が1つのものは Deprecated と警告が出たりするので、


👉 onChange(of:perform:) | Apple Developer Documentation hatena-bookmark

こんなかんじで書いてました。


Text("\(count)")
  .onChange(of: token) { _, newValue in
    // do something ...
  }

クソですね !

 

■ クロージャの引数

クロージャの引数なしと2つのものがあるんですね !


func onChange<V>(
    of value: V,
    initial: Bool = false,
    _ action: @escaping () -> Void
) -> some View where V : Equatable

👉 onChange(of:initial:_:) | Apple Developer Documentation hatena-bookmark


func onChange<V>(
    of value: V,
    initial: Bool = false,
    _ action: @escaping (V, V) -> Void
) -> some View where V : Equatable

👉 onChange(of:initial:_:) | Apple Developer Documentation hatena-bookmark

よって、引数なしのほうを使って、

フツーに以下のように書けば良かったのか。


Text("\(count)")
  .onChange(of: token) {
    print("\(token)")
  }

なるほど。

IDE の選択肢の一番上をなんとなく選択してたのだろうと思います。

きちんと、ドキュメントを見ることって大事。

考えてみると、引数2つの記述


Text("\(count)")
  .onChange(of: token) { oldValue, newValue in
    // do something ...
  }

は、oldValue が必要でない限り使う必要がないな。


【SwiftUI】task の id を使って処理を何度も繰り返す

この id を書き換えることで task のクロージャ内の処理を繰り返すことができるんですね !

onAppear と同様に初回だけだと思ってましたわ。


func task<T>(
    id value: T,
    priority: TaskPriority = .userInitiated,
    _ action: @escaping () async -> Void
) -> some View where T : Equatable

id
The value to observe for changes. The value must conform to the Equatable protocol.

👉 task(id:priority:_:) | Apple Developer Documentation hatena-bookmark

 

■ やってみる

公開されている無料現在時刻取得のAPIを使います。


# curl "http://worldtimeapi.org/api/timezone/Asia/Tokyo"

{
  "abbreviation": "JST",
  "client_ip": "2675:6780:4e0:3200:a181:c317:3902:c121",
  "datetime": "2024-03-26T22:10:13.824894+09:00",
  "day_of_week": 2,
  "day_of_year": 86,
  "dst": false,
  "dst_from": null,
  "dst_offset": 0,
  "dst_until": null,
  "raw_offset": 32400,
  "timezone": "Asia/Tokyo",
  "unixtime": 1711458613,
  "utc_datetime": "2024-03-26T13:10:13.824894+00:00",
  "utc_offset": "+09:00",
  "week_number": 13
}

👉 World Time API: Simple JSON/plain-text API to obtain the current time in, and related data about, a timezone. hatena-bookmark

クライアント側はシンプルな実装にしておきます。


let url = URL(string: "https://worldtimeapi.org/api/timezone/Asia/Tokyo.txt")!
var lines: [String] = []
for try await line in url.lines {
  lines.append(line)
}
print(String(lines[2].components(separatedBy: "T")[1]))

// 22:10:13.824894+09:00

👉 【SwiftUI】シンプルに HTTPリクエスト でお天気情報取得 hatena-bookmark

task (id:) を使っておいて、ボタンで id を更新させます。

 

■ まとめ

「id」って便利な使えるやつなんですね !

👉 【SwiftUI】View の 強制再描画 hatena-bookmark