【SwiftUI】Default background Color in built-in View Component

上は GroupBox を使って、

下は それに似せて Stack系のViewで書く。


GroupBox("Today's Menu 1") {
  VStack(alignment: .leading) {
    Text("🍛 curry and rice")
    Text("🥗 green salad")
  }
}
.frame(width: 300)

VStack {
  HStack {
    Text("Today's Menu 2")
      .bold()
    Spacer()
  }
  VStack(alignment: .leading) {
    Text("🍛 curry and rice")
    Text("🥗 green salad")
  }
}
.padding()
.frame(width: 300)
.background(
  .background, // *
  in: .rect(cornerRadius: 8)
)

背景色が違う !

 

🧑🏻‍💻 意図しない背景色

特に背景色を意識せずに画面を構成していくと、

微妙に色が違うことありません ?

そもそもデフォルトの背景色はどんな色なのですか。

もちろん、Color クラスに定義されてますよね?

簡単に呼び出せますよね ?

Light / Dark の変化に対応してますよね ?

 

🧑🏻‍💻 地道に調べる

Xcode のカラーピッカーで調べます。


Edit

 ↓

Format

 ↓

Show Colors

RGB で opacity 100% で


//       light               dark  
// 0.949 0.949 0.969 | 0.110 0.110 0.118

でした。

 

🧑🏻‍💻 既存の背景色 (Light/Dark 対応)

これ、SwiftUI.Color で定義されてませんよね。

UIColor の定義でそれらしきのものを見つけて、

数値を取ってみます。


//                           opacity 100%          light               dark
Color(.systemBackground)                  // 1.000 1.000 1.000 | 0.000 0.000 0.000
Color(.secondarySystemBackground)         // 0.949 0.949 0.969 | 0.110 0.110 0.118
Color(.tertiarySystemBackground)          // 1.000 1.000 1.000 | 0.173 0.173 0.180
Color(.systemGroupedBackground)           // 0.949 0.949 0.969 | 0.000 0.000 0.000
Color(.secondarySystemGroupedBackground)  // 1.000 1.000 1.000 | 0.110 0.110 0.118
Color(.tertiarySystemGroupedBackground)   // 0.949 0.949 0.969 | 0.173 0.173 0.180

どうやら、GroubBox デフォルト背景色は、


Color(.secondarySystemBackground)

と同じもののようです。

GroupBoxStyle に定義されているのでしょうが、

見つけられませんでした。

 

🧑🏻‍💻 まとめ

こういうの困りません?

なんだか単に「色」といってもかなり深そう。

👉 大解剖!UIColorファミリー by しもとり | トーク | iOSDC Japan 2020 - fortee.jp hatena-bookmark

iOS まわりの激しい変化の歴史を感じます。

👉 【SwiftUI】CardView のような GroupBox は本当に便利なのか hatena-bookmark


【SwiftUI】View を ドラッグ して移動 する

View をドラッグして動かします。


struct TestDragGesture: View {
  @State private var location = CGPoint(x: 150, y: 150)

  var body: some View {
    ZStack {
      VStack(alignment: .trailing) {
        Text("location.x: \(location.x)")
        Text("location.y: \(location.y)")
      }
      .monospaced()

      Circle()
        .fill(.red.opacity(0.5))
        .frame(width: 100)
        .position(location)
        .gesture(
          DragGesture()
            .onChanged { value in
              location = value.location
            }
        )
    }
  }
}

ドラッグ位置が変化するたびに View の位置を更新できます。

var location: CGPoint
The location of the drag gesture’s current event.

👉 DragGesture.Value | Apple Developer Documentation hatena-bookmark

Circle の position は Viewの中心

であることに対して

DragGesture().onChanged() で取得したドラッグ位置

とずれがあるため、

タップ位置を動かした瞬間に中心位置にジャンプしてしまいます。

なんか違和感がありますね !

 

👩🏼‍💻 startLocation と translation

計算方法を変えます。

以下を使います。

var startLocation: CGPoint
The location of the drag gesture’s first event.

var translation: CGSize
The total translation from the start of the drag gesture to the current event of the drag gesture.

👉 DragGesture.Value | Apple Developer Documentation hatena-bookmark


Circle()
  .fill(.red.opacity(0.5))
  .frame(width: 100)
  .position(location)
  .gesture(
    DragGesture()
      .onChanged { value in
        var newLocation = startLocation ?? location
        newLocation.x += value.translation.width
        newLocation.y += value.translation.height
        location = newLocation
      }
      .updating($startLocation) { _, startLocation, _ in
        startLocation = startLocation ?? location
      }
   )

Circle の中心位置に対して、ドラッグの移動量を増減することで、なめらかに違和感なく動くようになりました !



簡単なコードに見えますが、updating を使った位置情報の入れ替えとか、よくみると深い。

👉 updating(_:body:) | Apple Developer Documentation hatena-bookmark
👉 Move your view around with Drag Gesture in SwiftUI | Sarunw hatena-bookmark

しかし、なんか見通しが悪い。

@GestureState と updating() は使いづらい。

 

👩🏼‍💻 まとめ

これでいきます。



キモは、

「ドラッグの移動量を View のポジションに適用」

「ドラッグ開始時のドラッグ位置とView中心のズレ」



【SwiftUI】.contentTransition(.symbolEffect(.replace))



これよく見かけるのでやってみます。


.contentTransition(.symbolEffect(.replace))

まず、Button の Label のアイコンを、

ある値を見ながら切り替えます。

よくあるパターンなので想像はつくでしょう。


@State private var value = false


Button {
  value.toggle()
} label: {
  Label("Favorite", systemImage: value ? "heart.fill" : "heart")
}

次に .symbolVariant() を使います。

挙動は同じだと思われます。


Button {
  value.toggle()
} label: {
  Label("Favorite", systemImage: "heart")
    .symbolVariant(value ? .fill : .none)
}

ここで、

.contentTransition(.symbolEffect(.replace))

付けます。


Button {
  value.toggle()
} label: {
  Label("Favorite", systemImage: "heart")
    .symbolVariant(value ? .fill : .none)
    .contentTransition(.symbolEffect(.replace))
}

Apple 公式サンプルコードでは、

以下のようにさらに細かく調整されていました。


Button {
  value.toggle()
} label: {
  Label("Favorite", systemImage: "heart")
    .symbolVariant(value ? .fill : .none)
    .contentTransition(
      .symbolEffect(value ? .replace.upUp : .replace.downUp)
    )
}

以上4つを並べて確認します。

微妙にエフェクト具合が違うけど、

どれがいいとはいいづらい。

👉 【SwiftUI】市松模様を背景にする - Checkered Pattern Background 🏁 hatena-bookmark

 

🧑🏻‍💻 まとめ

タイトルどおりの


.contentTransition(.symbolEffect(.replace))

ぐらいがシンプルな記述のわりに見栄えが良いように思います。

みんなが良く使ってる意味が分かりました !