UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Document based app not editable

Forums > macOS

Hello, I'm training on document based app, and I can't understand why my file is not editable. Every time I add a letter to a text field, it is deleted immediatly.

I know this is a very important skill to get, but I'm completely stuck. Thank you very much

ContentView

import SwiftUI

struct ContentView: View {
    @Binding var document: FamilyDocument

    var body: some View {
        FamilyView(family: $document.family)
    }
}

DocumentAppMacApp

import SwiftUI

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

FamilyDocument

import SwiftUI
import UniformTypeIdentifiers

struct FamilyDocument: FileDocument {
    // tell the system we support only plain text
    static var readableContentTypes = [UTType.plainText]

    // by default our document is empty
    @State var family: Family

    // a simple initializer that creates new, empty documents
    init() {
        print("Init family file")
        family = Family(animals: [
            Animal(name: "Thémis", species: "Chien", age: 1),
            Animal(name: "Fidji", species: "Chat", age: 13),
        ])
        print(family.animals.count)
    }

    // this initializer loads data that has been saved previously
    init(configuration: ReadConfiguration) throws {
        if let data = configuration.file.regularFileContents {
            let decoder = JSONDecoder()
            let decodedData = try! decoder.decode(Family.self, from: data)
            family = decodedData
            print("Data read")
        } else {
            throw CocoaError(.fileReadCorruptFile)
        }
    }

    // this will be called when the system wants to write our data to disk
    func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
        let encoder = JSONEncoder()
        guard let data = try? encoder.encode(family) else {
            print("File not readable")
            throw CancellationError()
        }
        print("To be saved.")
        return FileWrapper(regularFileWithContents: data)
    }
}

FamilyView

import SwiftUI

struct FamilyView: View {
    @Binding var family: Family

    var body: some View {
        NavigationStack {
            Text("Here is my family")
            Text(family.name)
            Text("There are \(family.animals.count) animals.")
            ForEach($family.animals) { $animal in
                AnimalView(animal: $animal)
            }
        }
        .toolbar {
            Button("Add") {
                family.addAnimal()
            }
            Button("Clear") {
                family.clear()
            }
        }
        .navigationTitle("Family")
    }
}

struct FamilyView_Previews: PreviewProvider {

    static var previews: some View {
        let animals = [
            Animal(name: "Thémis", species: "Chien", age: 1)
        ]
        let family = Family(animals: animals)
        FamilyView(family: .constant(family))
    }
}

Family

import SwiftUI

struct Family: Codable {
    enum CodingKeys: CodingKey {
        case animals
        case name
    }

    @State var animals: [Animal]
    @State var name: String = "Default name"

    init() {
        self.animals = []
    }

    init(animals: [Animal]) {
        self.animals = animals
        self.name = "Init name"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        animals = try container.decode([Animal].self, forKey: .animals)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(animals, forKey: .animals)
    }

    func clear() {
        animals = []
        print("Clear!")
    }

    func addAnimal() {
        animals.append(Animal())
        print("New animal append!")
    }
}

AnimalView

import SwiftUI

struct AnimalView: View {
    @Binding var animal: Animal

    var body: some View {
        List {
            HStack {
                Text("Name")
                Spacer()
                TextField("name", text: $animal.name)
            }
            HStack {
                Text("Species")
                Spacer()
                TextField("species", text: $animal.species)
            }
            HStack {
                Text("Age")
                Spacer()
                TextField("age", value: $animal.age, formatter: NumberFormatter())
            }
        }
    }
}

struct AnimalView_Previews: PreviewProvider {
    static var previews: some View {
        let animal = Animal(name: "Thémis", species: "Chien", age: 1)
        AnimalView(animal: .constant(animal))
    }
}

Animal

import SwiftUI

struct Animal: Codable, Identifiable {
    var id = UUID()
    var name: String = ""
    var species: String = ""
    var age: Int = 0
}

3      

A problem I see in the code you showed is you are using the @State property wrapper outside a SwiftUI view. You use @State in the FamilyDocument struct.

@State var family: Family

And in the Family struct.

@State var animals: [Animal]
@State var name: String = "Default name"

@State properties are owned by SwiftUI views. You should not use them in your model structs. Remove @State from the variables in the FamilyDocument and Family structs.

The family binding in FamilyView should use @State instead.

@State var family: Family

You did not show the code for the content view so I'm not sure how the family and animal views interact. You may need the animal binding in AnimalView to use @State as well.

3      

You did not show the code for the content view so I'm not sure how the family and animal views interact. You may need the animal binding in AnimalView to use @State as well.

I did, it was the first item. But thank you very much, you reminded me a very basic rule that I forgot: @State is for views and nothing else.

So I followed your instructions and got an error with my methods clear() and addAnimal() inside Family, saying that self is immutable. It's logical, because structures are immutables (that's why I added @State). So I changed Family into a class conform to ObservableObject and with animals and name @Published, and it's okay now.

3      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

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.