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

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

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

## iOS 13 で `UIScrollView` の Indicator の仕様が変わった？

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

<img width="5" alt="scrollBarImage@3x.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/0ea2def7-c0ea-6904-b5d1-3c8805ef82f5.png">

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

```swift
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/40789d81-f53d-e0ef-bc69-431f73857624.gif)

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

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

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

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

<img width="872" alt="scrollbar_iOS12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/ba3f4c00-4d88-0fe0-7170-1c19710e0c1e.png">

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

<img width="1095" alt="scrollbar_iOS13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/96fbf47d-c25c-acf4-b8f0-119aebc5f173.png">

## iOS 13 での対応

* Xcode 11
* iOS 12, 13

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

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

```swift
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/e86fe396-f21d-740a-7e79-c946c5de4115.png)

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

```swift
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/a1e0947e-7257-62e6-ab44-2449b15baf99.gif)

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

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

|ライトモード|ダークモード|
|:--:|:--:|
|![lightmode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/9c7ed7eb-a39e-9352-4c7e-1e9d8d9a7db2.png)|![darkmode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/ed1a5f87-f065-61ea-b74b-eef373afbc3d.png)|

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

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

|ライトモード|ダークモード|
|:--:|:--:|
|![scrollbar_light_red.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/33b8d375-ca0c-5098-95c3-5cc166811d78.png)|![scrollbar_dark_red.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88266/c0e72f2a-0f88-d218-2aad-dbb3b238e26d.png)|

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


## おわりに

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

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