Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Sansan アプリにおける iOS 12 のサポート終了対応

こんにちは、技術本部 Mobile Application グループの山名です。

WWDC から早くも1ヶ月が経とうとしており、時の流れの速さを感じています...
(ちなみに自分は今年も What’s new in UIKit があることに絶望したのが一番印象に残っています)

そんなことはさておき、今回は Sansan アプリにおける iOS 12 のサポート終了対応についてお話しします。
iOS 12 というと大分前なので、既に世間には沢山知見が溜まっていて楽勝やろなあ…と高を括っていたのですが、思わぬ地雷を踏んでしまったのでぜひ最後までお楽しみください。

修正した内容

KeyWindow の取得方法

iOS 12 までは UIApplication.shared.keyWindow で取得できていましたが、iOS 13 から Scene の概念が持ち込まれたことで Deprecated になり、↓の Warning が発生していました。

  • 'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes

各 Scene における KeyWindow は UIWindowScene.keyWindow で取得できますが、この API は iOS 15 以降でのみ使用可能なため、それ以前では UIWindow.isKeyWindowUIWindowScene.windows をフィルタする必要があります。
またそもそも UIWindowScene をどこから取得するかですが、現状の Sansan が複数 Scene に対応していないことを鑑み、お安く済む UIApplication.connectedScenes を用いた取得方法を採用しました(参考)。
具体的には以下のようなコードになります。

let windowScene = UIApplication.shared.connectedScenes
    .filter { $0.activationState == .foregroundActive }
    .compactMap { $0 as? UIWindowScene }
    .first

if #available(iOS 15, *) {
    return windowScene.keyWindow
} else {
    return windowScene.windows.first { $0.isKeyWindow }
}

(UIScene.ActivationState などに関しては、Managing Your App's Life Cycle が図付きで分かりやすいのでよろしければどうぞ)

ただ単一 Scene しかない前提のコードなのであまり積極的に使用したくなく、本対応以降は UIView.window?.windowScene を使用する方向にしています。
これを最初から活用しなかった理由は UIView.window の Discussion にあるように、場合によっては window が取得できないためです。

This property is nil  if the view has not yet been added to a window.

この Warning は発生箇所が非常に多く、なおかつ結構な割合で window が nil のタイミングで処理を行なっていました。
そのため全てを修正するとかなりのコストがかかることが予想されたため、一旦先述した暫定対応コードで済ませました。

statusBar 系の取得方法

これも KeyWindow と同じく、複数 Sceneの概念が持ち込まれたことによって発生していました。
Sansan だと statusBarFrame , statusBarOrientation の使用箇所で以下の Warning が出ていました。

  • 'statusBarFrame' was deprecated in iOS 13.0: Use the statusBarManager property of the window scene instead.
  • 'statusBarOrientation' was deprecated in iOS 13.0: Use the interfaceOrientation property of the window scene instead.

statusBarManager , interfaceOrientation 共に UIWindowScene のプロパティなので、先述した方法で取得し、アクセスする形で対応しました。
また Sansan では対象外だった statusBarStyle を使用している場合も同様の Warning が出ますが、UIStatusBarManager が持っているので statusBarFrame と同じ方法で対応できます。

UIActivityIndicatorView.Style の指定方法

iOS 12 までは UIActivityIndicatorView.Stylegray , white , whiteLarge と色の情報も含んでいましたが、iOS 13 ではそれらが Deprecated となり、単純にサイズを表す medium , large に置き換わりました。
各スタイルを並べるとこんな感じです。

UIActivityIndicatorView.Style の一覧

図の通り iOS 13 からはデフォルトカラーがグレーになったため、これまで通りホワイトで表示したい場合は明示的に指定してやる必要があります。

// Before
indicator.style = .white

// After
indicator.style = .medium
indicator.color = .white

知っていればなんてことない話ですが、知らないと Warning のメッセージで勘違いを起こしそうになるので注意が必要です。
例えば white を使用している場合は以下のメッセージが出ます。

  • 'white' was deprecated in iOS 13.0: renamed to 'UIActivityIndicatorView.Style.medium'

これだけ見るとあたかも medium にすればよいように思えますが、先述した通り色を追加で指定する必要があるため、忘れずに対応しましょう。
(まあ、動作確認する過程で気づけるのでそこまで罠でもないですが…)

特定 OS でのみ TabBar のテキストが見切れてしまう

iOS 13.3 での TabBar

iOS 13.4 での TabBar

あまりにも謎すぎる挙動ですが、iOS 13 から追加された UIBarAppearance 系を使用してフォントサイズやカラーをカスタマイズすると、13.4 未満でテキストが見切れてしまいます。
自分含め数人で調査したのですが解決には至らず、結局 if #available(iOS 14, *) で見なかったことにしました…

まあおそらく UIBarAppearance 系は 13 で導入されたばかりなので、何かしらのバグを含んでいたのかな…と勝手に想像しています。
一応再現するコードを貼っておきますので、もしお暇でしたら遊んでみてください。

class ViewController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // size は 10、weight は medium より大きくすると発生する(どちらかを極端に小さくすれば収まることもある)
        let font: UIFont = .systemFont(ofSize: 11, weight: .medium)

        let tabBarItemAppearance = UITabBarItemAppearance()
        tabBarItemAppearance.selected.titleTextAttributes = [.font: font]
        // foregroundColor を指定しないと非選択のタブは収まるが、指定すると収まらない
        tabBarItemAppearance.normal.titleTextAttributes = [.font: font,
                                                           .foregroundColor: UIColor.gray]

        let tabBarAppearance = UITabBarAppearance()
        tabBarAppearance.stackedLayoutAppearance = tabBarItemAppearance

        tabBar.standardAppearance = tabBarAppearance
    }
} 

その他

上記以外にも細々とした Warning が出ましたが、全て指示通り置き換えればよいだけだったため、個々の内容は割愛させていただきます。

  • 'setTargetRect(_:in:)' was deprecated in iOS 13.0: Use showMenuFromView:rect: instead.
  • 'setMenuVisible(_:animated:)' was deprecated in iOS 13.0: Use showMenuFromView:rect: or hideMenuFromView: instead.
  • Getter for 'scrollIndicatorInsets' was deprecated in iOS 13.0: The scrollIndicatorInsets getter is deprecated, use the verticalScrollIndicatorInsets and horizontalScrollIndicatorInsets getters instead.

おわりに

Sansan アプリにおける iOS 12 のサポート終了対応は以上となります。
基本的には指示通り置き換えるだけだったので実装はスムーズに行ったのですが、結合テストの最後の最後で TabBar の問題が発覚し、かなり焦ったのは良い思い出です。

今回の記事がこれから iOS 12 のサポート終了対応をする方のお役に立てれば幸いです。
ここまで読んでいただきありがとうございました。

© Sansan, Inc.