I am a novice learning to code. I've set myself a challenge of creating an app and have been following along to books and also using ChatGPT for help creating code for things I don't currently know how to do. Managed to get my app to work fine where I create some data when it opens at populate an array, however I asked it to help me move to reading and writing data to the device as a next step. The code it helped me write it says should work, but I keep getting a crash when the app starts. Here are the two objects and the functions that handling saving data and loading the data on startup:
struct Player : Codable {
let name: String
var handicap: Int
let userID: String
private init(name: String, handicap: Int, userID: String) {
self.name = name
self.handicap = handicap
self.userID = userID
}
static func createNewPlayer(name: String, handicap: Int) -> Player {
let uuid = UUID().uuidString
let newPlayer = Player(name: name, handicap: handicap, userID: uuid)
return newPlayer
}
static func createNewPlayer(name: String, handicap: Int, userID: String) -> Player {
let newPlayer = Player(name: name, handicap: handicap, userID: userID)
return newPlayer
}
}
class CreatedPlayers {
static var shared = CreatedPlayers()
var players: [Player] = [] {
didSet {
CreatedPlayers.savePlayers()
}
}
private init(players: [Player] = []) {
self.players = players
CreatedPlayers.loadPlayers()
}
static func savePlayers() {
do {
let encodedData = try JSONEncoder().encode(shared.players)
UserDefaults.standard.set(encodedData, forKey: "createdPlayers")
} catch {
print("Error encoding players: \(error.localizedDescription)")
}
}
static func loadPlayers() {
//if let encodedData = UserDefaults.standard.data(forKey: "createdPlayers") {
//if let jsonString = String(data: encodedData, encoding: .utf8) {
//print("JSON String: \(jsonString)")
//do {
//shared.players = try JSONDecoder().decode([Player].self, from: encodedData)
//} catch let decodingError {
//print("Error decoding players: \(decodingError)")
//}
//} else {
//print("No data found")
//}
//}
if let encodedData = UserDefaults.standard.data(forKey: "createdPlayers") {
do {
let json = try JSONSerialization.jsonObject(with: encodedData, options: [])
if let playerDataArray = json as? [[String: Any]] {
// Process each player dictionary
//var parsedPlayers: [Player] = []
for playerData in playerDataArray {
if
let userID = playerData["userID"] as? String,
let name = playerData["name"] as? String,
let handicap = playerData["handicap"] as? Int {
let player = Player.createNewPlayer(name: name, handicap: handicap, userID: userID)
self.AddPlayer(player: player)
}
}
} else {
print("Error: Unable to parse player data from JSON")
}
} catch {
print("Error decoding players: \(error.localizedDescription)")
}
} else {
print("No data found for key 'createdPlayers'")
}
}
static func AddPlayer(player: Player) {
shared.players.append(player) //*THE CRASH HAPPENS HERE*
}
static func PlayerCount() -> Int {
return shared.players.count
}
static func RetrievePlayers(keyValue: String) -> Player? {
if let retrievedPlayer = shared.players.first(where: { $0.userID == keyValue}) {
return retrievedPlayer
} else {
return nil
}
}
static func RetrievePlayersWithIndex(index: Int) -> Player? {
if index >= 0 && index < shared.players.count {
return shared.players[index]
} else {
return nil
}
}
static func DeletePlayer(userID: String) {
shared.players.removeAll(where: { $0.userID == userID})
}
}
All the commented out section in loadPlayers() is because I used the built in decoding approach first but when it crashed , I sought to then decode the data manually and create a new player manually so see if that made a difference (on the basis I know all these functions work fine when triggered from within the app itself), but it still crashes and I can't even seem to capture an error message.
The crash happens when I call my own function that adds a new player to the array of players so that once the app has started, there's an array full of the same player data that was written to the device.
The error in the debugger simply says:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1013ee3f4)
Could someone please help me understand why it's happening?
I troubleshooted this myself and thought perhaps it was to do with CreatedPlayers never being initialised before it starts being asked for data (such as a player count when a tableview on the first app screen starts to load information). When I thought about the order of events in the app, the init function is never called, and it is inside here that the loadPlayers() function was sitting.
I moved the call on loadPlayers() to the viewDidLoad function in the viewcontroller handling the tableview population, deleted the call inside CreatedPlayers init function, and it works! Also changed the decode function to JSONDecoder too as per HangarRash earlier comment.
So this was nothing to do with the decoding not working; it was to do with the object I was using to store the saved player information never being initialised and therefore the array inside not being either before I then begin asking it for the data.
This is the corrected code - I would have included the viewDidLoad function initially if I had thought it had anything to do with it.
struct Player : Codable {
let name: String
var handicap: Int
let userID: String
private init(name: String, handicap: Int, userID: String) {
self.name = name
self.handicap = handicap
self.userID = userID
}
static func createNewPlayer(name: String, handicap: Int) -> Player {
let uuid = UUID().uuidString
let newPlayer = Player(name: name, handicap: handicap, userID: uuid)
return newPlayer
}
static func createNewPlayer(name: String, handicap: Int, userID: String) -> Player {
let newPlayer = Player(name: name, handicap: handicap, userID: userID)
return newPlayer
}
}
class CreatedPlayers {
static var shared = CreatedPlayers()
var players: [Player] = [] {
didSet {
CreatedPlayers.savePlayers()
}
}
private init(players: [Player] = []) {
self.players = players
//CreatedPlayers.loadPlayers()
}
static func savePlayers() {
do {
let encodedData = try JSONEncoder().encode(shared.players)
UserDefaults.standard.set(encodedData, forKey: "createdPlayers")
} catch {
print("Error encoding players: \(error.localizedDescription)")
}
}
static func loadPlayers() {
if let encodedData = UserDefaults.standard.data(forKey: "createdPlayers") {
if let jsonString = String(data: encodedData, encoding: .utf8) {
print("JSON String: \(jsonString)")
do {
shared.players = try JSONDecoder().decode([Player].self, from: encodedData)
} catch let decodingError {
print("Error decoding players: \(decodingError)")
}
} else {
print("No data found")
}
}
}
class ViewController: UIViewController, AddPlayerDelegate {
@IBOutlet weak var PlayerTableView: UITableView!
@IBOutlet weak var AddPlayerPlusBarButton: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.PlayerTableView.dataSource = self
self.PlayerTableView.isEditing = false
CreatedPlayers.loadPlayers() //Added this to ensure array is initialised and players loaded before subsequent requests for player count and player data
}
Thank you HangerRash, lorem ipsum for answering. Will look at core data now rather than UserDefaults!