【UIKitシリーズ】その1 - UI実装方法の選択肢

[UIKitシリーズ目次はこちら](https://qiita.com/generosity_hiroto_taniguchi/items/5ab12cc1d0fa97598de6)

---

# はじめに
はじめて、Swift・XCodeを利用してアプリを開発するぞ！と意気込んだとき、UIを実装する手段として、**SwiftUI**と**UIKit**があることに戸惑いました。

なので、最初に各実装方法の違いとメリット・デメリットを把握することで、最初にどちらを学べばいいか？迷っている方、UIKitを学ぶことを尻込みしている方の参考になればと思います！



# 対象読者
- iOSエンジニアを目指している方！
- **UIKit** と **SwiftUI** どちらを学んでいけばいいか迷っている方！
- 今さらUIKit学ぶことに戸惑っている方！

# 環境
- Swift version 5.10
- Xcode version 15.3

## 先に結論
チーム開発・個人開発ともに、Storyboardのみを利用した実装はお勧めできない。

チーム開発の場合、UIKit と SwiftUI 両方できるのが理想。
または、途中から参加するプロジェクトがメインで使用している実装方法から習得する。

個人開発の場合、SwiftUIお勧め。


## UIの実装方法の選択肢
iOSアプリを作成する時、UIの実装方法の選択肢主に3つあります。
1. Storyboard (GUIベース)
2. UIKit(コードベース)
3. SwiftUI (コードベース)

GUIベースでUIを実装するなら、Storyboard。
コードベースで実装するなら、UIKit か SwiftUI になります。

下記には、Label を表示するだけの事例を見ながら各実装方法を紹介できればと思います。

# 1. Storyboard
## 実装例
### プロジェクト作成
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3786602/ea1f13c8-6c0a-f791-cad3-6d503707c306.png" alt="Create Project" width="300">

Create New Project を選択。
Product Name は任意のもの。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3786602/43c1079c-2ec4-14c6-c928-8ae5ad20d9e3.png" alt="Create Project" width="600">

templateは、iOS の App を選択肢して Next。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3786602/600cf434-fff4-9359-564f-21ed354edd64.png" width="600">

Interface を storyboard を選択。
Organaization Identifier は任意のものにして Next。
(SwiftUIを利用する場合は、Interface を SwiftUIにする)

### Label を配置する

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3786602/2cac4aaf-36b8-f07d-bab2-fd276d15c749.png"
width="300">

プロダクトを新規作成すると上記の画像のような構成になります。Storyboard である Main を選択します。
(詳しくは別記事で記載しますが、デフォルトの使用では、Main というStoryboard を編集することでアプリ実行時一番最初に表示されるUIを変更できます。)

![4_storyboard_label_set_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3786602/1b71cd6a-16a1-6bcf-34cf-472fdb91bb6d.png)

次に、Storyboardに追加することができる要素を表示するために、「+」ボタンを選択。
表示されている、「Label」を storyboard にドラッグ&ドロップ。

ドラッグ&ドロップした labelを編集するためには、labeクリックして右側に表示されるプロパティ欄の各項目を変更します。(右側にインスペクターパネルが表示されていない場合は、右上のボタンをクリック)

### メリット
- プログラミングベースではないので、取り組みやすい。
- 実装をしながら完成しているUIを確認できる。

### デメリット
・修正と変更の対応が大変。
・実態がXMLファイルのため、レビューの判断が難しい。 など

修正と変更が大変に関しては、チーム開発では、すでに実装してあるUIの一部を修正して欲しいということとあります。例えば、文字の色を全て白色から黒色にして欲しい、表示する画像のサイズを変更して欲しいなど。文字の色を全て変更することに対応する場合、文字色を制御しているプロパティの箇所を全て手動で変える必要があります。なので、TextなどのプロパティをStroyboard 上で制御することは、後の修正作業に影響を与えてしまいます。

次に、レビューの判断が難しいに関してです。StoryboardにLabelを追加しただけの状態のものを、Gitで変更履歴を確認します。Labelを追加しただけのものでも、見通しが悪く変更履歴からそのことを読み取るのが難しいです。読み取ることは可能ですが、時間が多くかかると思います。

<details><summary><font color="blue">今回の変更履歴を確認したい人はこちらから</font></summary>

```Main.storyboard
<?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+    <device id="retina6_12" orientation="portrait" appearance="light"/>
     <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <scenes>
         <!--View Controller-->
         <scene sceneID="tne-QT-ifu">
             <objects>
-                <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
+                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="uikit_sample" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
-                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <subviews>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Hello world !" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gt5-io-uLC">
+                                <rect key="frame" x="150" y="150" width="95" height="21"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                <nil key="textColor"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                        </subviews>
                         <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                     </view>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
             </objects>
+            <point key="canvasLocation" x="62" y="-2"/>
         </scene>
     </scenes>
+    <resources>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
 </document>
 
```

</details>

# 2. UIKit

先ほど作成したプロジェクトをそのまま活用します。
### 実装例
ViewControllerを選択して、Swiftファイルに変更を加えます。
```:UIViewController.swift
import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let label = UILabel()
        // ラベルのサイズと位置を設定
        label.frame = CGRect(x: 350, y: 100, width: 500, height: 100)
        // 表示テキスト設定
        label.text = "Hello, World!"
        // 色設定
        label.textColor = .red
        // フォントサイズ設定
        label.font = UIFont.systemFont(ofSize: 30)

        // View に UILabel を追加
        view.addSubview(label )
    }
}

```
**ビルド結果**
![Monosnap iPad (10th generation) 2024-07-26 14-23-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3786602/cc3b954f-22a8-52a8-a7ca-5ce1d6818cda.png)
(上記の画像は、シミュレータで iPad (10th generation) を指定して表示された結果)


### メリット
・修正と変更を柔軟に対応できる
・UI部品の再利用性
・レビューが比較的しやい。プロパティの一部変更した時など特にしやすくなる。 など

UI部品の再利用性・修正と変更を柔軟に対応できるに関しては、UI部品として作成してHeaderTextクラスを作成することで、複数の場所で使用することができ再利用性があります。また、Headerに配置してあるTextの色を黒から白に全て変更して欲しいという場合、　HeaderTextクラスに定義してある色の箇所を変更するだけですみます。

また、コードベースで実装することで変更履歴を確認することが容易になります。

(今回の実装例だけでは、メリットが分かりずらいかもしれませんが....今後紹介していきます！)

<details><summary><font color="blue">今回の変更履歴を確認したい人はこちらから</font></summary>

```:ViewController.Swift
 import UIKit
 
 class ViewController: UIViewController {
-
     override func viewDidLoad() {
         super.viewDidLoad()
-        // Do any additional setup after loading the view.
-    }
 
+        let label = UILabel()
+        // ラベルのサイズと位置を設定
+        label.frame = CGRect(x: 350, y: 100, width: 500, height: 100)
+        // 表示テキスト設定
+        label.text = "Hello, World!"
+        // 色設定
+        label.textColor = .red
+        // フォントサイズ設定
+        label.font = UIFont.systemFont(ofSize: 30)
 
+        // View に UILabel を追加
+        view.addSubview(label )
+    }
 }

```
</details>

### デメリット
- ビルドしなければ、実装した結果を確認することができない。

プロジェクト一度目のビルド時間は多くかかります。UIのプロパティの一部を変更して、再度ビルドするとします。一度目のビルド時間と比較して早くなります。
それでも、毎回ビルドしなければ確認することが出来ないのは少し手間がかかります。

([こちらの記事](https://qiita.com/taro-ken/items/9be7caa90fe2b05784f3)を参考にすればこのデメリットは解消できるかもしれません)

# 3.SwiftUI
### プロジェクト作成
UIKit と SwiftUIは共生することが出来ます。ここでは、一からプロジェクトを作成。
Storyboardのプロジェクト作成を参考に、Interface 項目を SwiftUI を選択して作成します。

### Textを配置する
```:ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        // Textを新たに追加
        Text("Hello, SwiftUI")
            .frame(width: 500, height: 100)
            .position(x: 200, y: 100)
            .foregroundColor(.red)
            .font(.system(size: 30))

        VStack {![Monosnap swiftUI_sample — ContentView.swift — Edited 2024-07-26 16-44-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3786602/8d8945af-435f-ec6f-fdb2-a53a370aba0b.png)

            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

```
**結果**

![Monosnap swiftUI_sample — ContentView.swift — Edited 2024-07-26 16-44-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3786602/56a8f18c-b36d-e1e7-8d72-31f3f4bf01d5.png)


SwiftUIの場合、変更内容をプレビューで確認できます。

### メリット
- 宣言的な記述で実装するので、UIの状態を一箇所に集中して記述する。なので、読みやすい。
- Modifiers を利用することで、View(textなどのUI要素)に対する変更を追加できる。
- リアルタイムで変更内容をプレビューで確認することができる。

### デメリット
- 細かいレイアウト実装が難しい場合がある。
- iOS13以降がサポートの対象である。(サポート以降のスマホの普及率によっては、無視できる)
- フレームワークがまだ成熟していない。


# まとめ

やはり長期的に見ると、**Storyboardのみで実装することはお勧めできません**。
開発プロジェクトが大規模になる程、UI周りの管理が難しくなると思います。
そして、一度コードベースの実装に慣れてUIの再利用性を考慮したりすると、開発速度も追い越すと思います。
(Storyboardのみで行うことの恐ろしさがわかる、[こちらの記事](https://qiita.com/lovee/items/acfc9d0f1ffa7207b38b)は面白いです。）

では、コードベースだとどちらを利用できるようになれば良いかというと、**プロダクトの要件を満たすことができるならば**、新規開発は**SwiftUI**がいいと思います。

しかし、**UIKitを習得する恩恵は多くあります**。

SwiftUIが登場したのが[2019年頃](https://developer.apple.com/videos/play/wwdc2019/204)ですので、UIKitを利用したプロダクトが多くあります。
また、UIKitで実装されている一部を、SwiftUIに置き換えることを行なっているプロダクト事例もあります。(SwiftUIとUIKitは共生できる)

なので理想は、両方のフレームワークに精通してプロジェクトの多様性に柔軟に対応できることだと思います。

レガシーながらも洗練されたUIKitの理解を深めながら、モダンな技術であるSwiftUIを学び続けていきたいですね！

---
次の項目: [その2 - プロジェクトの基本的な制御フロー理解する！](https://qiita.com/generosity_hiroto_taniguchi/items/8a2b3e17a3df1902e597)

---
### 参考
・　[iOSアプリのUI実装方法は4通りあるが何を使うべきか](https://qiita.com/st43/items/b21d61614ea8932b227f)
・　[SwiftUIの歴史と進化：AppleのUIフレームワークの新時代](https://swappli.com/history-swiftui/)