This Old App: Another Bug, Another Day

One of the biggest hazards of indie developers is getting full of themselves. You’ll do things that you think are right or you found on StackOverflow from some other indie developer the looks like good code. Then someone comes along and tells you otherwise.

The story so far: A few months ago, Apple removed my app Interval RunCalc from the App Store for being too old and not updated. In this series of articles I’m documenting what I did on rebuilding and improving the app. In the last installment, I did some debugging. I thought I was done but one of my biggest nightmares happened.

Stage Fright or the Actors Nightmare are really fears of looking foolish, of being embarrassed in front of a huge number of people. When I teach or perform I don’t have the problem. A teacher I had a long time ago told me the biggest secret to good presentation skills: Emulate good standup comedians. They are masters of communication, of getting a story across and of connecting with an audience. He suggested watching a video of a good comedian. I made the mistake of watching Robin Williams.

This has instilled two attributes of my on-stage and in-classroom presence. One is I have a lot of energy on stage — it used to be too much. About twenty years ago, I broke a tripod of someone videoing me as I moved around too much. , I’ve calmed down in the decades since then watching far too many TED talks. The other attribute is I could never lose enough inhibition to do what Williams did on stage. I could never be embarrassed because I could never match the Williams the Master in foolishness. At least that’s what I thought until I started writing online. The fear came back like this: I’m afraid that I will write something technically wrong and a whole bunch of people will call me on it.

This series is the first time I’ve ever shared an entire app on social media. And back a few installments ago, I got called on the core bit of code in the app. Here’s the code in question:

 func currentRow(button:UIButton){

    let cell = button.superview?.superview as! IntervalTableViewCell

    guard let indexPath = tableView.indexPath(for: cell) else{

        print("not a cell")

        return

      }

    currentRow = indexPath.row

  }

It sets a property currentRow with the index of the table view cell containing the view. It solved a problem I ran into. How do you figure out the row of the cell you are in if you only press the button in a custom table view cell. By pressing the button, you don’t launch the delegate for a selected cell, because the cell doesn’t select. The key to this working is this line:

let cell = button.superview?.superview as! IntervalTableViewCell

That’s what got me in trouble. I got several comments about the code, though the most pointed was this one.

Hi Steven, nicely written, but can I have a friendly advice? You should never ever resolve objects by parsing view hierarchy (i.e. using .superview). It can change. I remember when Apple added ContentView between CellView and your Button couple of years ago. It broke the functionality of apps using this aproach. It’s aproach without variables, but dangerous. And Apple is not recommending it as well ;)

Here was my nightmare coming true. People finding my work wrong. But it wasn’t a nightmare — just the opposite. It was 100% correct. It was a bad idea to do what I did. I’m wrong, and I admit it.

I had several suggestions about alternatives as well. The basic idea was to keep the cell’s index in the button, so you know where it belongs. A simple one would be to use the tag property of the UIButton to store the row, but thinking this out, I like the idea of the full index path. There’s a few updates to this app I can foresee that will require a full index path, such as warmup and cool down intervals and its best to put that in now. In the current code, that’s one of its advantages, I can expand to sections.

This means I’ll have to add the indexPath to a button. The most obvious way is to subclass UIButton, and add the extra property like this:

class UIIndexedButton: UIButton {
    //add another property to UIButton
    var indexPath = IndexPath(row: 0, section: 0)
}

I’ll then set up the code to use this extra bit. Then I make a big mistake. I double click to open the file on a mac with Xcode 9 beta on it. Xcode 9 launches my Xcode 8 file, and my files scramble. I’m unable to access the storyboard and there were a ton of errors. It takes me a while to descramble everything, but with a few deletions of derived data I get it fixed.

I’m running out of time, and this puts me back further. I try my new button setting the buttons to UIIndexedButton, in the table view cell and try the app. It still works. Then I run into problems. I add the following method to the IntervalTableCell to update the property on all three buttons:

func setButtons(indexPath:IndexPath){
        timeEntry.indexPath = indexPath
        distanceEntry.indexPath = indexPath
        paceEntry.indexPath = indexPath
    }

I get bad access errors. This should be a simple pass of data from one place to another, but something is wrong. I try the same thing with Int instead of IndexPath and it still doesn’t work. From the table’s data source cellForRow:AtIndexPath: I know I have a indexPath to save. Just to make sure I’m not subclassing wrong I try a simple case in playgrounds, and the subclass works fine. The subclassed button cell, or the dequeued cell doesn’t want to set anything.

There’s a point where time pressure gets you, where the solution you want is not the solution you get, but you have to move on. I’m there, far too late on many other projects to stick around with this. I could leave this until the next time it breaks or I could use that tag solution. I go for the tag solution.

In IntervalTableViewCell I write a setButtons function to set all the buttons.

    func setButtons(row:Int){
      timeEntry.tag = row
      distanceEntry.tag = row
      paceEntry.tag = row
    }

Back in IntervalTableViewController, for cellForRowAtIndexpath: I add this:

cell.setButtons(row:row)

For currentRow, I change to this:

 func currentRow(button:UIButton){
   currentRow = button.tag
   /* -- commented out, potiential bug changing superviews
     let cell = button.superview?.superview as! IntervalTableViewCell
     guard let indexPath = tableView.indexPath(for: cell) else{
         print("not a cell")
         return
     }
     currentRow = indexPath.row
  */
 }
    

That works in testing. Everything is acting correctly

That solves that problem which really wasn’t a bug right now. I still don’t know why my first solution didn’t work, and if anyone knows, please let me know. I’m sure its something knuckle headed. I’d rather have a full IndexPath than just a row. If I were to add sections in the intervals for warmups and cool downs this breaks. I’m not happy about using tags as a row index either, it makes debugging in the future a bit harder. However the potential for a problem with my view hierarchy is not a problem now.

Getting input from others wasn’t the nightmare I thought it was. My bigger fear was that one mistake would kill my authority on the topic and my writing career would be over. That’s not the case. We all make mistakes, and it makes for a better person to admit to a mistake, fix it and move on. I do make mistakes and I even made a mistake on my business card trying to say this same thing:

Though that mistake, I turned to an advantage. First I’ll let you find it (or them), and maybe find a solution that it isn’t a mistake given context, though it makes another interesting statement I happen to agree with. I use that error as a conversation starter when I use this card.

On the iPhone in Landscape, the app is running perfectly. However I still have to do layout for iPhone landscape and iPad, which I’ll tackle as the last thing before distribution in the next installment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s