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("北海道")
}
}
