0

I am trying to create a basic to-do application for command-line in Swift. Below is my function to add a new item to the to-do array, but the new entry keeps overwriting the old one, instead of creating a new one. In the end, there is only one entry in the todo.json file.

When I multiply the entries and .append statements manually it works, but probably my brain is too dead to figure it out at the moment.


struct TodoItem: Codable {
    let name: String
}

var todoList = [TodoItem]()

func addToList(_ item: String) -> String {
    let todoItem = TodoItem(name: item)
    todoList.append(todoItem)

    do {
        let fileURL = try FileManager.default
            .url(for: .applicationSupportDirectory,
                 in: .userDomainMask,
                 appropriateFor: nil,
                 create: true)
             .appendingPathComponent("example")
             .appendingPathExtension("json")

        let encoder = JSONEncoder()
        try encoder.encode(todoList).write(to: fileURL)

    } catch {
        return "Error: \(error.localizedDescription)"
    }
        return "Item added: \(todoItem.name)"
}
4
  • Have you debugged your code, are your sure todoList contains anything before the function is called? Do you get any errors? Commented Mar 18, 2020 at 19:15
  • Yes, it definitely is not empty. I also run the command several times, I can see the previous entry, but it gets replaced with the new one. Commented Mar 18, 2020 at 19:50
  • @fankibiber: please make a new question for your second problem. Commented Mar 18, 2020 at 20:13
  • @koen, edited that part out. Commented Mar 18, 2020 at 20:51

1 Answer 1

1

Your code works fine. I think the problem rests in the fact that todoList is empty when you run this. But you can write code to retrieve the contents of the JSON:

var todoList: [TodoItem] = []

func retrieveToDoList() {
    guard let data = try? Data(contentsOf: fileURL) else { return }

    todoList = (try? JSONDecoder().decode([TodoItem].self, from: data)) ?? []
}

So, for example, consider:

retrieveToDoList()

addToList("foo")
addToList("bar")
addToList("baz")

print(todoList)

// if you want to check the json

let data = try! Data(contentsOf: fileURL)
let json = String(data: data, encoding: .utf8)!
print(json)

That results in:

[MyApp.TodoItem(name: "foo"), MyApp.TodoItem(name: "bar"), MyApp.TodoItem(name: "baz")]
[
  {
    "name" : "foo"
  },
  {
    "name" : "bar"
  },
  {
    "name" : "baz"
  }
]

And if I later do:

addToList("abc")
addToList("def")
addToList("hij")

I then get:

[
  {
    "name" : "foo"
  },
  {
    "name" : "bar"
  },
  {
    "name" : "baz"
  },
  {
    "name" : "abc"
  },
  {
    "name" : "def"
  },
  {
    "name" : "hij"
  }
]

So, every time the app starts up, just make sure to call retrieveToDoList before trying to append items or else the toDoList will be empty.


FYI, this is the code I used to generate the above. Hopefully it illustrates the idea.

struct TodoItem: Codable {
    let name: String
}

class ViewController: UIViewController {
    private var todoList: [TodoItem] = []

    private let fileURL = try! FileManager.default
        .url(for: .applicationSupportDirectory,
             in: .userDomainMask,
             appropriateFor: nil,
             create: true)
        .appendingPathComponent("example.json")

    override func viewDidLoad() {
        super.viewDidLoad()

        retrieveToDoList()

        addToList("foo")
        addToList("bar")
        addToList("baz")

        print(todoList)

        // if you want to check the json

        let data = try! Data(contentsOf: fileURL)
        let json = String(data: data, encoding: .utf8)!
        print(json)
    }
}

private extension ViewController {
    func retrieveToDoList() {
        guard let data = try? Data(contentsOf: fileURL) else { return }

        todoList = (try? JSONDecoder().decode([TodoItem].self, from: data)) ?? []
    }

    @discardableResult
    func addToList(_ item: String) -> String {
        let todoItem = TodoItem(name: item)
        todoList.append(todoItem)

        do {
            let encoder = JSONEncoder()
            encoder.outputFormatting = .prettyPrinted
            try encoder.encode(todoList).write(to: fileURL)
        } catch {
            return "Error: \(error.localizedDescription)"
        }

        return "Item added: \(todoItem.name)"
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Yes! Thank you, now I understand.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.