【SwiftUI】再描画の伝播 - @State と @Binding

なんとく「対」で使う雰囲気だけで使っていた @State と @Binding。

そうでもないらしい。

 

🔄 @Binding はいらない

親のカウンターの値を子に渡す。


struct Parent: View {
  // Left side of mutating operator isn't mutable: 'self' is immutable
  // private var count = 0
  @State private var count = 0

  var body: some View {
    VStack {
      Button("\(count)") {
        count += 1
      }
      Child(count: count)
    }
    .padding()
    .background(.yellow)
  }
}

struct Child: View {
  let count: Int

  var body: some View {
    Text("\(count)")
    .padding()
    .background(.white)
  }
}

どうやら、子で @Binding を付けずに受け取っても再描画される。

@State がなくても伝わる? と思ったが不可。

struct 内のプロパティは var でも immutable

らしい。

 

🔄 @Binding を付ける

親に「$」、子に「@Binding」を付けて渡す。


struct Parent: View {
  @State private var count = 0

  var body: some View {
    VStack {
      Button("\(count)") {
        count += 1
      }
      Child(count: $count) // *
    }
    .padding()
    .background(.yellow)
  }
}

struct Child: View {
  @Binding var count: Int // *

  var body: some View {
    Text("\(count)")
    .padding()
    .background(.white)
  }
}

挙動は、最初と同じ。

あれ、@Binding って何だったの?

 

🔄 子で値を更新する

今度は、子でも受け取った値を更新してみます。


struct Parent: View {
  @State private var count = 0

  var body: some View {
    VStack {
      Button("\(count)") {
        count += 1
      }
      Child(count: $count)
    }
    .padding()
    .background(.yellow)
  }
}

struct Child: View {
  @Binding var count: Int

  var body: some View {
    Button("\(count)") {
      count -= 1 // *
    }
    .padding()
    .background(.white)
  }
}

親も更新されます。

双方向に再描画できるようです。

Jetpack Compose では「単方向」のみですが、その前の Android View でいうところの「View Binding」に似ています。

 

🔄 まとめ

  • struct 内のプロパティは var でも変更できないので @State を付ける。
  • その値の変化は子に再描画を @Binding なしで伝播できる。
  • 子で @Binding で受ければ、さらに親にも伝播できる。

参照してるイメージというべきか。

👉 【SwiftUI】@State を 子 View でどう受けるか 🤔 - @Binding hatena-bookmark


Jetpack Compose から SwiftUI に来ましたが今の謎をどうにかしたい 😩

Swift 初心者です。Kotlin からきました。

Apple 公式サンプルコードを3日間 ROM ってました。

👉 sample-backyard-birds/Multiplatform/Birds/BirdsSearchResults.swift at main · apple/sample-backyard-birds hatena-bookmark

どうも納得ができないので書き換えてみました。

動かしてみると3つとも特に問題ないような感じに見えました。

ネットで調べていると、どうもこちらも変化が激しいようで、どの記事を信じたらいいのか分かりません。

コードを見比べながら分からないこと、今後調べたいこと、を洗い出してみます。

 

😩 init()

ここで必要なのですか。なくても引数は同じ。


init(searchText: Binding<String>, @ViewBuilder content: @escaping (Bird) -> Content) {

パフォーマンス的な何か、なのでしょうか。

 

😩 KeyPath


_birds = Query(sort: \.creationDate)

すべて、Xcode 任せなのですが、省略できないんです。Bird が。

「Path」というぐらいなのでどこかに通せばいいと思っているのですが。

 

😩 Property Wrapper

いきなりでてくる _(アンダースコア)付きのこれ ROM 勢としては驚きました。

どこにもないのにいきなり登場してくる。

どこかに何か隠れてますか。


struct BirdsSearchResults<Content: View>: View {
  @Binding var searchText: String

  init(searchText: Binding<String>, ...) {
    _searchText = searchText

以下すべて挙動が同じに見えます。


struct BirdsSearchResults<Content: View>: View {
  @Binding var searchText: String


struct BirdsSearchResults<Content: View>: View {
  private var searchText: String

  init(searchText: Binding<String>, ...) {
    self.searchText = searchText.wrappedValue


struct BirdsSearchResults<Content: View>: View {
  private var searchText: Binding<String>

  init(searchText: Binding<String>, ...) {
    self.searchText = searchText

  var body: some View {
    let searchText = self.searchText.wrappedValue

ここの部分。


_searchText = searchText
_birds = Query(sort: \.creationDate)

以下記事で勉強したのですが。

👉 SwiftUI Property WrappersクラスのwrappedValue・projectedValue一覧表 #Swift - Qiita hatena-bookmark

隠しているものを丸出しに露出させることなどできるのでしょうか。

 

😩 $0

この記述よく見かけます。


birds.filter {
  $0.speciesName.contains(

分かりづらい感じがしますが、なぜ具体的なものに置き換えないのでしょうか。

Kotlin でも同様な記述があり、よく怒られていました。

 

😩 まとめ

対象の Apple サンプルコードは WWDC2023 のものなので約1年前ぐらいで、そんなに古くはないと思っています。

どうかどうかよろしくおねがいします。



【macOS / Android】scrcpy on Mac can't find "… libmbedcrypto.14.dylib" · Genymobile/scrcpy

scrcpy を起動しようとしたら、

何だか厳しそうなエラーが出ました。


dyld[90421]: Library not loaded: '/usr/local/opt/mbedtls/lib/libmbedcrypto.12.dylib'
  Referenced from: '/usr/local/Cellar/librist/0.2.7_1/lib/librist.4.dylib'
  Reason: tried: '/usr/local/opt/mbedtls/lib/libmbedcrypto.12.dylib' (no such file), '/usr/local/lib/libmbedcrypto.12.dylib' (no such file), '/usr/lib/libmbedcrypto.12.dylib' (no such file)
zsh: abort      /usr/local/Cellar/scrcpy/1.24/bin/scrcpy

👉 scrcpy on Mac can't find "…libmbedcrypto.14.dylib" · Issue #4409 · Genymobile/scrcpy hatena-bookmark

みんなが成功しているコメントから以下を実行して再インストールします。


brew uninstall librist --ignore-dependencies
brew uninstall mbedtls --ignore-dependencies
brew reinstall scrcpy

👉 Not working on Mac · Issue #3505 · Genymobile/scrcpy hatena-bookmark

だが、しかし。

 

😅 ERROR: Command not found: [adb], [start-server]


scrcpy

scrcpy v2.2 <https://github.com/Genymobile/scrcpy>
exec: No such file or directory
ERROR: Command not found: [adb], [start-server]
ERROR: (make 'adb' accessible from your PATH or define its fullpath in the ADB environment variable)
INFO: You may install 'adb' by "apt install adb"
ERROR: Could not execute "adb start-server"
ERROR: Could not start adb server
ERROR: Server connection failed

Android Studio インストール済みだったので PATH だけ通していけました!


# .zshrc

# android-tools
export PATH=$PATH:$HOME/Library/Android/sdk/platform-tools

【macOS / Android】scrcpy on Mac can't find "… libmbedcrypto.14.dylib" · Genymobile/scrcpy

 

😅 まとめ

macOS 新規インストールしてから、スクリプトツールたちがエラーを吐きまくってるのだが。


有料 Mac アプリ Bartender で何ができるのか

有料 Mac アプリ Bartender で何ができるのか

👉 Bartender 5 - Take control of your Menu bar hatena-bookmark

現在、2728円の有料アプリです。

 

💻 何がうれしいのか

便利な機能がいろいろありますが、一つだけ必須な機能を挙げてみます。

例えば、Android Studio を開いてみます。

14インチ Mac Book Pro で、解像度はデフォルトです。

有料 Mac アプリ Bartender で何ができるのか

画面上部メニューバーには、利用しているアプリのメニューが表示されます。

Android Studio を開いてみます。

画面上部中央のカメラレンズのあるノッチをまたいでメニューバー幅の6~7割ほどが利用中のアプリである Android Studio のメニュー項目で占められてしまいます。

有料 Mac アプリ Bartender で何ができるのか

macOS 上で常駐で動いてるアプリのアイコンたちは、利用中の Android Studio のメニューたちで隠れてしまっています。

常駐アプリのアイコンを見る方法は、利用中のアプリを Finder などのメニュー数の少ないアプリに切り替えるしかありません。

これを Bartender を使うと、以下のようになります。

有料 Mac アプリ Bartender で何ができるのか

有料 Mac アプリ Bartender で何ができるのか

有料 Mac アプリ Bartender で何ができるのか
メニュー数の多い Android Studio を利用しながらでも隠れた常駐アプリアイコンをメニューバー下部に表示することができます。

有料 Mac アプリ Bartender で何ができるのか

 

💻 まとめ

似たような無料アプリはありますが、メニューバーの下に並列して表示できるアプリは見つけることができませんでしたので、今はこれがベストではないかと思っています。

常駐アプリのアイコンはいつでも確認できる状態にしておきたいです。

無料アプリあれば教えてください。

ソースがオープンであれば参考につくってみたいです。



Hilt で KSP の依存関係の設定 (build.gradle.kts)

kapt から KSP に移行しようとしてハマる。

 

🚀 Dagger + KSP

今回は使わなったが動く。


plugins {
  id("org.jetbrains.kotlin.android") version "1.9.0"
  id("com.google.devtools.ksp") version "1.9.0-1.0.12"
}

dependencies {
  ksp("com.google.dagger:dagger-compiler:2.48") // Dagger compiler
  ksp("com.google.dagger:hilt-compiler:2.48")   // Hilt compiler
}

👉 Dagger KSP hatena-bookmark

 

🚀 Hilt + kapt


// build.gradle.kts (Project)

plugins {
  id("com.google.dagger.hilt.android") version "2.44" apply false
}


// build.gradle.kts (Module)

plugins {
  kotlin("kapt")
  id("com.google.dagger.hilt.android")
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.44")
  kapt("com.google.dagger:hilt-android-compiler:2.44")
}

kapt {
  correctErrorTypes = true
}

👉 Hilt を使用した依存関係の注入  |  Android デベロッパー  |  Android Developers hatena-bookmark

 

🚀 Hilt + KSP


// build.gradle.kts (Project)

plugins {
  id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
  id("com.google.dagger.hilt.android") version "2.44" apply false
}


// build.gradle.kts (Module)

plugins {
  id("com.google.devtools.ksp")
  id("com.google.dagger.hilt.android")
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.44")
  ksp("com.google.dagger:hilt-android-compiler:2.44")
}

👉 kapt から KSP に移行する  |  Android デベロッパー  |  Android Developers hatena-bookmark

👉 Revisions · Hilt + kapt → KSP hatena-bookmark

 

🚀 まとめ

Hilt で公式リファレンスを見ながら、kapt → KSP と順番に変化させていけばスムーズに対応できたのに、Dagger KSP ページを見ながら進んだのがハマった原因。

Version Catalog を使っていくことになりそうなので、抜粋しておく。