LoginSignup
8
4

More than 3 years have passed since last update.

iOS 13 端末で UIScrollView の Indicator に画像を設定するコード部分がクラッシュしていたのでその対応

Posted at

はじめに

iOS 13 がリリースされましたね。
例年になく大きな変更があり,皆さん様々な対応を行ったことと思います。
私も主に個人アプリで対応してギリギリ 9/20 にリリースできました。

今回はその iOS 13 対応中に見つけたクラッシュとその対応の記事になります。

iOS 13 で UIScrollView の Indicator の仕様が変わった?

UIScrollView の Indicator ってありますよね。
スクロールした際に右端に表示されるもの(今回の話は Vertical Indicator の方)です。
そのスクロールバーに下記の画像を設定していました。

scrollBarImage@3x.png

iOS 12 までは下記のようなコードで動いていました。
(今見ると恐いな・・・😓)

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let verticalScrollIndicator: UIImageView = (scrollView.subviews[(scrollView.subviews.count - 1)] as! UIImageView)
        verticalScrollIndicator.image = UIImage(named: "scrollBarImage")
    }
}

RPReplay_Final1569158455.gif

しかし,このコードだと iOS 13 の端末でスクロールしようとしたらクラッシュします。
クラッシュした際のログは下記の通りです。

Could not cast value of type '_UIScrollViewScrollIndicator' (0x7fff895ad0b0) to 'UIImageView' (0x7fff895cde00).

UIImageView で受けられなくなっているようで,
View Hierarchy で確認してみると・・・

iOS 12 の場合は,インジケータは UIImageView になっている。
こいつを取得して画像をセットしている。

scrollbar_iOS12.png

iOS 13 端末だと UIView になっている。
なるほど UIImageView でキャストできずクラッシュするわけだ。

scrollbar_iOS13.png

iOS 13 での対応

  • Xcode 11
  • iOS 12, 13

サンプルコードは GitHub にあります。気になる方はご覧ください。
https://github.com/MilanistaDev/ScrollBarWithImage

UIView になっているなら,
単純に UIImageViewaddSubView すればいいと考えました。
AutoLayout も使って Indicator いっぱいに画像を表示させます。

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // スクロールバーに画像をセット
        if let verticalScrollIndicator: UIView = scrollView.subviews.last {
            let scrollBarImageView = UIImageView.init(frame: .zero)
            scrollBarImageView.contentMode = .scaleToFill
            scrollBarImageView.image = UIImage(named: "scrollBarImage")
            scrollBarImageView.translatesAutoresizingMaskIntoConstraints = false
            verticalScrollIndicator.addSubview(scrollBarImageView)

            // AutoLayout で ScrollBar いっぱいに画像を設置
            scrollBarImageView.leadingAnchor.constraint(
                equalTo: verticalScrollIndicator.leadingAnchor,
                constant: 0.0
                ).isActive = true
            scrollBarImageView.bottomAnchor.constraint(
                equalTo: verticalScrollIndicator.bottomAnchor,
                constant: 0.0
                ).isActive = true
            scrollBarImageView.trailingAnchor.constraint(
                equalTo: verticalScrollIndicator.trailingAnchor,
                constant: 0.0
                ).isActive = true
            scrollBarImageView.topAnchor.constraint(
                equalTo: verticalScrollIndicator.topAnchor,
                constant: 0.0
            ).isActive = true
        }
    }
}

これでうまくいきますが,このままだと大変なことになっているだろうなと🤔
画面を見てもわからないけど,やはりビーム出してますね。。。

scrollbar_beam.png

addSubView された画像を都度削除するために,
Indicator の subViews を削除する処理を入れます。

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // スクロールバーに画像をセット
        if let verticalScrollIndicator: UIView = scrollView.subviews.last {
            // 追加した画像があれば削除
            let subviews = verticalScrollIndicator.subviews
            for subview in subviews {
                subview.removeFromSuperview()
            }
            let scrollBarImageView = UIImageView.init(frame: .zero)
            scrollBarImageView.contentMode = .scaleToFill
            scrollBarImageView.image = UIImage(named: "scrollBarImage")
            scrollBarImageView.translatesAutoresizingMaskIntoConstraints = false
            verticalScrollIndicator.addSubview(scrollBarImageView)
            // ScrollBar いっぱいに画像を設置
            scrollBarImageView.leadingAnchor.constraint(
                equalTo: verticalScrollIndicator.leadingAnchor,
                constant: 0.0
                ).isActive = true
            scrollBarImageView.bottomAnchor.constraint(
                equalTo: verticalScrollIndicator.bottomAnchor,
                constant: 0.0
                ).isActive = true
            scrollBarImageView.trailingAnchor.constraint(
                equalTo: verticalScrollIndicator.trailingAnchor,
                constant: 0.0
                ).isActive = true
            scrollBarImageView.topAnchor.constraint(
                equalTo: verticalScrollIndicator.topAnchor,
                constant: 0.0
            ).isActive = true
        }
    }
}

これでうまくいきました!

RPReplay_Final1569165176.gif

(おまけ)Indicator の BackgroundColor を設定する?

今回は画像を addSubView しているので,
ライトモードでもダークモードでも同じ色になります(私はこうしたかった)。
少し色味を変えたいのであれば,
アセットでライトモード用の画像とダークモード用の画像を用意すればいいですね。

ライトモード ダークモード
lightmode.png darkmode.png

単色などであれば,取得した Indicator の UIView
BackgroundColor を設定すれば良いとも思うんですが,
どうも UIScrollViewIndicatorStyle の設定があるためか綺麗な色が出せません。
単純に設定しない .default だと
ライトモード では .blackダークモード では .white が設定されているように見えます。

試しに UIColor.red を設定してみたのが下記になります。

ライトモード ダークモード
scrollbar_light_red.png scrollbar_dark_red.png

UIScrollView の Indicator を触るような実装をすることは
ほとんどないとは思いますが,画像で対応したほうが綺麗に対応できると思いました。

おわりに

今回は,iOS 13 対応中に出た UIScrollView の Indicator 周りのバグについて書きました。
iOS 13 単体の不具合が多い気がします。iOS 13.1 のリリースが待たれますね。

こうしたほうが良い,私はこうしてるよー等ありましたらご教示お願いいたします🙇‍♀️
ご覧いただきありがとうございました。

8
4
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
8
4