Contents
SwiftUIとUIKitの基本的な違いと選択基準
SwiftUIとUIKitは、iOS/macOS開発における2つの主要なフレームワークですが、設計思想や実装方法に大きな違いがあります。本記事では、宣言型アプローチのSwiftUIと手続き型アプローチのUIKitを比較し、プロジェクトでどちらを選ぶべきか判断する基準を解説します。
宣言型vs手続き型のアプローチ
SwiftUIは宣言型(Declarative)アプローチを取り、UIの構築は「何が表示されるべきか」を定義することで実現されます。一方で、UIKitは手続き型(Imperative)アプローチに則り、「どうやって表示するか」をコードで明示的に記述します。
-
SwiftUIの例
swift
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, SwiftUI!")
Button("Tap me") { print("Button tapped!") }
}
}
} -
UIKitの例
swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()123456789let label = UILabel()label.text = "Hello, UIKit!"view.addSubview(label)let button = UIButton(type: .system)button.setTitle("Tap me", for: .normal)button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)view.addSubview(button)}
@objc func buttonTapped() {
print("Button tapped!")
}
}
ライフサイクル管理の差異
SwiftUIでは、ライフサイクルを意識する必要が少なく、ビューの再描画は自動的に処理されます。これに対し、UIKitではviewDidLoad()やviewWillAppear()などのメソッドで明示的にライフサイクルを管理する必要があります。
|
1 2 3 4 5 |
| **フレームワーク** | **ライフサイクル管理方法** | **自動更新機能** | |--------------------|-------------------------------|------------------| | SwiftUI | ビューの変更を自動反映 | ✅ あり | | UIKit | `viewDidLoad()`など明示的 | ❌ なし(手動) | |
UIコンポーネントの作成方法
SwiftUIはViewプロトコルに準拠する構造体でUIを定義し、UIKitではUIViewやUIViewControllerを継承してカスタムビューを作成します。
注意点: SwiftUIは宣言型アプローチのため、複雑なUIロジックを扱う場合、コードが冗長になる可能性があります。これは誤解であり、SwiftUIではビュー階層とロジックを厳密に分離することで簡潔性を保つことができます。
UIHostingControllerによるUIKitコンポーネント埋め込み
SwiftUIビューをUIKitアプリ内に組み込むには、UIHostingControllerを使うのが一般的です。このセクションでは、UIViewController内でSwiftUIビューを表示する方法と、データバインディングの実装例を解説します。
UIViewControllerにSwiftUIビューを埋め込むコード例
以下は、UIViewControllerにSwiftUIビューを配置するシンプルな例です:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import SwiftUI class MyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let swiftUIView = ContentView() let hostingController = UIHostingController(rootView: swiftUIView) addChild(hostingController) view.addSubview(hostingController.view) hostingController.didMove(toParent: self) } } |
親子ビュー間のデータバインディング
SwiftUIとUIKit間でデータを双方向に共有するには、@Bindingや@ObservedObjectを使う方法が有効です。
例: SwiftUIビュー内でUIButtonの押下状態をUIViewControllerに通知する場合
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// SwiftUI側 struct ContentView: View { @Binding var isButtonPressed: Bool var body: some View { Button("SwiftUI Button") { isButtonPressed.toggle() } } } // UIKit側(MyViewController) class MyViewController: UIViewController { private var isPressed = false private var binding: Binding<Bool>! override func viewDidLoad() { super.viewDidLoad() let swiftUIView = ContentView(isButtonPressed: $isPressed) let hostingController = UIHostingController(rootView: swiftUIView) addChild(hostingController) view.addSubview(hostingController.view) hostingController.didMove(toParent: self) } } |
注意点:
@Bindingは、UIViewControllerとSwiftUIビューの間でデータを同期する場合に有効です。単方向通知が必要な場合は、NotificationCenterや閉包を使用します。
SwiftUIビュー内のUIKitコンポーネント利用法
SwiftUI内でUIKitコンポーネント(例: UITextFieldやUICollectionView)を使うには、UIViewRepresentableプロトコルを実装する必要があります。このセクションでは、カスタムビューのラッパー作成とSceneKitとの連携方法を解説します。
UIViewRepresentableの基本構造
UIViewRepresentableは、UIKitコンポーネントをSwiftUIに統合するためのエイリアスです。以下は、UITextFieldをSwiftUIビュー内に表示する例です:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct CustomTextField: UIViewRepresentable { @Binding var text: String func makeUIView(context: Context) -> UITextField { let textField = UITextField() textField.placeholder = "Enter text" textField.borderStyle = .roundedRect return textField } func updateUIView(_ uiView: UITextField, context: Context) { uiView.text = text } } |
SceneKitやARKitとの連携
SwiftUI内でSceneKitやARKitなどの低レベルAPIを扱う場合、UIViewRepresentableと組み合わせて使用します。
例: ARKitビューの表示
|
1 2 3 4 5 6 7 8 9 10 |
struct ARViewWrapper: UIViewRepresentable { func makeUIView(context: Context) -> ARSCNView { let arView = ARSCNView() // ARの設定をここに記述 return arView } func updateUIView(_ uiView: ARSCNView, context: Context) {} } |
重要ポイント:
UIViewRepresentableを使う場合、ビューのライフサイクル(makeUIView,updateUIView,removeFromSuperview)を正確に管理する必要があります。メモリリークは自動的に管理されるため、手動で解放する必要はありません。
パフォーマンス比較と混合アーキテクチャ設計の注意点
SwiftUIとUIKitはそれぞれ異なるパフォーマンス特性を持ちます。ここでは、レイアウト計算やメモリ使用量に注目した比較を行い、混合アーキテクチャにおける最適な設計パターンを紹介します。
レイアウト計算の効率性
SwiftUIは自動的にビュー階層を管理するため、手動でレイアウトを調整する必要がありません。一方、UIKitではAuto Layoutを使って配置情報を定義する必要があります。
|
1 2 3 4 5 |
| **フレームワーク** | **レイアウト計算方式** | **パフォーマンスの特徴** | |--------------------|---------------------------|---------------------------------| | SwiftUI | 自動的(宣言型) | シンプルな構成で高速 | | UIKit | 手動(Auto Layout) | 複雑なレイアウトでは計算コスト高 | |
メモリ使用量の差異
SwiftUIは、ビューが表示されない場合に自動的にメモリを解放するので、不要なオブジェクトの保持が少ない傾向があります。UIKitでは、手動でdeinitやremoveFromSuperview()などでメモリ管理を行う必要があります。
SwiftUI優先開発時のUIKitモジュール統合戦略
既存のUIKitコードをSwiftUIプロジェクトに統合する際には、以下の戦略が有効です。特に、グリーンフィールドプロジェクトとレガシーコードの両方に対応できる設計案を紹介します。
グリーンフィールドプロジェクトでの導入スイッチ
新しいプロジェクトでは、SwiftUIで全体のUIを構築し、一部のモジュール(例: レガシーコードが存在するサードパーティライブラリ)をUIKitで実装することが可能です。
- ステップ1: すべてのビューをSwiftUIで作成
- ステップ2: UIKitが必要なモジュールごとに
UIHostingControllerでラッピング - ステップ3: 各モジュール間のデータ共有は
@PublishedやNotificationCenterなどで実現
レガシーコードの徐々な置き換え方針
既存のUIKitプロジェクトでは、機能ごとにSwiftUIに移植する方法がおすすめです。
- 選定: 頻繁に更新されないサブモジュールから移行を開始
- ラッピング: UIKitビューを
UIViewRepresentableでラップし、SwiftUI内での表示を実現 - 統合: SwiftとObjective-Cの連携機能を利用して、徐々にコードを置き換えていく
共有コード層の設計パターン
SwiftUIとUIKitで共通して使えるコード層を作るには、Combine FrameworkやObservableObjectを使う方法が適しています。
- Shared Model: データモデルはSwiftUIとUIKit間で共有可能
- 通信手段:
NotificationCenterやCombineを用いてデータの変化を通知
注意点: UIKitモジュールとの境界を明確に保つことで、今後の保守性が向上します。また、UIHostingControllerを使用する際は、ビュー階層の深さを制限し、パフォーマンスの低下を防ぐように設計してください。