You are starting with your data.json in your bundle.
You read that, change the values, and then write it to Documents folder.
So, when you want to read the file, you need to first see if it exists in the Documents folder. If so, read it from there. If it doesn't exist (e.g. first time the app is run), then read it from the bundle.
Edit
The general idea is:
- try to load the
json file from the app's Documents folder
- if that fails
- create a "blank" user data object and show the Name Input view
- if we successfully loaded the
json file
- after every change (name was entered, user answered a question), update the user data object and save (overwrite) the json file in the Documents folder
No need to have a .json file in your bundle, because you'll create a blank user data object if there is no .json file in the Documents folder.
Here is a complete example you can run and inspect:
struct UserData: Codable {
var fullname: String
var answered: Int
var correct: Int
}
class FileTestVC: UIViewController {
var userData: UserData!
let nameTextField = UITextField()
let nameLabel = UILabel()
let questionLabel = UILabel()
let resultNameLabel = UILabel()
let resultLabel = UILabel()
let userNameView = UIView()
let questionView = UIView()
let restartView = UIView()
let numQuestions: Int = 10
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
setupNameView()
setupQuestionView()
setupRestartView()
[userNameView, questionView, restartView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
v.layer.cornerRadius = 16
v.layer.borderWidth = 2
v.isHidden = true
view.addSubview(v)
}
userNameView.layer.borderColor = UIColor.systemBlue.cgColor
questionView.layer.borderColor = UIColor.systemRed.cgColor
restartView.layer.borderColor = UIColor.systemGreen.cgColor
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
userNameView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
userNameView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
questionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
questionView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
restartView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
restartView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
])
userData = UserData(fullname: "", answered: 0, correct: 0)
if let jData = readLocalFile(forName: "data.json") {
do {
userData = try JSONDecoder().decode(UserData.self, from: jData)
} catch {
print(error)
}
}
if userData.fullname.isEmpty {
userNameView.isHidden = false
} else {
if userData.answered < numQuestions {
questionView.isHidden = false
nameLabel.text = userData.fullname
showNextQuestion()
} else {
showResult()
}
}
}
func setupNameView() {
// add a text field and "Save" button to userNameView
nameTextField.borderStyle = .roundedRect
let saveBtn = UIButton()
saveBtn.setTitle("Save", for: [])
saveBtn.setTitleColor(.white, for: .normal)
saveBtn.setTitleColor(.lightGray, for: .highlighted)
saveBtn.backgroundColor = .systemBlue
saveBtn.layer.cornerRadius = 8
[nameTextField, saveBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
userNameView.addSubview(v)
}
let g = userNameView
NSLayoutConstraint.activate([
nameTextField.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
nameTextField.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
nameTextField.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
nameTextField.widthAnchor.constraint(equalToConstant: 280.0),
saveBtn.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 20.0),
saveBtn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
saveBtn.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
saveBtn.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
saveBtn.addTarget(self, action: #selector(saveNameTapped(_:)), for: .touchUpInside)
}
func setupQuestionView() {
// add labels + Right and Wrong buttons to questionView
nameLabel.textAlignment = .center
nameLabel.backgroundColor = .yellow
nameLabel.text = "Name"
questionLabel.textAlignment = .center
questionLabel.backgroundColor = .green
questionLabel.numberOfLines = 0
questionLabel.text = "Question"
let rightBtn = UIButton()
rightBtn.setTitle("Right", for: [])
rightBtn.setTitleColor(.white, for: .normal)
rightBtn.setTitleColor(.lightGray, for: .highlighted)
rightBtn.backgroundColor = .systemGreen
rightBtn.layer.cornerRadius = 8
let wrongBtn = UIButton()
wrongBtn.setTitle("Wrong", for: [])
wrongBtn.setTitleColor(.white, for: .normal)
wrongBtn.setTitleColor(.lightGray, for: .highlighted)
wrongBtn.backgroundColor = .systemRed
wrongBtn.layer.cornerRadius = 8
[nameLabel, questionLabel, rightBtn, wrongBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
questionView.addSubview(v)
}
let g = questionView
NSLayoutConstraint.activate([
nameLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
nameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
questionLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 20.0),
questionLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
questionLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
questionLabel.widthAnchor.constraint(equalToConstant: 280.0),
rightBtn.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: 20.0),
rightBtn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
wrongBtn.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: 20.0),
wrongBtn.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
wrongBtn.leadingAnchor.constraint(equalTo: rightBtn.trailingAnchor, constant: 20.0),
wrongBtn.widthAnchor.constraint(equalTo: rightBtn.widthAnchor),
wrongBtn.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
rightBtn.addTarget(self, action: #selector(rightAnswerTapped(_:)), for: .touchUpInside)
wrongBtn.addTarget(self, action: #selector(wrongAnswerTapped(_:)), for: .touchUpInside)
}
func setupRestartView() {
// add labels + button to restartView
resultNameLabel.textAlignment = .center
resultNameLabel.backgroundColor = .yellow
resultNameLabel.text = "name"
resultLabel.textAlignment = .center
resultLabel.backgroundColor = .green
resultLabel.numberOfLines = 0
resultLabel.text = "You answered"
let restartBtn = UIButton()
restartBtn.setTitle("Restart", for: [])
restartBtn.setTitleColor(.white, for: .normal)
restartBtn.setTitleColor(.lightGray, for: .highlighted)
restartBtn.backgroundColor = .systemBlue
restartBtn.layer.cornerRadius = 8
[resultNameLabel, resultLabel, restartBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
restartView.addSubview(v)
}
let g = restartView
NSLayoutConstraint.activate([
resultNameLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
resultNameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
resultNameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
resultLabel.topAnchor.constraint(equalTo: resultNameLabel.bottomAnchor, constant: 20.0),
resultLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
resultLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
resultLabel.widthAnchor.constraint(equalToConstant: 280.0),
restartBtn.topAnchor.constraint(equalTo: resultLabel.bottomAnchor, constant: 20.0),
restartBtn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
restartBtn.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
restartBtn.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
restartBtn.addTarget(self, action: #selector(restartTapped(_:)), for: .touchUpInside)
}
@objc func saveNameTapped(_ sender: Any?) {
guard let s = nameTextField.text,
!s.isEmpty
else { return }
view.endEditing(true)
userNameView.isHidden = true
questionView.isHidden = false
userData.fullname = s
nameLabel.text = userData.fullname
saveJSON(userData)
questionLabel.text = "Question \(userData.answered + 1) of \(numQuestions)\nAnswer Right or Wrong"
}
@objc func restartTapped(_ sender: Any?) {
// "empty" user data object
userData = UserData(fullname: "", answered: 0, correct: 0)
// save (overwrite) existing data
saveJSON(userData)
restartView.isHidden = true
nameTextField.text = ""
userNameView.isHidden = false
}
@objc func rightAnswerTapped(_ sender: Any?) {
userData.correct += 1
showNextQuestion()
}
@objc func wrongAnswerTapped(_ sender: Any?) {
showNextQuestion()
}
func showNextQuestion() {
userData.answered += 1
saveJSON(userData)
if userData.answered < numQuestions {
questionLabel.text = "Question \(userData.answered + 1) of \(numQuestions)\nAnswer Right or Wrong"
} else {
questionView.isHidden = true
showResult()
}
}
func showResult() {
restartView.isHidden = false
resultNameLabel.text = userData.fullname
resultLabel.text = "You answered \(userData.correct) Right!"
}
private func readLocalFile(forName name: String) -> Data? {
if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let pathWithFilename = documentDirectory.appendingPathComponent(name)
do {
let jd = try String(contentsOf: pathWithFilename)
return jd.data(using: .utf8)
} catch {
print(error)
}
return nil
}
return nil
}
func saveJSON(_ d: UserData) {
do {
let jsonData = try JSONEncoder().encode(d)
let jsonString = String(data: jsonData, encoding: .utf8)!
if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let pathWithFilename = documentDirectory.appendingPathComponent("data.json")
do {
try jsonString.write(to: pathWithFilename,
atomically: true,
encoding: .utf8)
} catch {
print(error)
}
}
} catch {
print(error)
}
}
}