1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftUIでファイルを開く/保存(macOS)

Last updated at Posted at 2023-03-15

概要

  • AppKitではファイルを開く/保存する際にNSOpenPanel/NSSavePanelを使用していましたが、SwiftUIではfileImporter/fileExporterが利用できます。
image

参考

実装

  • まずファイルのアクセス権が必要となるので、SandboxのUser Selected FileRead/Writeに設定します。
image
  • また保存するファイルの形式に合わせて、FileDocumentのサブクラスを作る必要があります。
import SwiftUI
import UniformTypeIdentifiers

/// refs: https://www.hackingwithswift.com/quick-start/swiftui/how-to-export-files-using-fileexporter
struct TextFile: FileDocument {
    // tell the system we support only plain text
    static var readableContentTypes = [UTType.plainText]

    // by default our document is empty
    var text = ""

    // a simple initializer that creates new, empty documents
    init(initialText: String = "") {
        text = initialText
    }

    // this initializer loads data that has been saved previously
    init(configuration: ReadConfiguration) throws {
        if let data = configuration.file.regularFileContents {
            text = String(decoding: data, as: UTF8.self)
        }
    }

    // this will be called when the system wants to write our data to disk
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = Data(text.utf8)
        return FileWrapper(regularFileWithContents: data)
    }
}
  • 呼び出し例は以下の通りとなります。
import SwiftUI

struct ContentView: View {
    
    @State private var text = ""
    @State private var isSaveButtonDisable = true
    @State private var exporterPresented = false
    @State private var importerPresented = false
    
    var body: some View {
        
        VStack {
            textField()
            HStack {
                Spacer()
                saveButton()
                openButton()
            }
        }
        .padding()
        .fileExporter(isPresented: $exporterPresented,
                      document: TextFile(initialText: text),
                      contentType: .plainText,
                      defaultFilename: "Untitled.txt",
                      onCompletion: { result in
            switch result {
            case .success(let url):
                print("\(url)に保存が完了しました!")
            case .failure(let error):
                print(error.localizedDescription)
            }
        })
        .fileImporter(isPresented: $importerPresented,
                      allowedContentTypes: [.plainText],
                      allowsMultipleSelection: false) { result in
            switch result {
            case .success(let urls):
                guard let url = urls.first else { return }
                do {
                    let contents = try String(contentsOf: url)
                    text = contents
                } catch {
                    print(error.localizedDescription)
                }
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }
}
// MARK: - ViewBuilder

extension ContentView {
    
    @ViewBuilder
    private func textField() -> some View {
        TextField("Enter here!", text: $text, axis: .vertical)
            .lineLimit(3, reservesSpace: true)
            .textFieldStyle(.roundedBorder)
            .onChange(of: text) { newValue in
                isSaveButtonDisable = newValue.isEmpty
            }
    }
    
    @ViewBuilder
    private func saveButton() -> some View {
        Button {
            exporterPresented.toggle()
        } label: {
            Text("Save...")
        }
        .disabled(isSaveButtonDisable)
    }
    
    @ViewBuilder
    private func openButton() -> some View {
        Button {
            importerPresented.toggle()
        } label: {
            Text("Open...")
        }
    }
}

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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?