2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LiquidGlassでレイアウトが崩れた人へ(UISlider編)

Last updated at Posted at 2025-07-01

はじめまして。ハニカムラボの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の中心座標を取得できます。

uiViewController.swift
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の位置を変更します。

FlatSlider.swift
@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の位置などから画面全体の位置を計算できるようになります。

FlatSlider.swift
protocol FlatSliderDelegate {
    func onFlatSliderUpdate(value: Float,frame: CGRect)
}

見た目のカスタマイズ

thumbに関しては、サイズと色もしくは画像が設定できるようにします。Flatデザインでのthumbは円だったので横(直径)のサイズだけ設定できるようにします。

FlatSlider.swift
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に関しては、太さと色を設定できるようにします。

FlatSlider.swift
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のスライドを検知できるようにします。

FlatSlider.swift
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のサイズから、スライダー全体のサイズや位置の調整を行います。

FlatSlider.swift
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の更新など行えるようにします。

FlatSlider.swift
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)
}

ソースコード

これらを合わせたコードです。コピペで動くと思います。

FlatSlider.swift
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の方で行います。

SampleViewController.swift
    @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
    }
  1. Xcode26ではこの記事にあるように無効化することもできますが、一時対応なので

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?