0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【SwiftUI】Night Shiftのスライダーから学ぶ「止まったように感じるUI」の作り方

Posted at

登場人物

  • 先生:iOSアプリ開発に詳しい
  • 生徒:SwiftUIを勉強中の高校生

1. そもそも何が起きているの?

生徒
先生、iPhoneの「Night Shift」のスライダーって、真ん中に来ると「コツッ」って止まる感じがしますよね?
あれって、どうやって作ってるんですか?

先生
いいところに気づいたね。
結論から言うと、特別なスライダー部品があるわけじゃない

生徒
え、じゃあ何で止まった感じがするんですか?

先生
理由は3つだけ。

  1. 真ん中に来たら値を強制的に中央に戻す(スナップ)
  2. その瞬間だけ軽い振動(haptic)を鳴らす
  3. これを 1回だけ 起こす

この組み合わせで、人は「引っかかった」と感じる。

2. SwiftUIのSliderは止まれない?

生徒
でも、Slider ってただ動くだけですよね?
「止まる」機能なんて無さそうですが…。

先生
その通り。
Slider 自体には「物理的に止める」機能はない。

だからこう考える。

  • 止める ❌
  • 止まったように感じさせる ⭕️

プログラムで「中央に来たら値を戻す」ことで、結果的に止まったように見える。

3. 実装の考え方(コードを見る前に)

先生
今回やることは、たったこれだけ。

  • 値の範囲:-1 ... 1
  • 中央:0.0
  • 中央付近(±0.05)に入ったら…
    • 値を 0.0 に戻す
    • 軽い振動を 1回だけ 出す

生徒
「1回だけ」って、どうやって?

先生
「今、中央ゾーンにいるかどうか」を @State で覚える。

4. 完成コード(Night Shift風スライダー)

import SwiftUI
import UIKit

struct NightModeLevelView: View {

    @Binding var value: Double

    private let snapCenter: Double = 0.0
    private let snapThreshold: Double = 0.05

    // 中央ゾーンにいるかどうか
    @State private var isInSnapZone: Bool = false

    // 触覚フィードバック
    private let haptic = UIImpactFeedbackGenerator(style: .light)

    var body: some View {

        VStack(spacing: 0) {

            // ラベル
            HStack {
                Text("暗く")
                Spacer()
                Text("暖かく")
            }

            ZStack {

                // 目盛り
                HStack {
                    Rectangle().frame(width: 2, height: 8)
                    Spacer()
                    Rectangle().frame(width: 2, height: 8)
                    Spacer()
                    Rectangle().frame(width: 2, height: 8)
                }
                .foregroundStyle(.secondary)

                // スライダー
                Slider(value: $value, in: -1...1)
                    .onAppear {
                        haptic.prepare()
                    }
                    .onChange(of: value) { newValue in
                        let nowInZone = abs(newValue - snapCenter) < snapThreshold

                        // 中央に入った瞬間
                        if nowInZone && !isInSnapZone {
                            isInSnapZone = true
                            value = snapCenter
                            haptic.impactOccurred()
                            haptic.prepare()
                            return
                        }

                        // 中央から出た
                        if !nowInZone && isInSnapZone {
                            isInSnapZone = false
                            haptic.prepare()
                        }
                    }
            }
            .padding(.horizontal, 8)
        }
    }
}

5. なぜ「止まった感じ」が出るのか

生徒
これで、なんで止まったように感じるんですか?

先生
理由はシンプル。

  • 指は動いている
  • でも値は中央に戻される
  • 同時に「コツッ」と振動する

人の脳はこれを
「物理的に引っかかった」
と錯覚する。

6. よくある失敗ポイント

先生
初心者がよくやるミスも言っておこう。

  • 中央付近で 毎回 haptic を鳴らす
    → ブルブル震えて不快になる
  • UIImpactFeedbackGenerator を毎回生成する
    → 反応が遅くなり、体験が悪化する

だから次の2点が重要。

  • 状態で「1回だけ」を制御する
  • haptic は使い回す

7. まとめ

生徒
なるほど……
Night Shiftのスライダーって、実は錯覚なんですね。

先生
その通り。

今日のポイント

  • Slider は物理的に「止める」ことはできない
  • しかし
    • スナップ(値の吸着)
    • 触覚フィードバック

この2つを組み合わせることで
「止まったように感じる UI」 を作れる。

おわりに

UI では「仕様として正しいか」よりも、
人がどう感じるか が重要になる場面が多い。

Night Shift のスライダーは、その好例。

SwiftUI でも、
この体験は十分に再現できる。

0
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?