登場人物
- 先生:iOSアプリ開発に詳しい
- 生徒:SwiftUIを勉強中の高校生
1. そもそも何が起きているの?
生徒
先生、iPhoneの「Night Shift」のスライダーって、真ん中に来ると「コツッ」って止まる感じがしますよね?
あれって、どうやって作ってるんですか?
先生
いいところに気づいたね。
結論から言うと、特別なスライダー部品があるわけじゃない。
生徒
え、じゃあ何で止まった感じがするんですか?
先生
理由は3つだけ。
- 真ん中に来たら値を強制的に中央に戻す(スナップ)
- その瞬間だけ軽い振動(haptic)を鳴らす
- これを 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 でも、
この体験は十分に再現できる。