SwiftUI 入門やるよ!チュートリアルしてみる!v3 ユーザー入力操作

SwiftUI

前回はこちら

SwiftUI 入門やるよ!チュートリアルしてみる!v2 リスト作成とナビゲーション
v1はこちらv2 Building lists and navigation リストとナビゲーションこちらを進めます!section 1 Create a landmark model ランドマ...

ユーザー入力!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 this feature, you’ll start by adding a ...

section 1 お気に入りにマークする

Model/LandmarkにisFavoriteを追加

LandmarkRowに isFavoriteのとき Image star.fillを表示

            if landmark.isFavorite {
                Image(systemName: "star.fill")
                    .foregroundStyle(.yellow)
//                    .foregroundColor(.yellow)
            }

systemNameは SF Symbols
SF Symbolsはこちらに
https://developer.apple.com/jp/sf-symbols/

8000もあるそうです…なんでもありそうですね^^
アニメーションやら色々使えるっそうです

section 2 リストフィルター

isFavoriteみたいなのを表示するだけという

LandmarkList に @Stateを追加

@State private var showFavoritesOnly = false
struct LandmarkList: View {
    @State private var showFavoritesOnly = true
    // フィルターされたlandmarksを生成
    var filteredLandmarks: [Landmark] {
        landmarks.filter { landmark in
            (!showFavoritesOnly || landmark.isFavorite)
        }
    }
    
    var body: some View {
        NavigationSplitView{
            // フィルターされたものを読み込み
            List(filteredLandmarks) { landmark in
                NavigationLink{
                    LandmarkDetail(landmark: landmark)
                } label: {
                    LandmarkRow(landmark: landmark)
                }
            }
            .navigationTitle("Landmarks")

        } detail: {
            Text("select a landmark")
        }
    }
}

showFavoritesOnlyを trueにするとスター付きのみに

section 3 add control to toggle the state ステートをトグルでコントロールする

LandmarkList の List に ForEachを入れる。動的と静的の混在ができるそうな? 毎回foreach入れててもよいのかな?

今回は、 Listの中に Toggle と ForEach の2つが入る形

@State にアクセスするときは $ をつける

List に  .animation(.default, value: filteredLandmarks) をつけると少しニュルッと動く

そのままですがシンプルに動きました。

section 4 use observation for storage ストレージ監視?観察

jsonファイルのデータ書き換えを監視?

@Observable
class ModelData {   
}

@Observableをつけて class ModelData作成

observableのプロパティが変わるとswiftui viewが更新される?

ソースコード選択範囲の移動
行を選択状態のとき command + option + { で上 }で下に移動できる

var landmarksをModelDataの中に移動

@Observable
class ModelData {
    var landmarks: [Landmark] = load("landmarkData.json")
}

section 5 Adopt the model object in your views . モデルオブジェクトを採用

LandmarkList に @Environment(ModelData.self) var modelData を追加

Environmentは環境変数的にデータを渡せるようにするものらしい

#Preview {
    LandmarkList().environment(ModelData())
}

previewから LandmarkListを呼ぶときに environmentでModelData()を渡しているので @EnvironmentでModelData.selfを使えると

LandmarkRowとLandmarkDetail の previewの読み出しも変更しておく

#Preview {
    let landmarks = ModelData().landmarks
    return LandmarkDetail(landmark: landmarks[0])
}

ContentViewの呼び出しも

#Preview {
    ContentView().environment(ModelData())
}

environmentで渡すのはあまり良くないっぽいけどとりあえず?

LandmarkApp にもModelDataからの読み出しを追加

@main
struct LandmarksApp: App {
    @State private var modelData = ModelData()
    
    var body: some Scene {
        WindowGroup {
            ContentView().environment(modelData)
        }
    }
}

これで全部動きましたと。

section 6 favorite ボタンを追加

swiftui view で FavoriteButton.swift

@Binding を使って元データと連携

struct FavoriteButton: View {
    @Binding var isSet: Bool
    
    var body: some View {
        Button {
            isSet.toggle()
        } label: {
            Label("Toggle Favorite", systemImage: isSet ? "star.fill":"star")
                .labelStyle(.iconOnly)
                .foregroundStyle(isSet ? .yellow:.gray)
        }
    }
}

#Preview {
    FavoriteButton(isSet: .constant(true))
}

Buttonを追加してタップでisSet.toggle / 表示は★ isSetにより変更

Binding : landmarkのisFavoriteとisSetを繋ぐ作業

LandmarkDetailにて

struct LandmarkDetail: View {
// environment呼び出しに
    @Environment(ModelData.self) var modelData
   
    var landmark: Landmark
    
    var landmarkIndex: Int{
// 現在のlandmarkのインデックスを取得、配列利用のため
        modelData.landmarks.firstIndex(where:{$0.id == landmark.id})!
    }
    
    
    var body: some View {
        // 渡されたmodelData を Bindableで使用
        @Bindable var modelData = modelData
        
        ScrollView {
            MapView(coordinate:
                        landmark.locationCoordinate)
            .frame(height:300)
            
            CircleImage(image: landmark.image).offset(y:-130).padding(.bottom,-130)
            
            VStack(alignment:.leading) {
                
                HStack{
                    Text(landmark.name)
                        .font(.title)
// 現在のindexを使用してlandmarkデータを渡す。Bindingされているので、on offが反映される。
                    FavoriteButton(isSet: $modelData.landmarks[landmarkIndex].isFavorite)
                }
                HStack {
                    Text(landmark.park)
                        .font(.subheadline)
                    Spacer()
                    Text(landmark.state).font(.subheadline)
                }
                
                Divider()
                
                Text("About \(landmark.name) ").font(.title2
                )
                Text(landmark.description).font(.subheadline)
            }.padding()
            
        }
        .navigationTitle(landmark.name)
        .navigationBarTitleDisplayMode(.inline)
//        Spacer()
        
    }
}

#Preview {
    let modelData = ModelData()
    return LandmarkDetail(landmark: modelData.landmarks[0]).environment(modelData)
}

List -> Detail -> Button とBindingされるので、LandmarkList の preview操作で
DetailでのButtonでisFavorite変更が、Listに戻ったときも反映されている

#Preview {
    LandmarkList().environment(ModelData())
}

ListのpreviewのModelDataが変更される。まだjson保存は何もしていない

Check Your Understanding

Great job, you’ve answered all the questions for this tutorial.

相変わらず英語難しい。正解っぽいのはわかるけど翻訳しないと正解はわからない^^;

次は!?

Drawing paths and shapes 線と図形描画?

お気軽にコメントください!

スパム対応のためコメント認証に数日かかることがありますが、お気軽にコメントいただけると嬉しいです^^

コメント

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