Post

Replies

Boosts

Views

Activity

Reply to Where to put my controller logic?
hi bobandsee,i'll second your uneasiness in trying to remove the C from MVC ⚠but on the point of your question, i'd suggest the series of tutorials from Paul Hudson (hackingwithswift.com) with title "Cupcake Corner SwiftUI Tutorial." there are 8 parts, all of which are available on YouTube, and i think you'll see many similarities to what i think you're trying to do. hope that helps,DMG
Jan ’20
Reply to How to make an action in one view controller do the same thing on a different view controller if the content in the first view controller row is found in one of the rows in the second view controller?
hi,first, let's thank Claude31 for his contributions to this thread the last several days.i have stayed away until now, only because the app design has changed since last week -- see the thread How do I save and load an array into a table view using singleton database option? -- there are now more lists, more view controllers (some we never knew about) ... and there's a lot of view reloading and updating issues which i think are based on the app using modal segues (and in iOS 13, these got more interesting to handle, since viewDidLoad() may not be getting called when modals are dismissed, depending on how they are presented).i'd like to suggest to japio that we simplify the app design by not inventing more lists for the Database to manage, but instead by placing markers in each player's record as to whether they are top200, a rookie, and so forth. saving multiple lists to disk is not difficult, but it is confusing and maintenance-heavy: look how much code has been duplicated in the Database to handle different lists.also, with multiple lists, player data is being duplicated on disk in multiple locations, and one must be sure that the data never gets out of sync ... and this creates a lot of code in the app of the form "edit a player in a list, but be sure to edit the player in all the other lists in which he appears."with that, i've posted a newer version of what i think could make a small, relevant project. it's only a suggestion, but i hope you'll see some of the simplifications i am proposing. i've tried to add many comments to explain my choices, one of which is to not keep the lists of interest in order (that feature can be added, but it was never clear to me that it was required). all segues (in fact, you'll see that there's only one segue) are pushes, so i have avoided the refresh/update issues of dismissing modal segues.the project is located as before, at: bitbucket.org/delawaremathguy/japiohope that helps,DMG
Jan ’20
Reply to Swift Bug?
hi,i'm not sure why you think there's a bug here, but i'm not even sure what you're trying to accomplish in this loop. with the lines uncommented, it is doing what it is written to do.i've added a where qualifier on the inner loop to start simplifying your code, so try this first.func solve(arr: [Int]) -> Int { for i in arr.indices { for j in arr.indices where i < j { print("i is \(i) j is \(j)") // let (x,y) = (arr[i] , arr[j]) // guard (x * y) <= arr[i...j].max()! else { // print("array arr[i...j] is \(arr[i...j])") // print("breaking") // break // } } } return 0 } let a: [Int] = [1, 1, 2, 4, 2] let x = solve(arr: a) // x will be zeroif you uncomment the commented lines, there will be a point where you break out of the inner loop, since 2*4 = 8, and this is bigger than the largest element in the array (4). the index combination of 2 and 4 with then be skipped.hope that helps,DMG
Jan ’20
Reply to How to reorder an array of objects?
hi,very nice, guys, and so i'll be adding this nice one-liner extension to Array for one of my projects:extension Array { func subArrayOrdered(byIndexes indexes: [Int]) -> [Element] { return indexes.filter({ $0 >= 0 && $0 < self.count }).map({ self[$0] }) } }possible usage:let x = ["A","B","C","D","E","F"] let indexes = [0, 3, 2, 5, -1, 17] print(x.subArrayOrdered(byIndexes: indexes))the output is["A", "D", "C", "F"]this is a little more general solution than the original question requested -- it picks out any sub array of the original array, ordered by specific indexes (while ignoring improper indices, and it even allows you to repeat an index, which can be either a bug or a feature). if the index set is a permutation of the index range of the array, then you do indeed get a specific rearrangement of the original array.regards,DMG
Jan ’20
Reply to How do I save and load an array into a table view using singleton database option?
hi Claude31,just a quick comment on enjoying your help on this app with japio. we've suggested many tools for japio to use, but japio is still having trouble putting them all together.i've posted a working project as a solution, hope you'll take a look. and to your point above, changeMade() should work fine without a parameter, since the Database owns the playerData (everyone else just gets references, so changes made elsewhere are changes to the real data already owned in the Database).regards,DMG
Jan ’20
Reply to How do I save and load an array into a table view using singleton database option?
hi,the problem now is that there are too many themes in your code, some from your initial efforts, some from Claude31, and several from me, that just don't knit too well together.rather than giving you a long reply here in this forum (i really do hate code boxes and i prefer that posts not get too long), i've mocked up an app that contains all the elements you need, it manages everything that needs to be persisted in a Database singleton (essentailly a global variable), it unifies and simplifies some of the code that's been patched together these last few weeks, and it does exactly what you want (as best i can tell). you can find the project at: bitbucket.org/delawaremathguy/japiohope that helps,DMG
Jan ’20
Reply to How do I save a global array to a table view so that after I close the app, when I reopen it, the data isn't lost?
hi,in the previous message, i forgot that you occasionally delete a player from one of your lists. with the model i have suggested, you can do this for a given player by adding this method to Database to remove a player from the roster:func removeFromRosterList(player: PlayerData) { let currentRosterPosition = player.rosterPosition player.rosterPosition = -1 // denotes no longer on the rosterList // get all players on roster at a higher index let higherNumberedRosterPlayers = allPlayers.filter({ $0.rosterPosition > currentRosterPosition}) // their rosterPositions have now gone down by one for otherPlayer in higherNumberedRosterPlayers { otherPlayer.rosterPosition -= 1 // decrease position by 1 } }you'd add a similar method to remove a player from your draft list.hope that helps,DMG
Dec ’19
Reply to How do I save a global array to a table view so that after I close the app, when I reopen it, the data isn't lost?
hi,just getting caught up on this ... took a few days off ...japio: it's clear that when you closed out the last thread, you were satisfied with the Database singleton and that you wanted to persist the list of players. it's no surprise now to find out that things don't work because you never asked to persist myRoster and draftOrder, nor did the Database ever handle these.Claude31's suggestions should work fine, and if you're satisfied with them, that's cool with me. and whether you persist data to a file in the Documents directory or as data in UserDefaults, it probably does not matter for the size of this app.however, with all due respect to my friend Claude31, i think you really want to move to a file-based model managed by the Database singleton, and eventually begin to use a UIDocument model to allow auto-saving.so i would suggest all of what appears below. i'm sorry for the length, but the specificity really is necessary, and i'd encourage you to read through all of it and, if it makes sense, then consider using it in the future.(1) let the Database singleton manage all data centrally, rather than rely on hooks in the Application Delegate and adding portions of the data management throughout your code to save other lists.(2) instead of keeping three separate lists, one of type PlayerData and two of type String, simplify by making all lists of type PlayerData (and let whatever view controllers access those lists decide how they want to display them, using the name, the team, and the position).(3) keep all the internals of how the lists are managed within the Database singleton; and to accomplish this, i'd propose extending the definition of PlayerData to include two Int values, one perhaps named "rosterPosition" to indicate the position at which a player appears in the myRoster list, and one perhaps named "draftPosition" to indicate the position at which a player appears in the draftOrder list. use -1 as a value to indicate that a player does not appear in a list.in short, there will be only one list of players ⚠ all the draft and roster information will be incorporated into the PlayerData records directly in this single list.(4) expand the Database singleton to have methods to provide myRoster and draftOrder as needed in your program withfunc rosterList() -> [PlayerData] { let rosteredPlayers = allPlayers.filter({ $0.rosterPosition >= 0 }) return rosteredPlayers.sorted(by: { $0.rosterPosition < $1.rosterPosition }) } func draftList() -> [PlayerData] { let draftedPlayers = allPlayers.filter({ $0.draftPosition >= 0 }) return draftedPlayers.sorted(by: { $0.draftPosition < $1.draftPosition }) }(5) expand the Database singleton to allow moving a player onto one of these lists (which, from your code, always seems to be an "appending"). this requires a quick computation to figure out where in the list to place an addition:func addToRosterList(player: PlayerData) { let maxRosteredIndex = allPlayers.map({ $0.rosterPosition }).max()! player.rosterPosition = maxRosteredIndex + 1 } func addToDraftList(player: PlayerData) { let maxDraftedIndex = allPlayers.map({ $0.draftPosition }).max()! player.draftPosition = maxDraftedIndex + 1 }(6) your addPlayer() function then simplifies:func addPlayer(index: IndexPath) { // remove this line: _ = top30QuarterbacksTable.cellForRow(at: index) let player = objectsArray[index.row] // remove this line: let name: String = objectsArray[index.row].name // remove this line: let team: String = objectsArray[index.row].team // remove this line: let position: String = objectsArray[index.row].position player.strikeThrough = true player.accessory = true player.color = true Database.shared.addToRosterList(player: player) // remove this line: if myRoster == [] { // remove this line: myRoster?.insert("\(name), \(team) - \(position)", at: 0) // remove this line: } else { // remove this line: myRoster?.append("\(name), \(team) - \(position)") // remove this line: } Database.shared.addToDraftList(player: player) // remove this line: if draftOrder == [] { // remove this line: draftOrder?.insert("\(name), \(team) - \(position)", at: 0) // remove this line: } else { // remove this line: draftOrder?.append("\(name), \(team) - \(position)") // remove this line: } top30QuarterbacksTable.reloadData() Database.shared.changeMade() }that does it in principle, except that you'll need to understand the implications:(a) (EDIT: you'll need to update the PlayerData class definition)class PlayerData: Codable { var num: Int = 0 var name: String = "" var team: String = "" var position: String = "" var strikeThrough: Bool = false var color: Bool = false var accessory: Bool = false var rosterPosition: Int = -1 // not on the roster var draftPosition: Int = -1 // not drafted init(num: Int, name: String, team: String, position: String) { self.num = num self.name = name self.team = team self.position = position } }(b) in any view controller where you display the roster and draft order, you'll start withoverride func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated: animated) myRoster = Database.shared.rosterList() draftOrder = Database.shared.draftList() }(c) in such a viewController, you would need to build out whatever string you want to appear in cellForRow(at:) separately, because myRoster and draftOrder will now be lists of PlayerData and not lists of String. for example, it's no longerlet str = myRoster[index]but insteadlet player = myRoster[index] let str = "\(player.name), \(player.team) - \(player.position)"(d) finally, if you make all these changes and adopt this program, you should expect to run into errors when you run your app, because we have changed the definition of PlayerData. it's now incompatible with what is stored on disk and/or in UserDefaults. simply delete the app from the simulator or iOS device, then run again from XCode with a fresh copy of the app and no data yet persisted. you'll be back at the beginning.that's a lot to digest. i hope it makes some sense to you, and it will greatly simplify a lot of your code.hope that helps,DMG
Dec ’19
Reply to How to save the state of a table view?
hi,as Claude31 said ... you're not being explicit about "what errors," and "where" they occur.but i can see an obvious problem with your code -- and please let me apologize for not being even more explicit. i should not have assumed that you would follow through on the logical implications of proposing a name change.since i asked that you rename the class "Data" to "PlayerData," so as to not conflict with Foundation's Data class, i assumed that you would then rewrite all of lines 73 - 154 to use "PlayerData" and not "Data." you didn't do that, and i think that's probably generating the errors you're seeing.hope that helps,DMG
Dec ’19
Reply to How to save the state of a table view?
hi,great! we've finally now gotten to what i think was your original question of "how to save the state ..." as i said above, "... if you really want to persist this data across application launches ... that's another journey."what follows below is enough to get things working (albeit maybe not the best way to do this)step 1: when the Database is first used, populate the allPlayers list from disk, or default to your hard-coded data if we have not yet written anything to disk.first, change the name of your Data class to, perhaps, PlayerData -- you need to do this because we must use the Foundation class named Data -- and make it conform to the Codable protocol by writingclass PlayerData: Codable { // change name and add Codablenext, add the following functions to the Database class. you'll store the data in the Documents directory with the name playerData.jsonfileprivate func playerDataURL() -> URL { let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).last! return documentDirectoryURL.appendingPathComponent("playerData.json") } fileprivate func readFromDisk() -> [PlayerData]? { let fileURL = playerDataURL() guard FileManager.default.fileExists(atPath: fileURL.path) else { return nil } do { let fileContents = try Data(contentsOf: fileURL) let list = try JSONDecoder().decode([PlayerData].self, from: fileContents) return list } catch let error as NSError { NSLog("Error reading file: \(error.localizedDescription)") } return nil }also change the init() method of Database slightly to beinit() { allPlayers = [PlayerData]() if let playerList = readFromDisk() { allPlayers = playerList } else { allPlayers = [ // then all the data ] } // don't forget to add a closing curly brace here for the else } // closes the init() methodstep #2: add the following method in Database to be able to write data to disk.fileprivate func writeToDisk() { do { let data = try JSONEncoder().encode(allPlayers) try data.write(to: playerDataURL()) } catch let error as NSError { NSLog("Error reading file: \(error.localizedDescription)") } }step #3: you need to detect when changes have been made to the player data. one possible solution: explicitly tell the database that changes have been made whenever you make a change so that it will save to disk. so add the following method to the Database class:func changeMade() { writeToDisk() }step #4: wherever in your code you make a change to any individual player, be sure to add a call to changeMade(). example: in addPlayer(), removePlayer(), and markAsTaken(), close the functions with bothtop30QuarterbacksTable.reloadData() // makes the visual changes on screen Database.shared.changeMade() // triggers the data change on diski'm pretty sure i have all the code above correct on reading and writing (???) hope that helps,DMG
Dec ’19
Reply to How to save the state of a table view?
hi,from the extended back-and-forth below, and your previous thread in which both Claude31 and i were trying to move you forward, we now know more about what you are trying to do. i'd suggest that ALL the Data records for ALL the players you want to work with should probably be held globally, in a central database.i'd suggest that you want a singleton Database class that manages all the data of all the players. any ViewController that needs to present and/or modify this data can access it here.step #1: make sure the Data struct is now a CLASS, with a convenient init() method that let's you abbreviate its use, since all three boolean values are false upon creationsclass Data { var num: Int = 0 var name: String = "" var team: String = "" var position: String = "" var strikeThrough: Bool = false var color: Bool = false var accessory: Bool = false init(num: Int, name: String, team: String, position: String) { self.num = num self.name = name self.team = team self.position = position } }step #2: create a singleton, global Database class withclass Database { // this is a singleton class, exposing its shared instance static let shared = Database() fileprivate var allPlayers: [Data] func playerList(position: String) -> [Data] { return allPlayers.filter({ $0.position == position }) } init() { allPlayers = [ // edited after posting to correct an error Data(num: 1, name: "Patrick Mahomes", team: "KC", position: "QB"), Data(num: 2, name: "Deshaun Watson", team: "HOU", position: "QB"), Data(num: 3, name: "Aaron Rodgers", team: "GB", position: "QB"), Data(num: 4, name: "Matt Ryan", team: "ATL", position: "QB"), // add all the other QB data you have and then just keep on going // with all the RB data you have Data(num: 1, name: "Saquon Barkley", team: "NYG", position: "RB"), Data(num: 2, name: "Christian McCaffrey", team: "CAR", position: "RB"), Data(num: 3, name: "Alvin Kamara", team: "NO", position: "RB"), Data(num: 4, name: "Ezekiel Elliott", team: "DAL", position: "RB"), // and finish out all the RB data Data(num: 80, name: "Andre Ellington", team: "TB", position: "RB") ] } }step #3: for your second-level VCs that need data either for "QB" or "RB" players, include the following to initialize its objectsArray: [Data] in viewWillAppear()override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) objectsArray = Database.shared.playerList(position: "QB") // edited after original post // and whatever else you need to do; and also note that you may want to // sort the objectsArray perhaps by their "num" value at this point }step #4: i'm not sure there is a step 4, because any change you now make directly to a Data object will be made in the Database, because Data objects are classes and accessed by reference.that solves your immediate problem of having the list of players in a second-level VC being reset each time it opens: the data is kept globally, and you pull an updated list of players everytime such a VC comes on screen. and, of course, you may now want to access the Database in your main-level VC ...still more to do, though, if you allow adding and deleting rows in your VC (you do, right?), and if you really want to persist this data across application launches. that's another journey ⚠ that will require more discussion about what you are trying to accomplish in your app.hope that helps,DMG
Dec ’19