WWDC Quick Look 💓 By SwiftGGTeam
What’s new in UIKit

What’s new in UIKit

元の動画を見る

ハイライト

iOS 18 の UIKit は SwiftUI Animation で UIView アニメーションを駆動可能。ジェスチャーシステムはフレームワーク間で依存関係を統一。Tab Bar は sidebar 形態に自動適応。


主要内容

UIKit で iPad app を書く開発者は皆同じ問題に直面——UITabBarController の tab bar と sidebar は独立した 2 つの UI。切り替え時に手動で state 管理が必要。コードが長くバグも出やすい。iPadOS 18 でついに統合——新しい UITabUITabGroup API で app 構造を 1 回記述するだけで、システムが tab bar と sidebar の両形態を自動生成。ユーザーはドラッグで tab 順序もカスタマイズ可能。

trait 変更の処理も同様に面倒だった。以前は registerForTraitChanges で関心のある trait を手動登録し、コールバックで setNeedsLayoutsetNeedsDisplay を呼ぶ必要があった。iOS 18 の automatic trait tracking では、layoutSubviewsdrawRect などで trait を読むだけで UIKit が依存関係を自動記憶。trait 値が変わると対応する invalidation を自動トリガー。大量のボイラープレートを削除可能。

フレームワーク間協調の痛点も解消中。SwiftUI の Animation 型が UIKit view アニメーションを直接駆動可能(CustomAnimation 含む)。ジェスチャーシステムも統一——UIKit と SwiftUI のジェスチャー間で failure/requirement 関係を設定可能。新しい UIGestureRecognizerRepresentable プロトコルで UIKit ジェスチャーを SwiftUI 階層に埋め込み。


詳細

SwiftUI Animation で UIView を駆動

今回の更新で最も実用的な API 変更。UIView.animate(using:) は SwiftUI Animation 型を受け取り、インタラクティブアニメーションで特に有用——ジェスチャー中は .interactiveSpring、終了時は .spring。2 フェーズ間で速度がシームレスに継続(03:35):

switch gesture.state {
case .changed:
    UIView.animate(.interactiveSpring) {
        bead.center = gesture.translation
    }

case .ended:
    UIView.animate(.spring) {
        bead.center = endOfBracelet
    }
}

キーポイント:

  • .changed 分支は .interactiveSpring で指に追従。適度なバウンス
  • .ended 分支は .spring で終点固定。ジェスチャー終了瞬間から速度が自然に遷移
  • 2 フェーズは同じ velocity 伝達メカニズムを共有。初速度の手動計算不要

フレームワーク間ジェスチャー依存

SwiftUI view が UIKit view 内にネストされると、両フレームワークのジェスチャーが同時認識して競合する可能性。iOS 18 では name と delegate メソッドでフレームワーク間 failure requirement を設定可能(04:46):

// Inner SwiftUI double tap gesture

Circle()
    .gesture(doubleTap, name: "SwiftUIDoubleTap")


// Outer UIKit single tap gesture

func gestureRecognizer(
    _ gestureRecognizer: UIGestureRecognizer,
    shouldRequireFailureOf other: UIGestureRecognizer
) -> Bool {
    other.name == "SwiftUIDoubleTap"
}

キーポイント:

  • SwiftUI ジェスチャーは name: パラメータでタグ付け。UIKit 側は other.name でマッチ
  • UIKit のシングルタップは SwiftUI ダブルタップが失敗してからトリガー。同時認識を回避
  • 標準的な UIGestureRecognizerDelegate メソッド。新プロトコル不要

Automatic trait tracking

layoutSubviews で trait を読むと UIKit が依存関係を自動追跡(06:40):

class MyView: UIView {
    override func layoutSubviews() {
        super.layoutSubviews()

        if traitCollection.horizontalSizeClass == .compact {
            // apply compact layout
        } else {
            // apply regular layout
        }
    }
}

キーポイント:

  • layoutSubviews 呼び出し時、UIKit は horizontalSizeClass へのアクセスを記録
  • その trait が変化すると UIKit が自動で setNeedsLayout を呼ぶ
  • 以前は手動 registerForTraitChanges + コールバック内 setNeedsLayout が必要——削除可能
  • サポートメソッド:layoutSubviewsdrawRect:traitCollectionDidChange など。完全リストはドキュメント参照

List environment trait

UICollectionView list section と UITableView 内の全 view が list environment trait を自動保有。UIListContentConfigurationUIBackgroundConfiguration がこの trait に基づき外観を調整(07:43):

func configurations(for location: FileLocation) ->
    (UIListContentConfiguration, UIBackgroundConfiguration) {

    var contentConfiguration = UIListContentConfiguration.cell()
    let backgroundConfiguration = UIBackgroundConfiguration.listCell()

    contentConfiguration.text = location.title
    contentConfiguration.image = location.thumbnailImage

    return (contentConfiguration, backgroundConfiguration)
}

キーポイント:

  • UIListContentConfiguration.cell() は sidebar/通常スタイルを手動判別するロジックを置き換え
  • UIBackgroundConfiguration.listCell() は list environment trait から外観を自動読み取り
  • 新しいコンストラクタ:.listHeader.listFooter
  • list appearance をパラメータで渡す必要なし——cell に適用時に自動適応

CADisplayLink を置き換える新 API。自動 view tracking と低遅延モード(11:18):

let updateLink = UIUpdateLink(
    view: view,
    actionTarget: self,
    selector: #selector(update)
)
updateLink.requiresContinuousUpdates = true
updateLink.isEnabled = true

@objc func update(updateLink: UIUpdateLink,
                  updateInfo: UIUpdateInfo) {
    view.center.y = sin(updateInfo.modelTime)
        * 100 + view.bounds.midY
}

キーポイント:

  • 構築時に view にバインド。可視 window に加入時に自動アクティブ、削除時に自動停止
  • updateInfo.modelTime はディスプレイ同期時間。手動タイミングより正確
  • requiresContinuousUpdates = true で毎フレームトリガー。false は他の更新(ジェスチャー、layer 変化)があるときのみ
  • 低遅延モードをサポート。描画系 app に適する

その他の更新

  • Zoom transition:iOS 18 で zoom transition 追加。navigation と presentation をサポート。完全インタラクティブ——転場中にドラッグで中断可能(02:50
  • SF Symbols アニメーション.wiggle.breathe.rotate プリセット追加。.periodic(繰り返し回数と間隔指定)と .continuous 動作。Magic Replace が badge と slash の遷移アニメーションを自動処理(12:47
  • UICanvasFeedbackGenerator:大規模キャンバス app 向け新フィードバック生成器。作成時に view にバインド。フィードバックトリガー時に位置座標を渡す(15:02
  • Text formatting panel:UITextView にフォーマットパネル追加。フォント、サイズ、リスト、ハイライトなど。textFormattingConfiguration でレイアウトカスタマイズ。nil で終了(16:14
  • Text highlighttextHighlightStyletextHighlightColorScheme でテキストハイライト。5 種のプリセット配色(16:49
  • Writing Tools:UITextView がデフォルトで Writing Tools UI を統合。編集可能 text view はインライン編集、非編集はオーバーレイパネル(17:40
  • Apple Pencil Pro:squeeze ジェスチャー、barrel-roll 角度、undo slider、PKToolPicker カスタムツール(19:01

重要ポイント

  • やること:インタラクティブアニメーションで UIViewPropertyAnimatorUIView.animate(.spring) に置き換え。価値がある理由:SwiftUI spring の velocity 伝達がインタラクティブ/非インタラクティブフェーズ間でシームレス。コード量を半分以上削減。始め方:ジェスチャーの .changed.ended 分支でそれぞれ UIView.animate(.interactiveSpring)UIView.animate(.spring) を呼び、旧 animator コードを削除。

  • やることUITabBarItem 配列を UITab / UITabGroup 構造に移行。価値がある理由:新 API が tab bar と sidebar 形態に自動適応。Mac Catalyst と visionOS でもネイティブ体験。プラットフォームごとに二重のナビゲーションロジックを書く必要なし。始め方UITabBarControllerviewDidLoadUITabUITabGroup から tabs 配列を構築。手動 tabBar.items 設定を削除。

  • やることregisterForTraitChanges の手動 trait 登録コードを削除。価値がある理由layoutSubviews などでの automatic trait tracking により手動登録は冗長。始め方:Xcode で registerForTraitChanges をグローバル検索。各箇所で trait が layoutSubviewsdrawRect: で既にアクセスされているか確認。されていれば登録コードを削除。

  • やること:list スタイル手動判別を UIListContentConfiguration.cell()UIBackgroundConfiguration.listCell() に置き換え。価値がある理由:list environment trait が sidebar と insetGrouped 外観を自動適応。画面回転や split view 時に cell の手動 reconfigure 不要。始め方:cell 設定コードの isSidebar 分支を探し、統一された .cell() / .listCell() コンストラクタに置き換え。


関連セッション

コメント

GitHub Issues · utterances