SwiftUIで、データをそのまま持たせていると、表示される度に、初期化が何度も行われてしまいます。
それでは、困ってしまう場合があります。
そんな時はEnvironmentObjectを使います。
どう使うかというと、まず、初期化を一度だけにしたいデータ(配列だったり、普通のデータだったり)を持たせるクラスにObservableObjectプロトコルを継承させます。(サンプルコードではCityDataクラスです)
サンプルコードで保持したい実際のデータはCityDataクラスのメンバのmyDataフィールドである[TwitterData]の配列です。
そして、CityDataクラスを利用したいViewで(サンプルコードではMyDetailView)、CityData型のメンバを宣言する時に、@EnvironmentObject属性を付けます。
最後にSaigaiInfoAppクラスのbody内で、ContentView()に.enviromentObject(CityData())をModifierします。
このサンプルコードでは、初期ViewはContentViewで(都道府県名のリストを表示するView)、そこでListの一つの都道府県名をクリックすると、MyDetailViewが(市区町村名のリストを表示するView)呼び出されます。
SaigaiInfoApp.swift
import SwiftUI @main struct SaigaiInfoApp: App { var body: some Scene { WindowGroup { ContentView() .environmentObject(CityData()) /*複数保持したいデータがある場合は次のように続けます。 .environmentObject(HogeClass()) .environmentObject(HogehogeClass()) */ } } }
ContentView.swift
import SwiftUI class TwitterData: Identifiable { //都道府県名 var prefecture: String = "" //市区町村名 var city: String = "" var twitter: String = "" var note: String = "" } class Prefecture: ObservableObject { @Published var myData = [TwitterData]() @State var loaded: Bool = false init() { if self.loaded { return } if let path: String = Bundle.main.path(forResource: "saigai-prefList", ofType: "csv") { let enc = String.Encoding.utf8 do { let s = try String(contentsOfFile: path, encoding: enc) let rawData = s.split(separator: "\r\n") for d in rawData { if String(d) == "都道府県" { //何もしない } else { print(d) let s = TwitterData() s.prefecture = String(d) myData.append(s) } } self.loaded = true } catch { print("ファイルの内容の取得に失敗しました。") } } } } struct ContentView: View { @ObservedObject var prefectureList = Prefecture() init() { } var body: some View { NavigationView { VStack { List(self.prefectureList.myData) { item in NavigationLink(destination: MyDetailView(item.prefecture)) { Text(item.prefecture) .font(.subheadline) Spacer(minLength: 5).fixedSize() } } } }.navigationViewStyle(StackNavigationViewStyle()) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
CityData.swift
import Foundation class CityData: ObservableObject { @Published var myData = [TwitterData]() init() { if let path: String = Bundle.main.path(forResource: "saigai-twitterList", ofType: "csv") { let enc = String.Encoding.utf8 do { let s = try String(contentsOfFile: path, encoding: enc) let rawData = s.split(separator: "\r\n") var blnFirst: Bool = true for d in rawData { if blnFirst { blnFirst = false } else { print(d) let data = d.split(separator: ",") let twitter: TwitterData = TwitterData() twitter.prefecture = String(data[1]) twitter.city = String(data[2]) twitter.twitter = String(data[3]) if data.count == 5 { twitter.note = String(data[4]) } self.myData.append(twitter) } } } catch { print("ファイルの内容の取得に失敗しました。") } } } func getData(_ item: String) -> [TwitterData] { var data = [TwitterData]() for d in self.myData { if d.prefecture == item { data.append(d) } } return data } }
DetailView.swift
import SwiftUI struct MyDetailView : View { @EnvironmentObject var TwitterList: CityData var pref: String init(_ d: String) { pref = d } var body: some View { VStack{ Text("タップ1回でTwitterが、2回で役所のホームページが開きます") Divider() Text(self.pref).font(.title) List(TwitterList.getData(self.pref)) { item in HStack { Text(item.city) VStack { Text("Twitter:" + item.twitter) Text(item.note) }.gesture(TapGesture(count:2) .onEnded{ (val) in if item.note.contains("http") { let text: String = item.note debugPrint(text) let url = URL(string: text) if UIApplication.shared.canOpenURL(url!) { UIApplication.shared.open(url!, options:[:], completionHandler: nil) } } }) .gesture(TapGesture(count: 1) .onEnded{ val in if item.twitter.contains("@") { let text: String = "https://twitter.com/" + item.twitter.suffix(item.twitter.count - 1) let url = URL(string: text) if UIApplication.shared.canOpenURL(url!) { UIApplication.shared.open(url!, options:[:], completionHandler: nil) } } }) }.font(.subheadline) } } } } struct MyDetailView_Previews: PreviewProvider { static var previews: some View { MyDetailView("北海道") } }