ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

iOSアプリ - UIKitアプリの画面更新機能についての冒険

iOSアプリ - UIKitアプリの画面更新機能についての冒険

端的にいうと: iPadの縦横比の動的切り替えを支える機能がアップデートされていたことを、後になって知った話。

Appleの技術カンファレンスであるWWDC26が開催されました。WWDC26の内容の多くを占めていたのは、Apple Intelligenceの主要機能であるパーソナライズされたAIを実現するためのアップデートでした。

Apple Intelligence自体は2年前に大々的にアピールされましたが、サーバーサイドのAIリソース不足やアプリ開発者へ提供されるツール群の不足などが相まって、達成したいとされていた機能の多くは先送りされてきました。それらが、2026年後半にリリースされるであろうiOS27と新しいiPhoneで採用される見込みです。

Apple Intelligenceがカンファレンスの主要な題材でしたが、新しいiPhoneデバイスの登場を窺わせるようなプレゼンテーションも用意されていました。

developer.apple.com

タイトルが「UIKitアプリのモダナイズ」とあるように、既存のiOSプロジェクトでUIKitを使っているアプリをモダンな形にするための要点をまとめたプレゼンテーションとなります。

内容を見ていくと、iPadのようにアプリ利用中にアプリの縦横比が切り替わることが、Appleの言う「モダン」なアプリとのことです。つまりは、新しいiPhoneデバイスもiPadのように縦横比が切り替わるのでしょうか? 発表が楽しみですね。

1) UIKitアプリでの画面更新

プレゼンテーション内容で目に留まったのが、画面の縦横比や画面解像度などを管理するクラスUITraitCollectionです。UITraitCollectionは、UIに関連する特性(縦横比、解像度)を集めたクラスで、アプリ使用中に縦横比が変わるiPadなどで重要になります。

プレゼンテーション中では以下のコードが示されていました。

// Replace the screen's scale with trait collection's displayScale
override func layoutSubviews() {
    super.layoutSubviews()
    // layoutSubviews will be called again automatically when displayScale changes
    let displayScale = traitCollection.displayScale
    // ...
}

出典: UIKitアプリのモダナイズ - WWDC26 - ビデオ - Apple Developer

内容としては、UIKitを使用して画面上のUI構築パーツでUITraitCollectiondisplayScaleに一度アクセスすると、displayScaleが変化するたびにOS側がlayoutSubviews()を再度呼び出すという仕組みを説明しています。要は、サードパーティ製ライブラリなどを使用せずに、layoutSubviews()に画面の構成要素の変更処理を集約することができます。

こちらは画面を構成するUIViewControllerでも利用可能となっています。サンプルコードを示すと以下となります。

import UIKit

class MyViewController: UIViewController {
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        // トレイト(UITraitDefinition対応)への読み取り
        let scale = traitCollection.displayScale
        countLabel.font = .systemFont(ofSize: scale > 2 ? 14 : 16)
    }
}

サンプルコードを書いてみて気づいたのは、これがUIKitとObservation frameworkを組み合わせた際の使い方と同じように記述できるという点です。しかし、UITraitCollectionの由来から考えると、同じことが実現できているのは不思議なことでした。

2) 最新機能と古参機能

Observation frameworkを説明すると、データオブジェクトに対して状態監視機能を組み込む仕組みです。Observation frameworkは、Appleアプリ開発で利用されるSwift言語の機能を活用し、簡潔な宣言で開発者が最低限の作業で既存/新規のデータオブジェクトの属性に状態監視機能を組み込むことを実現可能にしています。

Observation frameworkはアプリを構築するためのフレームワークであるSwiftUIと組み合わせて利用することが広く紹介されていますが、Appleは従来のiOSアプリ構築用のUIKitでも利用可能としています。制約としてはiOS18.6以降が対象で、プログラミング言語もSwiftのみ対応、Objective-Cは非対応となっています。OSバージョンの制約は今後緩和されていくでしょうし、iOSアプリを開発する言語としてObjective-Cが積極的に採用されることは稀でしょう。

以下のサンプルコードで、@Observableを付与したCounterModelの属性countを変更すると、OSからviewWillLayoutSubviews()が呼び出され画面が更新される仕組みとなっています。

import Observation

@Observable
class CounterModel {
    var count = 0
}

class MyViewController: UIViewController {
    @IBOutlet private let countLabel: UILabel!

    // iOS18.6にバックポートされた
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        // Observableモデルのプロパティへの読み取り
        countLabel.text = "\(model.count)"
    }

    // iOS26以降はupdateProperties()メソッドでも利用可能
    override func updateProperties() {
        super.updateProperties()
    }
}

実装の内情を察すると、viewWillLayoutSubviews()@Observableに対応したクラス/構造体のプロパティにアクセスすると、値の監視対象として登録される仕組みが用意されているものと思われます。

Observation frameworkがiOS17で登場したのに対して、UITraitCollectionはiOS8から存在するUIKitの機能の一部です。登場自体はiOS8で、iOSがUIをスキュモーフィズムからフラットデザインへ刷新したiOS7の翌年にあたります。当時、iPadやiPhoneなど画面サイズの異なる端末が増える中でアプリが動作中に縦横比が切り替わる問題への対処として、画面レイアウトや解像度を取得する必要があったためにUITraitCollectionが提供されました。

UITraitCollectionもいくつかアップデートが加わっていますが、UITraitCollectionが関わるデバイスが専らiPadのため、関心はそれほど高くはありません。Appleの数ある開発者向けプレゼンテーションでUITraitCollectionの登場頻度は、直近7年で9回と抑えられています。つまり、利用する人が限定されていた機能となります。

Observation frameworkと比べてUITraitCollectionに課されている制約もあります。UITraitCollectionはiOS8から登場しているため、Objective-Cなど古いアプリ開発環境でも機能する必要があります。Observation frameworkと同じような機能はSwift言語を使った際にのみ機能すれば良いですが、UITraitCollectionはObjective-Cで使った場合にも問題なく利用できることが必要となります。

iOS Programming Language and frameworks

3) UITraitCollectionの画面更新の仕組みを解き明かす

UITraitCollectionがどのようにObservation frameworkと同様のUIKit画面更新機能を実現しているかというと、Swift言語を使っている場合にUITraitCollectionの各属性へのアクセスを記録しておき、必要な属性が変更された際に画面更新処理を呼び出す仕組みを実現しています。

画面更新の仕組みについてAppleはAutomatic Trait trackingという名称をつけています。 developer.apple.com

UITraitCollectionの場合、各属性は実際には次のような形で実装されています(概念的な例)。

extension UITraitCollection {
    subscript<T: UITraitDefinition>(trait: T.Type) -> T.Value {
        // 内部ストレージから該当トレイトの値を取り出す
        // ここでアクセス記録(tracking)のフックが働く
    }
}

extension UITraitCollection {
    var displayScale: CGFloat {
        self[UITraitDisplayScale.self]
    }
}

つまりtraitCollection.displayScaleという「見た目は普通のプロパティ」も、内部的にはself[UITraitDisplayScale.self]という subscript 呼び出しに変換されています。この subscript は「UITraitDisplayScaleという型」をキーとして、内部の辞書的なストレージから値を取り出す実装になっています。

上記実装を前提として、なぜ状態監視機能につながるかというと、subscriptは1つの共通したget実装を、あらゆるトレイトの型に対して使い回せるからです。そのため、UIKitは「共通のsubscript get実装の中に1箇所だけ」アクセス記録用のフックを仕込めば、displayScaleだろうとuserInterfaceStyleだろうと、すべてのトレイトへのアクセスを同じ場所で検知できます。個々のプロパティ(displayScaleuserInterfaceStyle...)ごとに追跡コードを書く必要がなく、キーとなる型(UITraitDisplayScale.selfなど)がそのままトラッキング対象の識別子として使える、という設計上の利点があります。

結論としては、UITraitCollectionはObservation frameworkに依存せず、同じような機能を実現していることがわかります。

4) 時系列を整理する

UITraitCollectionの状態監視結果を画面構築につなげる仕組みは、iOS17で実現されています。Observation frameworkを使った状態監視結果を画面構築につなげる仕組みは、iOS26から実現されています。時系列を鑑みると、iOS17でUITraitCollectionが実現した状態監視結果を画面構築につなげる仕組みを、2年後のiOS26でObservation frameworkを使った状態監視結果を画面構築につなげる仕組みに転用した、とみるのが自然な流れかと考えます。

iOS trail tracking implement timeline

5) まとめ

Observation frameworkの新機能を見てからUITraitCollectionでも同じことができることを再発見するに至ったのは、UITraitCollectionがiPadのような動的に縦横比が変わるアプリ向けの機能となっていたことで、UITraitCollectionへの関心が向いていなかったためです。本来の機能公開順序とは逆の順番で知ることになったため理解するまで遠回りした感じがあります。

たまたまAppleが公開した「UIKitアプリのモダナイズ」のプレゼンテーションで知る機会を得ましたが、このプレゼンテーション自体がAppleが新しいiPhoneデバイス向けに準備していた仕組みの展覧場となっておりキャッチアップ仕切れなかった機能を確認する良い機会とも言える気はします。

ユニファでは一緒に働く仲間を募集しています!

jobs.unifa-e.com