ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
| この記事の要点 |
|
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=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
子ページはありません
人気ページ
- 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アノテーションとは
最近更新/作成されたページ
- 【django】ログイン 認証機能 2026-09-08 03:22:09
- MySQL ERROR 1063 Incorrect column specifier for column|原因と直し方 2026-09-08 03:11:09
- 【PHPエラー】Object of class stdClass could not be converted to string 2026-09-07 07:46:04
- Julia Genie ローカル開発サーバ起動完全ガイド 2026-09-07 07:46:04
- Wi-Fi とは|規格と世代(Wi-Fi 4〜7)・周波数帯・CSMA/CA・WPA3 NEW 2026-09-07 07:45:13
- set コマンドでシェルオプションと位置パラメータを操作 | bash 2026-09-07 07:45:13
- JPEG(.jpg/.jpeg)画像形式の完全ガイド — 仕様・マジックナンバー・他形式との比較・EXIF 2026-09-07 07:45:13
- UE5 Get Overlapping Actorsで特定クラスだけ処理|Class Filter・Cast To 2026-09-07 07:45:13
- Linuxで特定拡張子のファイルを再帰削除|find -deleteの安全手順 2026-09-07 07:45:13
- Django CBVでテンプレートに値を渡す|get_context_data・extra_context 2026-09-07 07:45:13
- Linuxで複数ファイルの文字列を一括置換|find・sed・xargsの安全手順 2026-09-07 07:45:13
- Laravelルートグループ|prefix・middleware・nameで一括管理 2026-09-07 07:45:13
- Linux whichコマンドの使い方|絶対パス取得とtype・command -vの違い 2026-09-07 07:45:13
- Linux catコマンドの使い方|行番号表示・複数ファイル連結 2026-09-07 07:45:13
- unable to execute 'gcc': No such file or directory 2026-09-07 07:45:13
コメントを削除してもよろしいでしょうか?