[Swift]SwiftUIチュートリアル Section6に入門してみた。[Handling User Input編]

Swift

はじめに

前回からの続きでSwiftUIのチュートリアル入門で、[Handling User Input編]になります。

もと記事サイト

Handling user input | Apple Developer Documentation
In the Landmarks app, a user can flag their favorite places, and filter the list to show just their favorites. To create...

Step0

このセクションではLandmarkの詳細画面にお気に入りボタンを設置し、お気に入りの登録・解除の変更を@Bindingを使用してModelDataに反映するようにします。

Step1

最初にお気に入りボタンとなるFavoriteButton.swift.を作成します。

import SwiftUI

struct FavoriteButton: View {
    var body: some View {
        Text("Hello, World!")
    }
}

struct FavoriteButton_Previews: PreviewProvider {
    static var previews: some View {
        FavoriteButton()
    }
}

Step2

isSetプロパティを宣言します。その際に@Bindingを付与します。これでisSetの変更内容がModelDataisFavoriteに反映されるようにします。
またFavoriteButton_PreviewsにもイニシャライザでisSetを渡します。@Bindingを付与しているためisSetの型が@Binding<Bool>となっているので、.constant(true)で変換します。

import SwiftUI

struct FavoriteButton: View {
    @Binding var isSet: Bool
    
    var body: some View {
        Text("Hello, World!")
    }
}

struct FavoriteButton_Previews: PreviewProvider {
    static var previews: some View {
        FavoriteButton(isSet: .constant(true))
    }
}

Step3

ボタンを設置します。ボタンを押された際にisSetの値を反転します。
お気に入りボタンらしくなるように星の画像も追加します。

import SwiftUI

struct FavoriteButton: View {
    @Binding var isSet: Bool
    
    var body: some View {
        Button(action: {
            isSet.toggle()
        }) {
            Image(systemName: isSet ? "star.fill" : "star")
                .foregroundColor(isSet ? Color.yellow : Color.gray)
        }
    }
}

struct FavoriteButton_Previews: PreviewProvider {
    static var previews: some View {
        FavoriteButton(isSet: .constant(true))
    }
}

Step4

ファイルが増えてきたのでフォルダーを作成して整理します。
CircleImage.swiftMapView.swiftFavoriteButton.swiftHelpersフォルダーを作成して中にいれます。
Landmarksフォルダーを作成しLandmarkList.swiftLandmarkRow.swiftLandmarkDetail.swiftを中にいれます。

Step5

LandmarkDetail.swiftを選択し、対象のLandmarkがModelDataのlandmarks配列の何番目に保存されているのか比較できるように、@EnvironmentObjectmodelDataを親Viewから受け取れるようにします。LandmarkDetail_Previewsにも変更を加えます。
比較する際にlandmarkIndexを使用してidが一致する要素番号を取得しています。

import SwiftUI

struct LandmarkDetail: View {
    @EnvironmentObject var modelData: ModelData
    var landmark: Landmark
    
    var landmarkIndex: Int {
        modelData.landmarks.firstIndex(where: { $0.id == landmark.id })!
    }
    
    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)
            
            CircleImage(image: landmark.image)
                .offset(y: -60)
                .padding(.bottom, -60)
            
            VStack(alignment: .leading) {
                Text(landmark.name)
                    .font(.title)
                HStack {
                    Text(landmark.park)
                        .font(.subheadline)
                    Spacer()
                    Text(landmark.state)
                        .font(.subheadline)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)
                
                Divider()
                
                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct LandmarkDetail_Previews: PreviewProvider {
    static let modelData = ModelData()
    
    static var previews: some View {
        LandmarkDetail(landmark: modelData.landmarks[0])
            .environmentObject(modelData)
    }
}

Step6

landmarkの名前を表示しているTextHStackで囲み、FavoriteButtonを追加します。
その際のイニシャライザにisSetmodelData.landmarks[landmarkIndex].isFavoriteを渡しますが、先頭に$を付与することでisSetmodelData.landmarks[landmarkIndex].isFavoriteをバインドさせてmodeldatalandmark.isFavoriteを自動で更新させています。

import SwiftUI

struct LandmarkDetail: View {
    @EnvironmentObject var modelData: ModelData
    var landmark: Landmark
    
    var landmarkIndex: Int {
        modelData.landmarks.firstIndex(where: { $0.id == landmark.id })!
    }
    
    var body: some View {
        ScrollView {
            MapView(coordinate: landmark.locationCoordinate)
                .ignoresSafeArea(edges: .top)
                .frame(height: 300)
            
            CircleImage(image: landmark.image)
                .offset(y: -60)
                .padding(.bottom, -60)
            
            VStack(alignment: .leading) {
                HStack {
                    Text(landmark.name)
                        .font(.title)
                    FavoriteButton(isSet: $modelData.landmarks[landmarkIndex].isFavorite)
                }
                HStack {
                    Text(landmark.park)
                        .font(.subheadline)
                    Spacer()
                    Text(landmark.state)
                        .font(.subheadline)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)
                
                Divider()
                
                Text("About \(landmark.name)")
                    .font(.title2)
                Text(landmark.description)
            }
            .padding()
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct LandmarkDetail_Previews: PreviewProvider {
    static let modelData = ModelData()
    
    static var previews: some View {
        LandmarkDetail(landmark: modelData.landmarks[0])
            .environmentObject(modelData)
    }
}

Step7

LandmarkList.swiftを選択し、ライブプレビューを実行してみましょう。
詳細画面で変更したお気に入りボタンのステータスが一覧画面に戻ると反映されていることが確認できます。

Landmarkの名前の隣にあるお気に入りの星をタップします。

一覧画面に戻ると先程の変更が反映されています!

まとめ

今回でHandling User Input編もお終いになります。ユーザのアクションを@Bindingを使用してデータソースと同期する方法がわかりました。これで簡単なアプリなら作成できそうですね。
次回からは[Drawing Paths and Shapes]編になります。図形の描画はレイアウト組む際に必要になることもあるので頑張って勉強しましょう。

コメント

タイトルとURLをコピーしました