前節で, App内にあらかじめバンドルさせておいたJSONファイルからPhotoDataを作り, リストに表示することができました. しかしバンドルしたJSONはあくまで読み込み専用で,編集したデータを保持できません. そこで今節ではバンドルではなく自由に読み書きできるストレージを使ってJSONを扱う方法を紹介します.
まず準備です.
- SFSwiftUI03-3.swiftpm を開く
(サンプルコード)
- 03-2を完了したコードと同様
- photos配列を空っぽにしている
- 後で読み込むためのJSONファイルが入っている
- データがないため, プレビューも何もない状態から始まることを確認
実行結果#
コード1#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import SwiftUI
struct ContentView: View {
@State var photos:[PhotoData] = []
var body: some View {
List {
Section {
ForEach( $photos ) { bind_photo in
NavigationLink( destination:{
SceneryDetail( data:bind_photo )
},
label: {
SceneryRow( data:bind_photo.wrappedValue )
})
}
.onDelete { index_set in
photos.remove( atOffsets:index_set )
}
}
header: {
HStack {
Text( "景色写真" )
Spacer()
Button {
let new_data = PhotoData( type:"景色", imageName:"CC0_06", title:"新規", message:"追加したデータです" )
photos.append( new_data )
}
label: {
Image( systemName:"plus" )
.font( .title2 )
.foregroundStyle( Color.accentColor )
}
}
}
}
.listStyle( .grouped )
.navigationTitle( "写真集" )
}
}
|
それではファイルの読み書きが可能なストレージへアクセスするプログラムを追加します. iOSではAppごとにファイルの保存場所が独立しています. 用途によって保存先が異なりますが, 今回はもっともスタンダードな Documentフォルダ を使います.
- 新規のファイルを作成, LocalPath.swift に名前を変更する
ファイルの追加#
名前の変更#
- FileManager.default.urls( for:in: ) で DocumentフォルダのURLを取得
- .documentDirectory, .userDomainMask を指定する
- docURLs.first!.path がDocumentsのファイルパス
- docFile( name:ext: )関数 を作成する
- 名前と拡張子を渡すと, Documentsフォルダのファイル名のフルパスが得られる
コード2#
1
2
3
4
5
6
7
8
9
10
11
|
import Foundation
class LocalPath {
static let docURLs = FileManager.default.urls( for:.documentDirectory, in:.userDomainMask )
static let docFolderPath = docURLs.first!.path
static func docFile( name:String, ext:String ) -> String {
return docFolderPath + "/" + name + "." + ext
}
}
|
次に, 作成したLocalPath.docFile関数をつかってPhotoDataをストレージに保存する関数を追加します.
-
PhotoData.swiftを開く
-
PhotoData構造体に save( path: data: )関数 を作成する
- path:は保存する場所のパス
- data:は保存したいPhotoData配列
-
JSONEncoder().encode( data ) で, PhotoDataをJSONのData型に変換
-
URL( fileURLWithPath:path ) で保存先のパスをURLに変換suru
- user_json.write( to: options: ) で,ファイル出力を試みる
-
PhotoData構造体のプロトコルに Equatable を追加する
- Equatableは中身が比較できることを示す
- 後述の .onChange( of: ) を使えるようにするため追加しておく
コード3#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import Foundation
struct PhotoData : Identifiable, Codable, Equatable {
var id:String = UUID().uuidString
var type:String
var imageName:String
var title:String
var message:String
static func parse( json:Data ) -> [PhotoData] {
do {
let photos = try JSONDecoder().decode( [PhotoData].self, from:json )
return photos
}
catch {
print( "JSONをPhotoData配列に変換できませんでした." )
print( error.localizedDescription )
return []
}
}
static func save( path:String, data:[PhotoData] ) {
let user_json = try! JSONEncoder().encode( data )
let url = URL( fileURLWithPath:path )
try! user_json.write( to:url, options:[.atomic] )
}
}
|
try!が2箇所出てきますが, 後ほどのページで改良するので今はtry!のままにしておいてください.
ストレージにJSONを書き込む準備ができました. ContentViewで使ってみましょう.
-
ContentViewでファイルの保存先を決めた変数を用意する
- let user_json_path = LocalPath.docFile( … ) を追加
-
ListのnavigationTitleの後に .onChange( of: ) を追加
- .onChangeは 指定した値が変化するたび処理が行われる
-
.onChangeの中で PhotoData.save( … ) を実行する
コード4#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
import SwiftUI
struct ContentView: View {
let user_json_path = LocalPath.docFile( name:"user_photos", ext:"json" )
@State var photos:[PhotoData] = []
var body: some View {
List {
Section {
ForEach( $photos ) { bind_photo in
NavigationLink( destination:{
SceneryDetail( data:bind_photo )
},
label: {
SceneryRow( data:bind_photo.wrappedValue )
})
}
.onDelete { index_set in
photos.remove( atOffsets:index_set )
}
}
header: {
HStack {
Text( "景色写真" )
Spacer()
Button {
let new_data = PhotoData( type:"景色", imageName:"CC0_06", title:"新規", message:"追加したデータです" )
photos.append( new_data )
}
label: {
Image( systemName:"plus" )
.font( .title2 )
.foregroundStyle( Color.accentColor )
}
}
}
}
.listStyle( .grouped )
.navigationTitle( "写真集" )
.onChange( of:photos ) { oldData, newData in
PhotoData.save( path:user_json_path, data:newData )
}
}
}
|
+ボタンから項目を追加してみてください. 見た目ではリストが追加されるだけでわかりませんが裏側で .onChangeが動作し, ストレージへの保存が行われます. ただ現段階では保存ができているかを確認する術がありません. 次節でストレージに保存したJSONファイルを読み込み, 正しく保存されているかを確認します.
Playgrounds Appでの設定#
デバイスにファイルを書き出す場合、ファイルアクセスの許可が必要な場合があります(MacのPlaygrouds Appのプレビューなど). もしJSONファイルの書き出しがうまくできない場合機能を追加してください.
プロジェクトの「機能」を選択します.
画面右上の「+」を押します.
「ファイルアクセス」を選択します.
ピクチャなどへのアクセスなど詳しい設定もありますが, 細かい設定は行なわず追加してください.