Applying label update to cell

I have managed to get most of my app done. But I’m convinced there is a better way to code the following although my version does work, with one small error. So wondered if someone could point out the best or a better way to fix my problem. It didn’t feel overly right when I was coding it.

I have a Table with three sections.

1 => Playing
2 => Subs
3 => Unavailable

My model is just of type [[Player]]. On the viewDidLoad() I order these objects and put the first 11 in the playing section playingList[0], the remainder go into the substitute section playingList[1]. As the game plays, I update a label on the cell with how long this person has played. This is a separate piece of data. I have a dictionary playedTimePersonDictionary [String: Int] (as a side note, I toyed with making this of type [Player:Int]. I am going to store this dictionary as the player:MinutesPlayed in an NSSet.

Every second I am updating my timer and updating the cells of the table like so:

        for cells in playerTableView.visibleCells {
            let cell = cells as! MatchPlayerCell
            if let playerLabel = playedTimePersonDictionary[cell.playerName.text!]{
                for player in playingList[0]{
                    if player.name == cell.playerName.text {
                       cell.configurePlayedTimeLabel(playerLabel, hex: "playing")
                    }
                }
                for player in playingList[1]{
                    if player.name == cell.playerName.text {
                        cell.configurePlayedTimeLabel(playerLabel, hex: "substitute")
                    }
                }
                for player in playingList[2]{
                    if player.name == cell.playerName.text {
                        cell.configurePlayedTimeLabel(0, hex: "unavailable")
                    }

Each hex value relates to a different UIColor label.

In this function, I update the Dictionary of playing Time by they key (The player’s name) and the value (Seconds played).

But when I come to update their label on the cell, show how long they’ve played. It works but, in my simulator all the cells update with a label, as if they are all playing. Which they are not, I’ve echoed out the contents of the playingList to confirm.

But When I run my app on my phone, it works, except the very last row in my table shows a value, like they are playing. But they aren’t, so not sure what is going on or the best approach?

I’m wondering if you have any persistence in this app - how do you know how long someone has played once the table view has gone away?

I would probably create Player objects that have their own records of how long they have played. This could be done in a number of ways but I’m thinking of two components, one a record of time played and completed, two a record of time currently being played. So I start a player with played = 0 and playing = 0. When the player goes on they get a message that they are starting and they update playing to current time (NSDate().timeIntervalSince1970). When they get a message saying they are coming off the pitch they again take current time and add this total to played, that’s a record of playing periods completed. Whenever you query a Player’s time played they return the total of played + playing where playing is calculated by checking the current time and seeing how long the current playing period has been going. Of course either or both of these may be zero, if they are not currently playing or they have not played previously. When you update your cells you just query the Player that is assigned to that cell.

This has the advantage that you can use NSManagedObjects for Player if you want, or you could write a persist/restore pair of functions for that class so you can load them up next time you use the app. If you did use Core Data then you could use NSFetchedResultsController with various predicates to identify players in the various states, and that would automatically place them into sections for you!

Additionally you can add more state to the Players - if injured a Player reports they are unavailable. The selection of color for the cell based on a Player state is still the job of the UI though.

I can’t guess why your simulator and device show a different result though. The only times I have seen this are when a case-handling difference, or a failure to fully update the bundle has resulted in the device not correctly showing an image or other asset. Have you checked this further by fully deleting the app from the device, then doing a clean and full rebuild before installing again? The corresponding action for the simulator would be to reset it from the menu, you will see a white apple boot just like a real device when this is done and everything is erased.

Thanks @aeberbach

My app uses core data, it works more like this:

My app has teams, each team lists details about the team and also contains an NSSet of Players.
I also have fixtures, fixtures belong to a team, but it also has a list of players who’ve played in that game.

My player object has details on the player like name etc, but is also has an NSSet of games they’ve played in and what Team they belong to.

I decided to use a combination of NSUSerDefaults and variables/dictionaries to store some data about a game that is in progress in case they accidentally click back or the app enters suspense. I also did this as I thought it would be less expensive to save to a var than it would a CoreData object every second?? ( I don’t know if that’s necessarily true)

I have just ran the app on a device i’ve not tried on the simulator and it seemed ok, but I was only using a few rows and the table didn’t need to be scrolled to get to.

Regarding the actually updating of the label with regard to using playerTableView.visibleCells, is that the best method, it does seem to (understandably) have some lag as the cells are reused. I am just using this code (above in my initial Question) within a function that updates my game counter. As my game counter increase, I update the Player’s playing time like so in seconds:

            // iterate over playingList[0] and add new time to each Player object playing
        for player in playingList[0] { // playingList[0] => Players actively playing
            if let playerExists = playedTimePersonDictionary[player.name]{ // player exists in dictionary, so update their value
                playedTimePersonDictionary[player.name] = playerExists + 1
            } else { // new play not currently in dictionary, add them and 1 second
                playedTimePersonDictionary[player.name] = 1
            }
        }

       // apply for-in to visible cells => Is this the best method?

Hope that gives you a better Idea of what I’ve done to date and my thinking, I admit that I’m not sure that some of my assumptions are necessarily correct.

OK, sounds like a reasonable schema.

I feel like updating the cells each second is a really heavyweight activity no matter how you do it. Is it necessary to show the player on-field time every second? I would lean toward showing a much simpler indication of player state such as on/off/unavailable but then having the complete status for the player (in seconds) available as a detail view that can be exposed if the cell is selected. It would also make the layout of your app much less busy and uncluttered. Is it important for your app to show when players are reaching some kind of time limit, or do you want to be able to easily compare how long each player has been on? Could you use a more visual way to do it, like bars showing the relative times for each player, or use colors to indicate a short or a long period of time on the pitch.

The golden rule for table views has always been to make them scroll at 60fps and I don’t know if that would be possible with all the updating that is going to be going on. Have you measured performance?

I don’t know for sure that Core Data would be a lot slower than writing NSUserDefaults - when you optimise the only way to be sure is to measure, in this case with Instruments. But do you need to persist these values every second? Seems like these could be in-memory values until you get notification from iOS that your app is transitioning to the background, and that’s where you would do managedObjectContext.save().

Hi @aeberbach

I have been a little concerned on the amount of updating required to the view. I planned to do update every minute in the end, but for testing I was using seconds as I didn’t know if it would work and didn’t want to wait a minute to find out.

Much as I don’t have a phone capable of this, I like the idea of forcing touching a cell and it popping up with current playing time. (I’m looking for overall playing time in a particular game)

I’ve done no performance testing at all. It’s my first meaningful attempt at an app and moving from web-based languages. I’m not even sure how to properly do that, but I haven’t finished Matthijs’s book yet.

I did see that I can use, tableview.scrollView.decelerationRate = UIScrollViewDecelerationRateFast;

What I mean by 60fps is not the acceleration curve of the table view, but the ability of the phone to manage the UIKit elements and refresh the display at 60fps, whether the table view is moving fast or slow. When you do something dumb (like creating one NSDateFormatter on every cell refresh, which I once did) the table view can really slow down and it looks like a game running on a bad video card, a terrible user experience. It’s a much easier problem to avoid these days with the incredible CPU/GPU in current phones but it is still possible to get it wrong. So if you go ahead with the update of seconds in every cell, definitely get some performance numbers. Check out the Instruments Tutorial - definitely a skill worth having.

How do you mean you don’t have a phone capable? I just mean that when you tap on the row in the table view it could open a detail view for that player. Every phone can do that.

I have a phone that can do the detail view, I just liked to idea of a “peek” with 3D touch which mine can’t do :frowning:

I will look at the tutorial, I agree, definitely a great skill to have.

I’ve thought more about this. I think a middle ground approach might be:

  • Add a new entity to my Player object called currentPlayingTime and make it optional
  • Add a new entitiy to my Game object called gameComplete and make it optional

I think I’m ok to keep my dictionary and update it as the time goes by. But every minute i’ll look to update the Player Objects with their current time and then the table. I feel like this will be cleaner than trying to loop through objects and extract names to do string comparisons against dictionary keys and update cells from two different data sources and less intensive memory wise.

Does that sound ok from a swift/iOs/programming perspective?

OK, 3D Touch! I get it, mine can’t do that either.

Yes your plan of updating sounds fine, though I don’t think you need to actually save until iOS advises your app is leaving the foreground.

I’ll have a go at trying to implement this! Thanks for your help :slight_smile: