iOS13でUISegmentedControlの見た目をカスタマイズする


※ 実装/動作確認環境: Xcode 11 beta5 + Simulator
※※ iOS13正式リリース前につき、スクリーンショットは未掲載(100%見た目についての話なのでなんとも物足りない感じですが…)。

背景

iOS13ではUISegmentedControlの見た目が変更される。
それに伴い、見た目(主に色)のカスタマイズをする場合、iOS12以前とは異なる方法で行う必要がある。

さしあたりの対応として行ったことをメモ。
(DarkMode対応なども鑑みるとカスタマイズなしで使うようにUIを設計し直すのが妥当とは思うが…)

iOS12以前

tintColorがビューやラベルの色に適用されるため、ここに任意の色を指定するだけでOKだった。

tintColor = UIColor.orange

iOS13

デフォルトの表示が無彩色となり、tintColorに依存しなくなった。

以下の方法でそれなりにカスタマイズできる。しかしそれぞれ一長一短あり。

selectedSegmentTintColor を使用する方法

tintColor ではなく selectedSegmentTintColor を指定することで、選択中のセグメントの背景色を設定できる。
また、文字色はデフォルトで黒色であるため、設定する背景色によっては文字色も変更したほうがベター。

func configure() {
  let color = UIColor.orange

  selectedSegmentTintColor = color
  setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: .selected)
  setTitleTextAttributes([NSAttributedString.Key.foregroundColor: color], for: .normal)
}
メリット

コード量も少なく、やっていることも明確である点。
基本的にはこの程度のカスタマイズで収めたい。

デメリット

この方法ではビューの背景にある薄いグレーはそのままなので、selectedSegmentTintColor の明度によってはコントラストが弱くなる恐れがある。
(View Hierarchyを確認したところ、SegmentControlのsubview内に半透明のグレー画像が設定されたUIImageViewがある模様)

コントラストに懸念があり、かつ色も変更できない場合は次の方法で薄いグレーの背景を除去するのがよさそう(ただし急場しのぎ。詳細は後述)。

setBackgroundImage() を使用する方法

func configure() {
  let color = UIColor.orange

  // 背景を単色画像に差し替え
  setBackgroundImage(UIColor.clear.toImage(), for: .normal, barMetrics: .default)
  setBackgroundImage(color.toImage(), for: .selected, barMetrics: .default)

  // ラベルのスタイルを設定(選択中のセグメントのラベルがBoldでなくなるので、合わせて設定)
  setTitleTextAttributes(
    [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 13.0),
     NSAttributedString.Key.foregroundColor: UIColor.white],
    for: .selected)
  setTitleTextAttributes([NSAttributedString.Key.foregroundColor: color], for: .normal)

 // グレー領域がなくなったことで境界がわかりづらくなるので、ボーダーを追加
 layer.borderColor = color.cgColor
 layer.borderWidth = 1.0
}

↑で使用している UIColor.toImage() は1x1の単色UIImageを生成する拡張メソッド。実装方法はこちらの記事等を参照。

メリット

背景のグレーがなくなるので、SegmentedControlの色をより自由に決められる点。

デメリット

ビューの構造も変わってしまうらしく、見た目はiOS12以前のSegmentedControlのようになってしまう。

また、たかだか6ステップ程度ではあるが、先の方法よりは回りくどい。
そうまでしてiOS12以前っぽい見た目にするのか?となるので、やはりiOS13標準に則ったUIに変更するのが真っ当だと思う。

参考