| この記事の要点 |
|
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 / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- プロジェクトをTomcatプロジェクトとして認識させる方法 2026-10-07 22:32:50
- MySQLの1366 Incorrect string value|Laravelの文字コード・絵文字エラー 2026-10-07 21:54:03
- curlの証明書ホスト名不一致|旧エラー51・現行60の確認と対処 2026-10-07 21:54:03
- LaravelのMassAssignmentException|fillableの原因と安全な対処 2026-10-07 21:54:03
- Eclipse で Tomcat の起動ログがコンソールに出ない時の確認手順 2026-10-07 21:54:02
- MySQLにおける中央値(Median)の導き方(バージョン8未満) 2026-10-07 13:49:45
- getInputForward 2026-10-07 13:41:15
- JSONから配列に変換 2026-10-07 13:41:15
- ビューから値をモデルに格納しコントローラーで受け取る方法 2026-10-07 13:23:41
- Laravelのテーブル作成と定義変更|マイグレーション・up/down・注意点 2026-10-07 13:23:41
- NumPy 配列に要素を追加する方法 (append / concatenate) 2026-10-07 13:23:41
- MariaDB・MySQLで現在日時を取得する方法|NOW・タイムゾーン・保存型 2026-10-07 13:13:36
- 【django】テンプレートで定数を使用する方法 2026-10-07 13:10:15
- Spring BootにおけるApplication.propertiesの環境依存設定の分割方法 2026-10-07 12:09:35
- Not supported for DML operations【Springエラー】 2026-10-07 11:09:38