解放区在住氷翠 緑の閃光
【解放区在住氷翠 緑の閃光】swift

【Swift】忘れないようにファイル保存の基本

2023-01-02 08:46

氷翠です。

iMac M1
メモリ:16GB
MacOS:Ventura 13.0.1
Xcode:14.1

情報としては2023.1.2時点での情報となります。

今回は単純なものです。テキストデータをデスクトップにファイル保存するだけというものです。

完成

これが完成の形で、テキストを入力する箇所と、ファイル保存するパスを表示する場所、それと保存するトリガーになるボタンを設置して完成という感じ。

あと、ほとんどの解説は動画の中でしているので、ここではあまり解説はしませんが、コードは載せておこうと思います。

まずこの「形」をつくるためのコードがこちら

import SwiftUI

struct ContentView: View {
    
    // 「TextEditor」で入力した文字を代入する変数を用意
    @State var textStr: String = ""
    
    // どこに保存されるのかを確認するために
    // この変数の中に保存先が代入されるようにします
    // そのため、下の「TextField」ではなく
    // 単なる「Text」でも可。
    @State var status: String = ""
    
    // 先ほどのクラスをここで利用するため
    // あらかじめ変数に代入しておきます
    // しかもこのクラスは不変なので「let」で宣言します
    let fs = fileSaveModel()
    
    var body: some View {
        VStack {
            TextEditor(text: $textStr)
            TextField("", text: $status)
            Button("ファイルに保存") {
                // 保存処理をここで行う
                // 実際には他のファイルにて保存処理をします
                // 保存したときに帰ってくる文字列は「status」
                status = fs.write(textStr)
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

とにかく、「@State」でこの形を形成している中で外部のデータのやりとりをする変数とか定数とかを利用していく。他のクラスを利用するにしてもそうだ。

更に、各コンポーネントの中で変数を使う時は「$」をつけてその後ろに変数名を記述するというルール。これで入力されたデータが変数に代入したり、変数の内容が変更された場合にはその内容を出力してくれる。

こういった基本的なものも覚えておく必要がある。

次のコードはファイルを保存していくコードになります。

import Foundation

class fileSaveModel: ObservableObject {
    
    // FileManagerを利用するため、そのメソッドまでを短縮します
    let fm = FileManager.default
    
    // ここからファイルに書き込む処理のメソッドとなります
    func write(_ text: String) -> String {
        // 文字列を返すように設定しておきます
        // そのため返す用の変数を用意しておきます
        var output = ""
        
        // 固定されたパスをある程度自動で生成してくれるので、それを利用します
        // 変数の型は「URL」型になります
        // パスの生成に失敗したときのことも考えて「guard」を利用します
        // 今回は自分のデスクトップに保存したいと考えます
        guard let path = fm.urls(for: .desktopDirectory, in: .userDomainMask).first else {
            fatalError("URL取得失敗")
        }
        // 上記のパスにファイル名を設定します
        let fullURL = path.appendingPathComponent("sample.txt")
        // ここから実際にデータ(テキスト)を書き込んでいく処理になります
        do {
            // 書き込むデータはメソッドの引き数にある「text」
            // 「atomically」は上書きするかどうかを強制的に判断します。
            // trueならすでにファイルが存在しているなら強制的に上書きします
            // 文字のエンコードは「UTF-8」を選択
            try text.write(to: fullURL, atomically: true, encoding: .utf8)
            // これで書き込みが完了したので、変数outputにそのパスを出力します
            output = fullURL.absoluteString
        } catch {
            // 例外エラーが発生したときの処理をここで
            output = "書き込み失敗"
        }
        // 最後に文字列を返して完了とします
        return output
    }
}

ここでの注意点は、クラス名の後ろに「ObservableObject」をつけるといいかと。というかつけないとエラーになるw

今回はファイルの操作に関することなので、「FileManager」というのを利用します。これを利用することで、フォルダの作成とかそのパスはフォルダなのかファイルなのかをチェックするとかできるようになります。

「func write(_ text: String) -> String」というメソッドを作成。「text」の中に文字列が入り、それをファイルに保存するというもの。そして最後には文字列でファイルのパスを返すという内容。

「guard」と利用することでエラーが起こると「fatalError」で文字列を返すようになる。

更に「do〜catch」でエラー回避。

「try」で文字列である「text」のメソッドとして「write」が使えるので、これでファイルに保存するというメソッドで。引き数は「to」はURL型の変数を指定し、「atomically」は上書きをするかどうか。同じファイル名のファイルがあるなら、強制的に上書きしてしまうので、注意が必要。「encoding」は文字のエンコードを指定します。ある程度いろんなエンコードが用意されている。でもMac上では基本的に今は「.utf8」なので特に気にしないようなものであればこれでいいかと思います。

保存先のパスが代入されている変数は「URL型」となっている。これは文字列ではないので、出力するときに文字列に変換しなくてはいけない。それが「fullURL.absoluteString」というものです。