たった数行のコードで作成できる iOS 15 の新たな SwiftUI の機能

- タスクを優先順位付きで作成し、タスクを実行し、タスクをキャンセルします。
- ユーザーが「集中モード」をオンにしたかどうかを把握する
- 位置情報ボタン
- (SwiftUI) SwiftUIを利用してリモート画像を読み込む
- (SwiftUI) `List`リストの検索機能
- (SwiftUI) リストセパレータを隠す
- (SwiftUI) リストにPull to Refresh（引っ張って更新）機能を追加
- (SwiftUI) テキストフィールドのフォーカスを外す
- (SwiftUI) リストセルにスワイプアクションを追加する
- (SwiftUI) テキストビューでマークダウンを使用する
- (SwiftUI) 画面が表示されたらタスクを実行します
- (SwiftUI) タブビューにバッジ（未読メッセージカウンター）を追加

この記事は `developer.apple.com` 上で公開されている文書に基づいて書かれました。スクリーンショットは後ほどこちらに追加します。

iOS 14の新機能に関して私が書いた別の記事もお読みいただけます。
[たった数行のコードで作成できる iOS 14 の新たなフレームワークの機能](https://qiita.com/MaShunzhe/items/a4db494b1c07c9e5b5ba)

## タスクを優先順位付きで作成し、タスクを実行し、タスクをキャンセルします。

このコードはタスクを非同期 (asynchronously) で実行します。

```swift
let task = Task(priority: .userInitiated) {
    Server.fetchCatNames { catNames in
        print(catNames)
    }
}
```

## ユーザーが「集中モード」をオンにしたかどうかを把握する

フォーカスモードはiOS15の新機能です。
アプリは、ユーザーが邪魔されたくないかどうかを確認することができます。

まず、`Communication Notifications`権利を登録する必要があります。

次に、ユーザーの「集中モード」にアクセスが必要な理由を説明する`NSFocusStatusUsageDescription`キーの値を登録します。

以下のコードを使ってアクセス権をリクエストします。

```swift
// import Intents
let permissionNotDetermined = (INFocusStatusCenter.default.authorizationStatus == .notDetermined)
if permissionNotDetermined {
    INFocusStatusCenter.default.requestAuthorization { status in
        print(status)
    }
}
```

ユーザーの集中モードの状態を確認するには、次のコードを使います。

```swift
let isAccessAuthorized = (INFocusStatusCenter.default.authorizationStatus == .authorized)
if let isFocused = INFocusStatusCenter.default.focusStatus.isFocused,
   isAccessAuthorized {
    print(isFocused)
}
```

## SwiftUIの位置情報ボタン

ユーザーの位置情報をリクエストしたい場合、その旨を示すボタンを表示することができます。

このボタンには、開発者にかわって実際に位置情報の許可をリクエストする機能はありません。
プリセットスタイルが適用された、単なる普通のボタンです。
ユーザーの位置情報をリクエストして取得するためのロジックの実装は、開発者に委ねられています。

```swift
// import CoreLocation
// import CoreLocationUI
LocationButton(.currentLocation) {
    print("Location requested.")
}
.cornerRadius(15)
.symbolVariant(.fill)
.foregroundColor(.white)
```

ボタンに表示するラベルは、さまざまなものから選ぶことができます：

```swift
LocationButton(.currentLocation)
LocationButton(.sendCurrentLocation)
LocationButton(.sendMyCurrentLocation)
LocationButton(.shareCurrentLocation)
LocationButton(.shareMyCurrentLocation)
```

UIKitフレームワークの位置情報要求ボタン`CLLocationButton`についてはこちらの記事をご覧ください。
[Apple Developer Documentation](https://developer.apple.com/documentation/corelocationui/cllocationbutton?changes=latest_minor)

## SwiftUIを利用してリモート画像を読み込む

リモート画像を非同期で素早く読み込むことができます。

```swift
AsyncImage(url: URL(string: "https://via.placeholder.com/350x150")!)
```

画像UIオブジェクトやエラーメッセージを取得し、画像をダウンロードしている最中にロード画面を表示することもできます。

```swift
AsyncImage(url: URL(string: "https://via.placeholder.com/350x150")!, scale: 1.0) { phase in
    if let image = phase.image {
        image
    } else if let error = phase.error {
        Text(error.localizedDescription)
    } else {
        ProgressView()
    }
}
```

## SwiftUIのリストに関する新機能

### `List`に対して`.searchable`を使用すれば検索ボックスを表示できます。

```swift
struct ContentView: View {
    
    @State var searchTextEntered: String = ""
    var allCatNames = ["ムギ", "ソラ", "リン"]
    
    var body: some View {
        NavigationView {
            Form {
                List {
                    ForEach(allCatNames, id: \.self) { catName in
                        Text(catName)
                    }
                }
                .searchable(text: $searchTextEntered)
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    var searchResults: [String] {
        if searchTextEntered.isEmpty {
            return allCatNames
        } else {
            return allCatNames.filter { catName in
                return catName.contains(searchTextEntered)
            }
        }
    }
    
}
```

また、次のコードを使用して検索候補を表示することもできます。

`.searchable(text: <#T##Binding<String>#>, prompt: <#T##LocalizedStringKey#>, suggestions: <#T##() -> View#>)`

### `.listRowSeparator(.hidden)`を使って、リストビューの改行コードを非表示にすることができます。

```swift
List {
    ForEach(allCatNames, id: \.self) { catName in
        Text(catName)
            .listRowSeparator(.hidden)
    }
}
```

### リストの更新メカニズムを提供することもできます

SwiftUIリストにPull to Refresh（引っ張って更新）機能を追加できます。
ローディングインジケーターは、ユーザーがプルダウンして更新すると表示され、コードが完了すると非表示になります。

```swift
List {
    ForEach(allCatNames, id: \.self) { catName in
        Text(catName)
    }
}
.refreshable {
    Server.fetchCatNames { catNames in
        self.allCatNames = catNames
    }
}
```

### リストセルにスワイプアクションを追加する

ユーザーが行を左または右にスワイプすることで、特定のアクションを行えるようにすることができます。

```swift
Form {
    List {
        ForEach(allCats, id: \.self) { cat in
            Text(cat)
                .swipeActions(edge: .leading) {
                    Button {
                        print("pinned")
                    } label: {
                        Image(systemName: "bookmark")
                    }
                    .tint(.blue)
                }
        }
    }
}
```

## SwiftUIのテキストビューでマークダウンを使用する

これでSwiftUIでマークダウンを使用できます

```swift
Text("You can **now** type *in* markdown. You can put [links](https://example.com) here, or some `code`.")
```

## テキストフィールドのフォーカスを外す

アクティブなテキストフィールドにあるキーボードは、（`TextField`の）変数`FocusState`とプロパティ`.focused`を使って閉じることができます

（下の例を参照してください）

## テキストフィールドにキーボードツールバーを加えましょう

テキストフィールドの動作中、キーボード上方にツールバーを表示させることができます。

```swift
struct ContentView: View {
    
    @State var catNameEntered: String = ""
    @FocusState var isCatNameFieldFocused: Bool
    
    var body: some View {
        Form {
            TextField("Cat name", text: $catNameEntered)
                .focused($isCatNameFieldFocused)
                .toolbar {
                    ToolbarItemGroup(placement: .keyboard) {
                        HStack {
                            Button("Clear textfield") {
                                self.catNameEntered = ""
                            }
                            Spacer()
                            Button("Done") {
                                self.isCatNameFieldFocused = false
                            }
                        }
                    }
                }
        }
    }
    
}
```

## SwiftUI画面が表示されたらタスクを実行します

画面が表示されたらタスクを実行することができます。
これを利用すれば遠隔サーバからデータを取り込めます。

```swift
var body: some View {
    Form {
        List(allCatNames, id: \.self) { catName in
            Text(catName)
        }
    }
    .task {
        Server.fetchCatNames { catNames in
            self.allCatNames = catNames
        }
    }
}
```

## ユーザーがキーボードの送信ボタンをクリックしたことを知る

ユーザーがキーボードの送信ボタンをクリックしたことを知るために、`.onSubmit`を`Form`に使用することができます。

```swift
struct ContentView: View {
    
    @State var newCatNameField: String = ""
    @State var newCatColorField: String = ""
    
    var body: some View {
        Form {
            TextField("Cat name", text: $newCatNameField)
            TextField("Cat color", text: $newCatColorField)
        }
        .onSubmit {
            print("onSubmit \(newCatNameField) \(newCatColorField)")
        }
    }
    
}
```

## SwiftUIのタブビューにバッジ（未読メッセージカウンター）を追加

タブビューのタブにバッジを追加することができます（例えば、チャットタブでは、未読メッセージ数のバッジを表示することができます）。

```swift
TabView {
    
    Form {
        Text("Tab 1")
    }
    .tabItem({
        Label("Tab 1", systemImage: "book")
    })
    .badge(10)
    
    Form {
        Text("Tab 2")
    }
    .tabItem {
        Label("Tab 2", systemImage: "calendar")
    }
    .badge(1)
    
}
```

## Time-Sensitive（一刻を争う）notifications 通知

通知を緊急のものとしてマークすることができます。その場合、ユーザーがフォーカスモードをオンにしていても、画面上に通知が表示されます。

アプリ資格付与ファイルに`Communication Notification`、`Push Notifications`、`Time Sensitive Notifications` を追加する必要があります。

これら2つのページをチェックすることができます：

[Apple Developer Documentation](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/3747256-interruptionlevel)

[Apple Developer Documentation](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/3821031-relevancescore)

---

:sparkles: :sparkles: :sparkles: 

:relaxed: [Twitter ツイッター @MszPro](https://twitter.com/MszPro)

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