NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: DocumentGroup / FileDocument

Forums > SwiftUI

i have a DocumentGroup app and some ui input or object modifications does not appear as "edited" beside the file name. means the FileDocument think it was not modified.

TextEditor raised a "edited" TextField not ColorPicker not DatePicker not

(macOS Xcode SwiftUI)

   

seems i must replace @StateObject or @ObservedObject to @Binding.

it is very difficult to replace this afterwards ..

i have a few parts working and TextField show me that i edited the document in the title bar. i guess it will resolve other issues with input for date/color drag and drop in arrays too.

edit: now it works half. if me add a column to a board.columns array via method board.addColumn() the view does not get a update :( it updates the views if me put the xcode ide in front and if me switch back to my app it looks ok.

i have a data model with published properties. board,columns,cards each of them have a view BoardView, ColumnView, CardView. once i bind a color picker to the property but also set a random color into the card object itself. now with @Binding the color picker give me a edtited filedocument but via menu and method the color change card.randomColor() does nothing, it not reflect to ui anymore.

   

can someone see my mistake here?

//
//  ContentView.swift
//  TestBinding

import SwiftUI
import UniformTypeIdentifiers

struct ContentView: View {

    @ObservedObject private var file: MyFile = MyFile() // @ObservedObject

    var body: some View {
        VStack {
            TextField("name 1", text: $file.data.name1)
            Text(file.data.name1)
            Button("Reset 1") {
                file.data.reset()
            }
            TextField("name 2", text: $file.data.data2.name2)
            Text(file.data.data2.name2)
            Button("1 new 2") {
                file.data.new2()
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    //@State static var file: MyFile = MyFile()
    static var previews: some View {
        ContentView()
    }
}

// , FileDocument
class MyFile: ObservableObject {
    static var readableContentTypes: [UTType] = [UTType.plainText]

    @Published var data : MyData1 = MyData1()

    /**
    required init(configuration: ReadConfiguration) throws {
        self.data = MyData1()
    }

    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = Data(data.name1.utf8)
        return FileWrapper(regularFileWithContents: data)
    }
     */

}

class MyData1: ObservableObject {
    @Published var name1: String = "new 1"
    @Published var data2 : MyData2 = MyData2() // @ObservedObject @Published

    func reset() {
        name1 = "default 1"
        data2.name2 = "default 2"
    }

    func new2() {
        data2 = MyData2()
    }
}

class MyData2: ObservableObject {
    @Published var name2: String = "new 2"

    func reset() {
        name2 = "default 2"
    }
}

basic app code

mport SwiftUI

@main
struct TestBindingApp: App {

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

   

Do you complete project 13 of HackingWithmacOS SwiftUI book ?

I just start this project, you can see my code here : https://github.com/Sebastien-Remy/HackingwithmacOS-13-Screenable

Hope it can help you

@twothrow may be this post had to move to the macOs forum ?

1      

If you are going to use a class for your document, the class must conform to ReferenceFileDocument. FileDocument is for structs.

class MyFile: ObservableObject, ReferenceFileDocument {

}

There is very little documentation on ReferenceFileDocument so there will be a struggle using it.

1      

Hi Markus

One of the issues I see in your code is that you are using nearly everywhere reference (=class based) types instead of value types. SwiftUI is heavily based on value types to automatically detect changes and therefore redraw the screen. Is this "by coincidence" or a design decision?

If I were you, I went with plain value types (=struct based) and pass a binding to the ContentView which is then editing all the attributes. Therefore you could use FileDocument instead of ReferenceFileDocument as the document type.

Other issues I see in your code: You are using @Published for classes (data in MyFile and data2 in MyData1). This doesn't work as there is not automatic propagation from changes happening in MyData2 to MyData1).

And a third issue: Never ever initialize an @ObservedObject variable in your view (as you did in your ContentView). They are meant to be set by the caller of the view. If you want to initialize ObservableObject within a view (and therefore its lifetime is linked to the view) it should be stored as @StateObject.

regards Philipp

1      

Here is my solution using just value types (based on your code). It can be improved by using Codable on MyData1 and MyData2 to load and store the content of the file.

import SwiftUI
import UniformTypeIdentifiers

@main
struct TestApp: App {
    var body: some Scene {
        DocumentGroup(newDocument: MyFile()) { file in
            ContentView(file: file.$document)
        }
    }
}

struct ContentView: View {
    @Binding var file: MyFile

    var body: some View {
        VStack {
            TextField("name 1", text: $file.data.name1)
            Text(file.data.name1)
            Button("Reset 1") {
                file.data.reset()
            }
            TextField("name 2", text: $file.data.data2.name2)
            Text(file.data.data2.name2)
            Button("1 new 2") {
                file.data.new2()
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    @State static var file: MyFile = MyFile()
    static var previews: some View {
        ContentView(file: $file)
    }
}

struct MyFile: FileDocument {
    static var readableContentTypes: [UTType] = [UTType.plainText]

    var data: MyData1 = MyData1()

    init() {
    }

    init(configuration: ReadConfiguration) throws {
        self.data = MyData1()
        if let data = configuration.file.regularFileContents,
           let name1 = String(data: data, encoding: .utf8)
        {
            self.data.name1 = name1
        }
    }

    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let data = Data(data.name1.utf8)
        return FileWrapper(regularFileWithContents: data)
    }
}

struct MyData1 {
    var name1: String = "new 1"
    var data2: MyData2 = MyData2()

    mutating func reset() {
        name1 = "default 1"
        data2.name2 = "default 2"
    }

    mutating func new2() {
        data2 = MyData2()
    }
}

struct MyData2 {
    var name2: String = "new 2"

    mutating func reset() {
        name2 = "default 2"
    }
}

1      

thank you all for explanation and examples. :)

yes it seems i must use structs and functions with mutating.

another interesting template is the date planner from swift play ground.

   

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.