iOS 14 における SwiftUI の新機能: @AppStorage, @SceneStorage, SwiftUI app

この記事では次の内容について説明します。
1. `@AppStorage` を使用して、SwiftUI アプリの `UserDefaults` を置き換える
2.`@SceneStorage` を使用して、1つのプログラムシーン（ウィンドウ）に固有の情報を保存する
3. `SwiftUI` の `App Delegate` 機能を実装する

## `@AppStorage`

```swift
@AppStorage(wrappedValue: "Tokyo", "currentCity")
var currentCity
```

このコードは変数 `currentCity` を `UserDefaults` に保存された値にリンクする

`UserDefaults` は `currentCity` の値を求めようとし、もし求められれば、変数 `currentCity` は求められた値に相当する。

`User Defaults` がそれを求められなければ、デフォルトで `wrappedValue` を使用する。

変数 `currentCity` に変更が加えられた場合、その変更は保存される。

この `SwiftUI` プログラムによりテストが可能である：

```swift
struct ContentView: View {
    
    @AppStorage(wrappedValue: "Tokyo", "currentCity")
    var currentCity
    
    var body: some View {
        Text(currentCity).padding()
        TextField("", text: $currentCity)
    }
    
}
```

## `@SceneStorage`

`@SceneStorage` は `@AppStorage` とは違い、プログラムのシーンの状態を保存するために使います。 `SceneStorage` に保存されたデータは一つのシーン内にのみ在ります。（現在iOSは一つのアプリケーションで複数のシーンを持てるようになっているためです。）

```swift
struct textTabView: View {
    
    @SceneStorage("postContent")
    var postContent = "This is a draft..."
    
    var body: some View {
        TextEditor(text: $postContent)
    }
    
}
```

## SwiftUI App アプリ用App Delegate

今年のWWDC 2020で、AppleはSwiftUIアプリを発表しました。SwiftUI appでは、App DelegateファイルとScene Delegateファイルがありません。このセクションでは、App DelegateのアップデートをSwiftUIアプリで受け取る方法をご説明します。

<img width="350" alt="スクリーンショット 2020-07-14 午後2.24.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/ef1681b8-2da6-ad43-f71d-5e52c68e001e.png">


### バックグラウンド (background)、アクティブ (active)、 インアクティブ (inactive) 状態

`scenePhase` という環境変数を探し、その変数が変更された場合に通知されます。

```swift
struct ContentView: View {
    
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some View {
        Text("Hello, world!").padding()
            
            .onChange(of: scenePhase) { phase in
                switch phase {
                    case .active:
                        print("active")
                    case .inactive:
                        print("inactive")
                    case .background:
                        print("background")
                    @unknown default:
                        print("unknown")
                }
            }
        
    }
    
}
```

### ユニバーサルリンク / URLスキーム

URLスキームは、アプリを起動するために設定できるアプリ固有のURLです（app://）

https://developer.apple.com/documentation/uikit/inter-process_communication/allowing_apps_and_websites_to_link_to_your_content/defining_a_custom_url_scheme_for_your_app

ユニバーサルリンクは、ウェブサイトへアクセスした時にモバイルアプリを起動できます。

https://developer.apple.com/ios/universal-links/

これらのイベントを処理するには、`.onOpenURL` を使用します

```swift
@main
struct exploreSwiftUIApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { (url) in
                    print("Universal Link: \(url)")
                }
        }
    }
    
}
```

### App Delegateファンクションを使用する

古い AppDelegate.swift ファンクションを使用したい場合は、アダプターを追加することができます。

```swift
@main
struct exploreSwiftUIApp: App {
    
    @UIApplicationDelegateAdaptor var delegate: AppDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
    
}

class AppDelegate: NSObject, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        print("Did finish launch")
        return true
    }
    
}
```

### 他の種類の通知

以下を使って、SwiftUIで他の種類の通知を受け取ることができます

```swift
Text("Enter some text here")
    .onReceive(NotificationCenter.default.publisher(for: UIApplication.keyboardDidShowNotification)) { (output) in
        print("Keyboard is shown")
    }
```

---

:relaxed: [Twitter @MszPro](https://twitter.com/MszPro)

:sunny: [私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。](http://mszpro.com/ioskiji/)
<img width="150" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">