前回の記事 で SwiftUI の魅力をお伝えしましたが、今回から実際の UI パーツの実装をしていきたいと思います。

まずはアプリの基本となるリスト表示です。

UIKit で実装したことのある方だと、「UITableView を使って Delegate と DataSource を設定して…」という手順が思い浮かぶかと思いますが、SwiftUI では Delegate などのコールバック的な処理は必要としません。

struct ContentView: View {
    var body: some View {
        List {
            Text("Google")
            Text("Apple")
            Text("Facebook")
        }
    }
}

これだけです。
これを実行(もしくは Xcode のプレビュー)すると、リスト表示ができていることがわかります。

次に配列を表示してみます。

struct ListView: View {
    private var items = ["Google", "Apple", "Facebook", "Amazon", "Microsoft", "Twitter", "TOYOTA", "Baidu", "LINE", "Tesla"]

    var body: some View {
        List {
            ForEach(items, id: \.self) { item in
                Text(item)
            }
        }
    }
}

ForEach の中に id: .self という見慣れない書き方がありますが、これは List 内で要素を識別するのに ID が必要なためです。

List 内のデータの要素(この場合は items)を構造体にして id というプロパティを用意すれば不要になるのですが、ここでは深く触れないこととします。

実行するとこうなります。

さらに、項目を追加できるようにしてみます。

struct ListView: View {
    @State private var showInputModal = false
    @State private var newName = ""
    @State private var items = ["Google", "Apple", "Facebook", "Amazon", "Microsoft", "Twitter", "TOYOTA", "Baidu", "LINE", "Tesla"]

    var body: some View {
        ZStack(alignment: .bottomTrailing) {
            List {
                ForEach(items, id: \.self) { item in
                    Text(item)
                }
            }
            .sheet(isPresented: $showInputModal, onDismiss: {
                if (self.newName != "") {
                    self.items.append(self.newName)
                    self.newName = ""
                }
            }) {
                ListInputView(name: self.$newName)
            }
            Button(action: {
                self.showInputModal.toggle()
            }) {
                Image(systemName: "plus.circle.fill")
                    .resizable()
                    .frame(width: 50, height: 50)
            }
            .padding()
        }
    }
}
struct ListInputView: View {
    @Environment(\.presentationMode) var presentationMode
    @Binding var name: String

    var body: some View {
        VStack{
            Spacer()
            Text("企業名を入力")
            TextField("企業名", text: $name)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            Spacer()
            HStack {
                Spacer()
                Button(action: {
                    self.name = ""
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("キャンセル")
                }
                Spacer()
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Text("OK")
                }
                .disabled(name.count == 0)
                Spacer()
            }
        }
        .padding()
    }
}

色々と新しい要素が出てきました。

まず @State ですが、これが付けられた変数は「状態変数」となり、変数の値が変わるとビューが再描画するようになります。

sheet ではモーダルで表示するビューを定義します。
showInputModal の値によって表示状態が決まり、onDismiss でビューが消える際の処理を書きます。
ListInputView は実際に表示されるビューです。

このモーダルビューを表示するトリガが Button です。
action にタップ時の処理を書きます。

また、Button List の前面に表示するために、全体を ZStack (Z 方向のレイアウト)で囲んでいます。

ListInputView では name を設定して、元のビューに返しています。
こちらのビューの細かい説明は省きますが、@Binding で宣言した変数はビューと同期されるようになり、ここで入力された値が自動的に name に入り、それが元のビューにまで反映されます。

実行するとこんな感じ。
(サンプル用に作ったアプリを流用しているので、ナビゲーションバーが付いていることは気にしないでください)

…と、こんな感じでよくあるリスト表示と項目の追加が実装できました。

UIKit だったらリスト表示だけでなく、モーダルの画面とのデータやりとりも考えないといけないので、それと比べるとかなり簡潔にコードが書けることがわかるかと思います。

iOS エンジニアの皆さん、少しずつ SwiftUI 始めましょう。