| この記事の要点 |
|
Storyboard で作る(UIKit)
1. 配置して属性を決める
1. Library(Cmd + Shift + L)から Slider をドラッグして配置する
2. Attributes inspector で値を設定する
Minimum 0
Maximum 100
Current 50
Continuous Updates ON(操作中も値が届く)
3. 左右の制約を付けて幅を決める
2. コードとつなぐ
スライダー本体を操作したい → Connection: Outlet, Name: volumeSlider
値が変わったことを知りたい → Connection: Action, Name: changeVolume,
Type: UISlider, Event: Value Changed
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var volumeSlider: UISlider!
@IBOutlet weak var valueLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
volumeSlider.minimumValue = 0
volumeSlider.maximumValue = 100
volumeSlider.value = 50
updateLabel()
}
@IBAction func changeVolume(_ sender: UISlider) {
updateLabel()
}
private func updateLabel() {
valueLabel.text = "\(Int(volumeSlider.value)) %"
}
}
イベントは Value Changed です。ボタンと同じ Touch Up Inside にすると、指を離したときしか呼ばれません。
コードだけで作る
private let slider: UISlider = {
let s = UISlider()
s.minimumValue = 0
s.maximumValue = 100
s.value = 50
s.isContinuous = true // 操作中も通知する(既定 true)
s.translatesAutoresizingMaskIntoConstraints = false
return s
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(slider)
NSLayoutConstraint.activate([
slider.leadingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 24),
slider.trailingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -24),
slider.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
slider.addAction(UIAction { [weak self] action in
guard let s = action.sender as? UISlider else { return }
self?.valueLabel.text = "\(Int(s.value)) %"
}, for: .valueChanged)
}
操作中と操作後を分ける
// 操作中: 表示の更新だけ(毎秒何十回も呼ばれる)
slider.addTarget(self, action: #selector(sliding), for: .valueChanged)
// 指を離したとき: 保存や通信はここで
slider.addTarget(self, action: #selector(finished),
for: [.touchUpInside, .touchUpOutside])
@objc private func sliding(_ sender: UISlider) {
valueLabel.text = "\(Int(sender.value)) %"
}
@objc private func finished(_ sender: UISlider) {
UserDefaults.standard.set(sender.value, forKey: "volume")
Task { await api.saveVolume(Int(sender.value)) }
}
valueChanged の中で通信や保存をしてはいけません。指を動かしている間に何十回も実行され、動作が重くなり通信量も増えます。
// 操作が止まってから実行したいとき(デバウンス)
private var task: Task<Void, Never>?
@objc private func sliding(_ sender: UISlider) {
valueLabel.text = "\(Int(sender.value)) %"
task?.cancel()
let value = sender.value
task = Task {
try? await Task.sleep(for: .milliseconds(300))
guard !Task.isCancelled else { return }
await api.saveVolume(Int(value))
}
}
整数として扱う・目盛りに吸着させる
@objc private func sliding(_ sender: UISlider) {
// 1 刻みに丸める
let step: Float = 1
let rounded = (sender.value / step).rounded() * step
if sender.value != rounded {
sender.value = rounded // つまみを吸着させる
}
valueLabel.text = "\(Int(rounded))"
}
// 5 刻みにしたいなら step = 5
// 0.1 刻みなら step = 0.1 として表示は String(format: "%.1f", rounded)
UISlider には SwiftUI の step にあたる機能がありません。刻みが必要なら、このように自分で丸めます。
見た目を変える
slider.minimumTrackTintColor = .systemBlue // つまみより左(値の側)
slider.maximumTrackTintColor = .systemGray4 // つまみより右(残りの側)
slider.thumbTintColor = .white // つまみ
// つまみの画像を差し替える
slider.setThumbImage(UIImage(systemName: "circle.fill"), for: .normal)
// 両端にアイコンを置く
slider.minimumValueImage = UIImage(systemName: "speaker.fill")
slider.maximumValueImage = UIImage(systemName: "speaker.wave.3.fill")
// 縦向きにする(回転させる)
slider.transform = CGAffineTransform(rotationAngle: -.pi / 2)
スライダーは横幅が十分ないと操作しづらくなります。左右に余白を取り、両端のアイコンを付ける場合はさらに幅が必要です。
SwiftUI の場合
struct ContentView: View {
@State private var volume: Double = 50
@State private var isEditing = false
var body: some View {
VStack(spacing: 16) {
Text("\(Int(volume)) %")
Slider(value: $volume, in: 0...100, step: 1) {
Text("音量") // VoiceOver 用のラベル
} minimumValueLabel: {
Image(systemName: "speaker.fill")
} maximumValueLabel: {
Image(systemName: "speaker.wave.3.fill")
} onEditingChanged: { editing in
isEditing = editing
if !editing {
save(Int(volume)) // 指を離したときだけ保存する
}
}
.tint(.blue)
}
.padding()
}
}
SwiftUI では step: を指定するだけで刻みが効き、onEditingChanged で「操作中か終わったか」を受け取れます。UIKit で自作していた処理が引数 1 つで済みます。
他の部品と連動させる
@IBOutlet weak var slider: UISlider!
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var previewView: UIView!
// スライダー → テキストフィールド
@IBAction func sliderChanged(_ sender: UISlider) {
textField.text = String(Int(sender.value))
previewView.alpha = CGFloat(sender.value / 100)
}
// テキストフィールド → スライダー
@IBAction func textChanged(_ sender: UITextField) {
guard let text = sender.text, let v = Float(text) else { return }
// 範囲外の入力を丸めてから反映する
let clamped = min(max(v, slider.minimumValue), slider.maximumValue)
slider.setValue(clamped, animated: true)
previewView.alpha = CGFloat(clamped / 100)
}
setValue(_:animated:) で変えた場合、valueChanged は呼ばれません。コードから変えたときも表示を更新したいなら、更新処理を関数に切り出して両方から呼びます。
private func apply(_ value: Float) {
slider.value = value
textField.text = String(Int(value))
previewView.alpha = CGFloat(value / 100)
}
@IBAction func sliderChanged(_ sender: UISlider) { apply(sender.value) }
override func viewDidLoad() {
super.viewDidLoad()
apply(UserDefaults.standard.float(forKey: "volume")) // 復元
}
音量調整に使う
import AVFoundation
private var player: AVAudioPlayer?
@IBAction func volumeChanged(_ sender: UISlider) {
player?.volume = sender.value // 0.0 〜 1.0
}
AVAudioPlayer.volume は 0.0〜1.0 です。スライダーの範囲を 0〜100 にしているなら sender.value / 100 に直してください。範囲の食い違いは「最小でも音が大きい」という症状になります。
アクセシビリティ
slider.accessibilityLabel = "音量"
slider.accessibilityValue = "\(Int(slider.value)) パーセント"
// 値が変わるたびに読み上げ内容も更新する
@IBAction func changed(_ sender: UISlider) {
sender.accessibilityValue = "\(Int(sender.value)) パーセント"
}
VoiceOver はスライダーを上下スワイプで操作します。accessibilityLabel が無いと「調整可能」としか読まれず、何のスライダーか分かりません。
範囲スライダーが欲しいとき
UISlider はつまみが 1 つだけです。「最小価格〜最大価格」のように 2 つのつまみが要る場合、標準の部品では実現できません。
UISliderを 2 つ並べ、互いの値を超えないように制限するUIControlを継承して自作する- SwiftUI なら 2 つの
@StateとSliderを組み合わせる - 用途によっては
UIStepperやUIPickerViewのほうが正確に選べる
// 2 本のスライダーで範囲を作る(下限が上限を超えないようにする)
@IBAction func minChanged(_ sender: UISlider) {
if sender.value > maxSlider.value {
sender.value = maxSlider.value
}
updateLabel()
}
うまくいかないときの確認
| 症状 | 原因 |
|---|---|
| 動かしても呼ばれない | イベントが Value Changed になっていない/接続が切れている |
| 指を離すまで反応しない | isContinuous = false になっている |
| 値がいつも 0〜1 の範囲になる | minimumValue / maximumValue の設定漏れ(既定は 0〜1) |
| 整数にならない | value は Float。Int() は切り捨てなので rounded() を使う |
| 操作が重い・カクつく | valueChanged の中で保存や通信をしている |
| つまみが端まで行かない | 幅の制約が足りない/親ビューからはみ出している |
関連
子ページ
子ページはありません
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスのソフト一覧|Blender・Metasequoiaと用途別の選び方
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- Visual Studio「デザイナーを表示できません」エラーの原因と対処 2026-10-28 12:06:00
- SSH 認証に失敗しました。再試行してください の対処 2026-10-28 12:06:00
- SSH2 秘密鍵の読み込みに失敗しました の原因と対処 2026-10-28 12:06:00
- TeraTerm のよくあるエラーと対処法完全ガイド — Connection refused / 2026-10-28 12:06:00
- Visual Studio エラー一覧|コンパイルエラー・警告の確認方法と対処 2026-10-28 11:39:57
- Whitelabel Error Page の原因と消し方|Spring Boot のエラーページ 2026-10-28 11:39:57
- Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法 2026-10-28 11:39:57
- SQL WHERE 句完全ガイド 2026-10-28 11:39:57
- SQL JOIN 完全ガイド (INNER/LEFT/RIGHT/FULL OUTER) 2026-10-28 11:39:57
- tomcat の起動 / 停止ログと catalina.log・catalina.out の違い 2026-10-28 10:24:23
- Tomcat の起動確認方法 5 つ|localhost:8080・ポート・プロセス・ログ 2026-10-28 10:24:23
- Oracle 19c COMMITの使い方: 暗黙コミット・自動確定・応答不明時の確認 2026-10-28 09:59:09
- Oracle 19c DELETE文|削除対象の確認・COMMIT前の判断・ROLLBACKの条件 2026-10-28 09:59:09
- Oracle 19c INSERT文|INSERT ALL・SELECT・RETURNINGとMERGEの使い分け 2026-10-28 09:59:09
- Oracle 19cのトランザクション制御|COMMIT・ROLLBACK・分離レベルと接続設定 2026-10-28 09:59:09