Develop in Swift Data CollectionsでiOS開発を学ぶ (1): Lesson 1.2まで

※(1)とついてますが続くかは不明

# Develop in Swift Data Collectionsとは

Appleが高校生や大学生向けに出している学習用教材に含まれる、勉強用のドキュメントです。

https://www.apple.com/tr/education/docs/develop-in-swift-curriculum-guide.pdf

![スクリーンショット 2020-11-21 12.35.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213719/059d3c3b-c0fb-31f7-934e-9fd6022ee4aa.png)

学生向け学習用カリキュラムがあり、そのカリキュラムの最終段階で学ぶ内容のようです。難しそうですが、まぁ現役のプログラマーならいけるでしょう（多分）。

この本を見つつ、iOS開発について学んでいく、という記事になります。基本は本を読んで、僕の記事はあくまで参考程度に見てもらえると良いと思います。

本自体はApple Booksから無料でダウンロードできます。

https://books.apple.com/jp/book/develop-in-swift-data-collections/id1511183970

# 前提知識・環境

もちろんMacが必要です。サンプルコードはXcode11のようですが、最新のM1搭載Mac上のXcode12でもサンプルコードは動きそうな気配がしてます（まだ全部のソースコードを動かしたわけではないですが）。

あた先ほど貼ったカリキュラム通り、Swfitの基本的な書き方とかは知っている前提の本になっていると思われます。

# サンプルコードのダウンロード

8ページ目にサンプルコードをまとめたzipファイルへのリンクが貼ってあり、ここからダウンロードすることができます。

![スクリーンショット 2020-11-21 15.28.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213719/d1d361e2-3ef8-a03c-c096-2fcc022897e6.png)

すべてのレッスンのサンプルコードが含まれている、というわけではなさそうです。

# やってみる

## Unit 1: Tables And Persistence

章のタイトルが？って感じなのですが、この章を終えると情報の表示と変数、保存のUIを構築できるようになるようです。

>“By the end of this unit, you'll be comfortable building many useful apps that display all kinds of information and that allow users to enter, edit, and save in-app information.”

>抜粋:: Apple Education  “Develop in Swift Data Collections”。 Apple Inc. - Education、2020年  Apple Books  https://books.apple.com/jp/book/develop-in-swift-data-collections/id1511183970

### Lesson 1.1: Protocols

プロトコル（他の言語で言うとinterface）の話がずっと続きます。いきなりプロトコルの話からはじまるのが結構ガチだなという気がしますが、iOSアプリ開発においてはすごい基礎的で重要なことなんでしょうね。

この本は、一通り読み終わった後、章末問題的なものが用意されています。

![スクリーンショット 2020-11-21 12.47.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213719/aed162ef-5176-c13e-f9d9-65a7211e294a.png)

いやーすごいですね。Apple Booksこんなこともできるんだ。

## Lesson 1.2: App Anatomy and Life Cycle

アプリケーションのライフサイクルとAppDelegate/SceneDelegateの話が出てきます。

この節は実際にコードを組んでみるという実験（本では`Lab`と読んでいるようなので）があるのでやってみます。

実験はアプリケーションのライフサイクルの動きを観察するものです。雛形は本のサンプルコード集に含まれていないようなので、Xcodeで新しく作成します。

で、コードを記述していきます。

### 1. AppDlegateにカウンタ変数を追加

下記の2つのメソッドの呼び出し回数をカウントできるようにコードを追加します。最終的に下記のようになると思います。

- application(_:didFinishLaunchingWithOptions:)
- application(_:configurationForConnecting:options:)

```swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var launchCount = 0
    var configurationForConnectingCount = 0

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        launchCount += 1
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        configurationForConnectingCount += 1
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    }


}
```

### 2. ViewControllerに表示用のラベルを追加

Main.storyboardを開き、下記のようにラベルを追加していきます。

![スクリーンショット 2020-11-21 14.59.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213719/cc0e4476-8b09-12e7-7ff4-b29c63b668d4.png)

普通にlabelを置いていっても良いと思いますし、StackView内に積んで行っても良いと思います。

僕は[このQiitaの記事](https://qiita.com/yucovin/items/ff58fcbd60ca81de77cb)を参考にVertical StackViewを配置して、その中にラベルを置いていきました。そしてAutoLayoutで上下左右を端っこにピン留め。

Main.storyboardを作った後は、ViewController.swiftにそれぞれのラベルをアウトレットとして繋いで行きます。アウトレットというのは、要するにStoryboard上のUIパーツへの参照です。

```swift
    @IBOutlet weak var didFinishLaunchingWithOptionsLabel: UILabel!
    @IBOutlet weak var configurationForConnectingLabel: UILabel!
    @IBOutlet weak var willConnectToLabel: UILabel!
    @IBOutlet weak var didBecomeActiveLabel: UILabel!
    @IBOutlet weak var willResignActiveLabel: UILabel!
    @IBOutlet weak var willEnterForegroundLabel: UILabel!
    @IBOutlet weak var didEnterBackgroundLabel: UILabel!
```

そして、それぞれのラベルに表示させるカウンタ変数を作成します。

```swift
    var willConnectToCount = 0
    var didBecomeActiveCount = 0
    var willResignActiveCount = 0
    var willEnterForegroundCount = 0
    var didEnterBackgroundCount = 0
```

### 3. AppDelegateの変数にViewControllerからアクセスする

SceneDelegateのカウンタ変数はViewController内にあるのですが、AppDelegateのカウンタ変数はAppDelegate内にあります（多分AppDelegateの方が生存期間が長いから？）

なので、それにアクセスできるようにする必要があります。

ViewControllerに下のように書けばアクセスできるようです。

```swift
    var appDelegate = (UIApplication.shared.delegate as! AppDelegate)
```

そして、それぞれのラベルを更新するupdateViewメソッドをViewControllerに作成します。

```swift
    func updateView() {
        didFinishLaunchingWithOptionsLabel.text = "didFinishLaunchingWithOptions called  \(appDelegate.launchCount) time(s)"
        configurationForConnectingLabel.text = "configurationForConnection called \(appDelegate.configurationForConnectingCount) time(s)"
        willConnectToLabel.text = "willConnectTo called \(willConnectToCount) time(s)"
        didBecomeActiveLabel.text = "didBecomeActive called \(didBecomeActiveCount) time(s)"
        willResignActiveLabel.text = "willResiginActive called \(willResignActiveCount) time(s)"
        willEnterForegroundLabel.text = "willEnterForeground called \(willEnterForegroundCount) time(s)"
        didEnterBackgroundLabel.text = "didEnterBackground called \(didEnterBackgroundCount) time(s)"
    }
```

### 4. SceneDelegateからViewControllerにアクセスする

SceneDelegateのカウンタ変数はViewControllerにあるので、SceneDelegateからViewControllerのカウンタ変数を操作する必要があります。

メンバ変数としてviewControllerを宣言し、

```swift
var viewController: ViewController?
```

willConnectToでwindowから取れば良いようです。

```swift
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
...        
        viewController = window?.rootViewController as? ViewController
...
    }
```

### 5. SceneDelegateでカウンタ変数を更新する

viewControllerにSceneDelegateからアクセスできるようになったので、カウンタ変数を更新します。最終的にSceneDelegateは下記のようになると思います。

```swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    var viewController: ViewController?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
        
        viewController = window?.rootViewController as? ViewController
        viewController?.willConnectToCount += 1
    }

    func sceneDidDisconnect(_ scene: UIScene) {
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        viewController?.didBecomeActiveCount += 1
        viewController?.updateView()
    }

    func sceneWillResignActive(_ scene: UIScene) {
        viewController?.willResignActiveCount += 1
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        viewController?.willEnterForegroundCount += 1
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        viewController?.didEnterBackgroundCount += 1
    }
}
```

### 6. updateViewを適切なタイミングで呼ぶ

カウンタ変数を更新しても、それをviewに反映させないと見ることはできません。

ViewControllerのupdateViewを適切なタイミングで呼ぶ必要があります。

SceneDelegateでは、先ほど貼ったコードで必要なタイミングで呼び出せています。あとはViewControllerのviewDidLoadで呼べばオッケーです。

最終的にViewControllerは下記のようになると思います。

```swift
class ViewController: UIViewController {
    
    @IBOutlet weak var didFinishLaunchingWithOptionsLabel: UILabel!
    @IBOutlet weak var configurationForConnectingLabel: UILabel!
    @IBOutlet weak var willConnectToLabel: UILabel!
    @IBOutlet weak var didBecomeActiveLabel: UILabel!
    @IBOutlet weak var willResignActiveLabel: UILabel!
    @IBOutlet weak var willEnterForegroundLabel: UILabel!
    @IBOutlet weak var didEnterBackgroundLabel: UILabel!
    
    var willConnectToCount = 0
    var didBecomeActiveCount = 0
    var willResignActiveCount = 0
    var willEnterForegroundCount = 0
    var didEnterBackgroundCount = 0
    
    var appDelegate = (UIApplication.shared.delegate as! AppDelegate)
    
    override func viewDidLoad() {
        super.viewDidLoad()
         updateView()
    }

    func updateView() {
        didFinishLaunchingWithOptionsLabel.text = "didFinishLaunchingWithOptions called  \(appDelegate.launchCount) time(s)"
        configurationForConnectingLabel.text = "configurationForConnection called \(appDelegate.configurationForConnectingCount) time(s)"
        willConnectToLabel.text = "willConnectTo called \(willConnectToCount) time(s)"
        didBecomeActiveLabel.text = "didBecomeActive called \(didBecomeActiveCount) time(s)"
        willResignActiveLabel.text = "willResiginActive called \(willResignActiveCount) time(s)"
        willEnterForegroundLabel.text = "willEnterForeground called \(willEnterForegroundCount) time(s)"
        didEnterBackgroundLabel.text = "didEnterBackground called \(didEnterBackgroundCount) time(s)"
    }
}
```

### 7. 動作確認

ここまでやったら動くようになっているので、実際にシミュレーターで動かします。動かした時の様子をgifで貼っておきます。

![Nov-21-2020 15-16-59.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213719/d3dd4e24-c635-4b78-ac49-e5b14d1acc06.gif)