はじめまして。ハニカムラボのnagashimaです。
いつもKotlin/Swift/Unityを使ってスマホアプリの開発をしています。
はじめに
6月頭にWWDC25でまったく新しいデザイン、LiquidGlassの発表がありました。Xcode26でビルドすると、既存のアプリもこの見た目になります。1
レイアウト崩れなど心配だったのでリリース済みのアプリに対して動作確認した結果、いくつか修正の必要なものが出てきました。今回はその中の一つ、UISliderの見た目をLiquidGlassからFlatなものにしてレイアウト崩れに対応します。
元のアプリでは
- ツマミ(thumb)の見た目を変更する(画像を設定する)
- thumbの上に、thumbに追従して位置が変わり、現在の値を表示する吹き出しがある
の二つの仕様があるのでこれらを満たせるように修正します。
以下はXcode26 beta版で確認しています。
正式版での挙動と異なる場合があります。
その1 UISliderを使ってなんとかする
UISliderの機能だけでも近づけることができました。
thumbの見た目を変更する
UISliderにはsetThumbImageという関数があるのでそれを使用することで変更できます。thumbのサイズは画像のサイズに影響されるので、デザインに合わせて画像をリサイズする必要があります。
uiSlider.setThumbImage(UIImage(named: "thumbImage"), for: .normal)
thumbの位置を取得する
これに関しては、スライダーの最大値/最小値/現在値などからthumbのx座標を求めることができます。しかし、setThumbImageを使用して画像を設定していないとthumbの幅がうまく取得できなかったので、中心を求めることができなかったです。(iPhone16Proのシミュレーターで見ると45みたいなので固定でなんとかなるかもしれない...)
setThumbImageを使用した場合は以下のようにすることで、スライダー内でのthumbの中心座標を取得できます。
let proportion = (uiSlider.value - uiSlider.minimumValue) / (uiSlider.maximumValue - uiSlider.minimumValue)
let thumbWidth = uiSlider.thumbImage(for: .normal)?.size.width ?? 0
var x = CGFloat(proportion) * (uiSlider.frame.width - thumbWidth)
x += thumbWidth * 0.5
その2 自作のSliderを作成する
上記のカスタマイズで問題なく動くようになりますが、せっかくなのでスライダーを自作することにしました。元々のアプリの要件を満たしつつ、他でも使えるようにある程度見た目をカスタマイズできるようにします。
スライドの取得
まず画面をスライドしていることを取得する必要があります。これができないことにはスライダーは作成できません。幸いswiftにはUIPanGestureRecognizerという、画面をなぞった動作を取得できるジェスチャーがあります。
適当なviewに
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:))))
をすることで、なぞっている状態がわかります。今回はthumbにジェスチャーを追加したいのでthumbViewにaddします。
なぞっている状態の変化は以下のようにすることで取得できます。変化量をthumbViewに加えることでthumbの位置を変更します。
@objc func panGesture(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: thumbView)
var x = thumbView.frame.origin.x + translation.x
sender.setTranslation(CGPoint.zero, in: thumbView)
}
今回は水平方向のスライダーにしたいので変化量は横(x)だけ使用します。
また、translationは最初の地点からの変化量なので、sender.setTranslation(CGPoint.zero, in: thumbView)
を実行することで変化量をリセットします。
値の受け渡し
UIViewController側にはdelegateを使い、値を受け渡します。
UIPanGestureRecognizerの更新ごとに現在の値(value)とthumbの位置情報(frame)を渡すようにします。UIViewControllerでthumbの位置情報と親viewの位置などから画面全体の位置を計算できるようになります。
protocol FlatSliderDelegate {
func onFlatSliderUpdate(value: Float,frame: CGRect)
}
見た目のカスタマイズ
thumbに関しては、サイズと色もしくは画像が設定できるようにします。Flatデザインでのthumbは円だったので横(直径)のサイズだけ設定できるようにします。
private var thumbWidth : CGFloat = 30
var thumbSize : CGFloat{
get{thumbWidth}
set{thumbWidth = newValue
//スライダー全体のサイズを調整する
}
}
private var thumbView:UIView = UIView()
var thumbColor : UIColor{
get{thumbView.backgroundColor ?? UIColor.blue}
set{thumbView.backgroundColor = newValue
thumbImageView.isHidden = true}
}
private var thumbImageView:UIImageView = UIImageView()
var thumbImage : UIImage?{
get{thumbImageView.image}
set{thumbView.backgroundColor = UIColor.clear
thumbImageView.isHidden = false
thumbImageView.image = newValue}
}
trackに関しては、太さと色を設定できるようにします。
private var barHeight : CGFloat = 6
var trackHeight : CGFloat{
get{barHeight}
set{barHeight = newValue
//スライダー全体のサイズを調整する
}
}
private var barBackGroundView:UIView = UIView()
var trackBackGroundColor : UIColor{
get{barBackGroundView.backgroundColor ?? UIColor.white}
set{barBackGroundView.backgroundColor = newValue}
}
private var barProgressView:UIView = UIView()
var trackProgressColor : UIColor{
get{barProgressView.backgroundColor ?? UIColor.blue}
set{barProgressView.backgroundColor = newValue}
}
初期化
カスタムviewとして使いたいので、おまじないの中で初期化を行います。
sliderのviewは以下の構成になるようにsubviewに追加していきます。
また、thumbViewにaddGestureRecognizerをすることでthumbのスライドを検知できるようにします。
override init(frame: CGRect){
super.init(frame: frame)
setupView()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setupView()
}
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
private func setupView(){
self.addSubview(barBackGroundView)
self.addSubview(barProgressView)
thumbView.addSubview(thumbImageView)
self.addSubview(thumbView)
//デフォルトのカラーを設定する
barBackGroundView.backgroundColor = UIColor.white
barProgressView.backgroundColor = UIColor.blue
thumbImageView.isHidden = true
thumbView.backgroundColor = UIColor.blue
//thumbにジェスチャーを設定する
thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:))))
//スライダー全体のサイズを調整する
}
それぞれのviewのサイズから、スライダー全体のサイズや位置の調整を行います。
private func setupSize(){
//スライダー全体の横幅を決定
sliderWidth = self.frame.width - thumbWidth
let barY = (thumbSize - barHeight) * 0.5
let barRect = CGRect(x: 0 ,y: barY,width: self.frame.width,height: barHeight);
//trackのサイズ・角丸状態にする
barBackGroundView.frame = barRect
barProgressView.frame = barRect
barProgressView.layer.cornerRadius = barHeight * 0.5
barBackGroundView.layer.cornerRadius = barHeight * 0.5
//thumbのサイズ・丸にする
let thumbRect = CGRect(x: 0,y: 0,width: thumbWidth, height: thumbWidth);
thumbView.frame = thumbRect
thumbImageView.frame = thumbRect
thumbView.layer.cornerRadius = thumbWidth * 0.5
//サイズが変わったのでthumbの位置を更新する
let proportion = (currentValue - minValue) / (maxValue - minValue)
let x = CGFloat(proportion) * sliderWidth
}
thumbの位置とbarProgressViewのサイズを変更します。その後delegateでコールバックすることでviewの更新など行えるようにします。
private func updateThumbPoint(x:CGFloat){
thumbView.frame.origin.x = x
barProgressView.frame.size = CGSize(width: thumbView.frame.origin.x + thumbView.frame.width * 0.5, height: barProgressView.frame.height)
//コールバックする
_delegate?.onFlatSliderUpdate(value: currentValue, frame: thumbView.frame)
}
ソースコード
これらを合わせたコードです。コピペで動くと思います。
import UIKit
class FlatSlider:UIView{
protocol FlatSliderDelegate {
func onFlatSliderUpdate(value: Float,frame: CGRect)
}
//MARK: track
//trackの太さ
private var barHeight : CGFloat = 6
var trackHeight : CGFloat{
get{barHeight}
set{barHeight = newValue
setupSize()
}
}
//track背景
private var barBackGroundView:UIView = UIView()
var trackBackGroundColor : UIColor{
get{barBackGroundView.backgroundColor ?? UIColor.white}
set{barBackGroundView.backgroundColor = newValue}
}
//track進捗
private var barProgressView:UIView = UIView()
var trackProgressColor : UIColor{
get{barProgressView.backgroundColor ?? UIColor.blue}
set{barProgressView.backgroundColor = newValue}
}
//MARK: thumb
//thumbのサイズ
private var thumbWidth : CGFloat = 30
var thumbSize : CGFloat{
get{thumbWidth}
set{thumbWidth = newValue
setupSize()
}
}
//thumbの丸のview
private var thumbView:UIView = UIView()
var thumbColor : UIColor{
get{thumbView.backgroundColor ?? UIColor.blue}
set{thumbView.backgroundColor = newValue
thumbImageView.isHidden = true}
}
//thumbの画像
private var thumbImageView:UIImageView = UIImageView()
var thumbImage : UIImage?{
get{thumbImageView.image}
set{thumbView.backgroundColor = UIColor.clear
thumbImageView.isHidden = false
thumbImageView.image = newValue}
}
var thumbFrame :CGRect {
get{ thumbView.frame}
}
//MARK: パラメータ
//最小値
private var minValue : Float = 0
var minimumValue : Float{
get{minValue}
set{minValue = newValue}
}
//最大値
private var maxValue : Float = 100
var maximumValue : Float{
get{maxValue}
set{maxValue = newValue}
}
//現在値
private var currentValue : Float = 50
var value : Float{
get{currentValue}
set{
//min ~ maxに収まっているか確認
if(minValue > newValue){
currentValue = minValue
}else if(maxValue < newValue){
currentValue = maxValue
}else{
currentValue = newValue
}
//thumbの位置を更新する
let proportion = (currentValue - minValue) / (maxValue - minValue)
let x = CGFloat(proportion) * sliderWidth
updateThumbPoint(x:x)
}
}
private var sliderWidth : CGFloat = 0
private var _delegate: FlatSliderDelegate?
var delegate : FlatSliderDelegate?{
get{_delegate}
set{_delegate = newValue}
}
//MARK: -
override init(frame: CGRect){
super.init(frame: frame)
setupView()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setupView()
}
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
//MARK: PRIVATE
//設定されたサイズから事前計算->viewに反映
private func setupSize(){
//スライダー全体の横幅を決定
sliderWidth = self.frame.width - thumbWidth
let barY = (thumbSize - barHeight) * 0.5
let barRect = CGRect(x: 0 ,y: barY,width: self.frame.width,height: barHeight);
//trackのサイズ・角丸状態にする
barBackGroundView.frame = barRect
barProgressView.frame = barRect
barProgressView.layer.cornerRadius = barHeight * 0.5
barBackGroundView.layer.cornerRadius = barHeight * 0.5
//thumbのサイズ・丸にする
let thumbRect = CGRect(x: 0,y: 0,width: thumbWidth, height: thumbWidth);
thumbView.frame = thumbRect
thumbImageView.frame = thumbRect
thumbView.layer.cornerRadius = thumbWidth * 0.5
//サイズが変わったのでthumbの位置を更新する
let proportion = (currentValue - minValue) / (maxValue - minValue)
let x = CGFloat(proportion) * sliderWidth
updateThumbPoint(x:x)
}
//sliderの初期化。デフォルトの状態にする。
private func setupView(){
self.addSubview(barBackGroundView)
self.addSubview(barProgressView)
thumbView.addSubview(thumbImageView)
self.addSubview(thumbView)
barBackGroundView.backgroundColor = UIColor.white
barProgressView.backgroundColor = UIColor.blue
thumbImageView.isHidden = true
thumbView.backgroundColor = UIColor.blue
//thumbにジェスチャーを設定する
thumbView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesture(_:))))
setupSize()
}
//thumbの位置の更新。
private func updateThumbPoint(x:CGFloat){
thumbView.frame.origin.x = x
barProgressView.frame.size = CGSize(width: thumbView.frame.origin.x + thumbView.frame.width * 0.5, height: barProgressView.frame.height)
//コールバックする
_delegate?.onFlatSliderUpdate(value: currentValue, frame: thumbView.frame)
}
@objc func panGesture(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: thumbView)
var x = thumbView.frame.origin.x + translation.x
if(x < 0){
x = 0
}else if(x > sliderWidth){
x = sliderWidth
}
//座標から現在の値を求める
let proportion = maxValue - minValue
currentValue = proportion * Float(x / sliderWidth) + minValue
updateThumbPoint(x:x)
sender.setTranslation(CGPoint.zero, in: thumbView)
}
}
使い方としてはstoryboardなどで適当なUIViewのCustomViewにFlatSliderを設定してください。最大値/最小値/色などのカスタマイズはUIViewControllerの方で行います。
@IBOutlet weak var flatSlider: FlatSlider!
func setupSlider(){
flatSlider.thumbSize = 30
flatSlider.thumbImage = UIImage(named: "slider thumb")!
flatSlider.trackHeight = 6
flatSlider.trackBackGroundColor = UIColor.red
flatSlider.trackProgressColor = UIColor.green
}