Adding Custom Buttons to the Navigation Bar with Storyboards

Andrew Bancroft has a great writeup showing how to add buttons to a navigation bar in storyboards. I’ve used that technique a lot over the course of my working in iOS. There have also been times where I want my own custom button inside of the navigation bar. So I thought I’d extend his original to cover the custom buttons.

Here’s what we end up with: Star button in action

How to

First, follow the basics from Andrew’s write-up. Get your scene setup, and embed it inside a navigation controller. Don’t put any buttons in your navigation bar just yet. Then:

Step 1

Drag a UIButton object from the object explorer to the navigation bar. It’s important to drag a UIButton and not a UIBarButtonItem, since that’s how we will get our custom class.

Step 2

Create a custom UIButton subclass, preferably one that conforms to IBDesignable. The IBDesignable bit will allow you to actually see the button in the navigation bar. I use PaintCode to create my icons that I use in code. Don’t overlook creating the states of the button, like pressed and inactive. Then set the custom class of the button.

You should keep the drawing of your button fairly small, and not dynamic. The button I’m showing here is 30x30. Drawing in code also means that you don’t need to worry about scaling issues. The system will take care of 1x, 2x, and 3x. That’s a big win in my book. At this point, you should be able to see what your button looks like.

Step 3

The next 2 steps don’t matter much in terms of order, but both are key to getting the appearance right. Remove the text from the button. It will look really strange if you don’t.

Step 4

Change the width of the button’s frame in the measurement inspector. Auto layout doesn’t play in the navigation bar from what I’ve seen. So doing this the manual way is the best way.

Step 5

Wire up the button’s action. The quirk here is to wire the action to the UIButton and not the UIBarButtonItem that contains the button (see the screenshot below). This is a normal IBAction that you would use other places in your code.

And that’s it! You now have a custom UIButton in your UINavigationBar. Hope this was helpful. If you want to see the whole sample project, check it out on GitHub.

Help Make This Code Better

I’m working on a chunk of code that validates whether or not an array of NSURLQueryItems contains a URL whose host is one of our email domains. The main idea is that when a link comes in via universal links, we need to attribute the click for our tracking. However, if we don’t know how to handle the link explicitly, it will get transformed into an internal URI that bounces to the mobile web (don’t fixate on this part, the main idea is that I need to search through the query items of the URI).

I have an array of our email domains, and I need to loop through both the query items of the URI, and see if the value of each contains a domain that we know about. If it does, I can abort the reporting operation.

I think my favorite thing about this code is naming the do block so that when I get a match , I can break the outer most part of the scope. That’s really great, and I don’t have to track state with a BOOL variable. I really want to avoid the number of loops that I’ve got going on if I can.

//: Playground - noun: a place where people can play

import UIKit

//The URI that we are going to be bounced over to
let uri = "zulily://action.show/web?url=http%3A%2F%2Ftrksmail4.zulily.com%2Ft%2FccgabBLXLjCAA01dBLXLjaabaaaaa%3Fo%3Dqauijqoh_5orcrs.iis%26Y%3Dqauijqoh_5orcrs.iis%26f%3DHxy%26r%3D%26x%3Dbznv%3A%2F%2Fq2q.5orcrs.iis%2FUvjEXuqtfuUj%3FY3n%40oX%3D%26sUv%40cj%3D&external=1"

//The email domains that we use
let domains = ["trksmail4.zulily.com", "trksmail5.zulily.com", "trksmail6.zulily.com", "trksmail7.zulily.com"]


//Go through the array of query items for the URI and see if any of them include an email domain in their value
//This would indicate that we don't want to hit the email server to attribute click throughs (that will be done when the user is bounced to mobile web)
emailTracker: do {
    if let comps = NSURLComponents(string: uri), items = comps.queryItems {
        for item in items where item.value != nil {
            for domain in domains {
                if item.value!.containsString(domain) {
                    print(item.value!)
                    break emailTracker
                }
            }
        }
        
        /* Perform network operation */
    }
}

I’d love to hear ways to make this better!

An iCloud Music Library Tale of Woe

16 years ago, I signed up for iTools and got my Apple ID that I use to this day. 3 years later, I started buying music from the iTunes music Store. 2 weeks ago I noticed that music I had purchased wasn't playable any more. Grrrr.

I started getting errors telling me songs that I purchased with DRM on them (i.e. Not iTunes Plus songs) were not playable because they had not "been purchased with your Apple ID". I haven't done a thorough analysis of my library, but there are likely hundreds of affected songs.

I checked some other known files and saw the same things with them too. My next destination was Apple support. I've used their chat system before to great success so I started there. 2 hours and a couple of transfers later, they wanted to schedule a call for the following morning. That call lasted 2 hours and got me bounced to 4 total reps.

Here's the "solution" we found:

  • Download files to my computer for the affected songs
  • Delete the songs from iCloud library, but keeping the files on disk
  • Import those songs into iTunes
  • Add the songs to iCloud

Again, I don't know how many songs are impacted right now. But it's a lot. And doing this means I lose play counts for up to 13 years of a given song's history. That's the biggest bummer for me in all this (and the time I will have to spend actually doing the work). So 4 hours of discussion with Apple and I just have more work ahead of me.

What I believe happened, is that the music that I had purchased on the store, and later converted to iTunes Plus, is still appearing to my Apple Music account as non-iTunes Plus. That's annoying, but fine. The real problem is that my Apple Music account is one that I share with my wife and she is the one that started the subscription. One of the troubleshooting steps we went through on the phone was for me to log out and then log in as her. The songs then played just fine.

One last thing that I checked is the backup I have of all my music, from when my iTunes Match subscription ended in October. All the files are DRM-free AAC files. I'm encouraged (that I have DRM free copies) and really bummed out (because iTunes won't serve them up to me on any of my devices by default).

So I'm not sure where exactly to go from here. I may try to hack something together where I edit my iTunes database to update play counts and hopefully get that reflected on my linked iTunes libraries. We'll see how that works out.

I don’t want to end on a negative note, so I will point out that I’m happy to have Apple Music. It’s been a great service an I’ve found new music that I really enjoy as a result of it. I bet that mine is a crazy edge case resulting from the years of my account’s activity. If you’re on the fence about a streaming service, don’t hesitate to give Apple Music’s 3 month trial a go.

Swift Protocol Extensions and the First Responder

I tried to do something clever yesterday, and it didn't work. I don't think it's too clever (hopefully, but if I am please let me know).

In Swift, I've declared a protocol and it contains a method. I then extend the protocol and give that method an default implementation. Because of how Swift works, I can call it on any object that conforms to the protocol and it will work. This is really awesome stuff.

However, if I want my declaring object to be a first responder to that method, it doesn't work. It seems to be because of how the Objective-C runtime and Swift interact. Any object that conforms to the protocol still returns NO when asked if it responds to a selector.

I've created a gist (embedded below) that shows my problem. Line 24 is where things break down.

What I want is for a view controller of mine to respond YES when it's asked about a method on my protocol and then perform the action as implemented in the extension. Is there a way to do this?

I've filed rdar://25167402 to hopefully resolve the issue. If there is a way for me to get this done, I'd love to hear it too.

Table View in a Table View

I’m working on a redesigned implementation of the order status screen for my day job, and one of the design challenges has been to list the products ordered on a given order (which itself is in a list of orders). Enter UITableView! It’s a trusted class that every iOS programmer should know intimate details about.

So I wondered if there was a sane way to put a table view within a table view cell. Such discussions have stemmed huge debates about what defines the “controller” portion of MVC. Those discussions aren’t the exact topic I want to tackle – but my position is that I have no problem passing a model to a view, and letting the view configure itself.

With that in mind, let’s see how it works together. The sample code can be found on Github. The app is really simple, visually. It’s a table view, with each cell representing a company. Inside each cell is a table view of its own, for each of the workers.

My model is really simple. I have 2 structs: Worker and Company. A company has a member called workers, which is an array of Worker objects. I have a function that generates an array of companies, and that is what powers our outer table view.

The view controller is the data source for the table view, but I did use class extensions to encapsulate those protocols into their own chunks. I’m really liking that aspect of Swift for things like that.

My company cell class has an API method called - configureWithCompany(_) on it, and I pass the company to the cell. There’s some force casting that I’m not sure how to get around at the moment, so I would love any suggestions there. Once I pass the model over to that cell, it’s all done. It can handle laying itself out.

My view controller is also the delegate for the table view. This is the part where things get kind of hairy. My table view needs to know how big the base height of the cell is, and how big each addition of a worker is. Here’s what that looks like:

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        var height: CGFloat = 45
        guard let companies = companies else { return height }
        
        let company = companies[indexPath.row]
        for _ in company.workers {
            height += WorkerTableViewCell.desiredCellHeight
        }
        
        return height
    }

I don’t like this implementation since it feels hacky, but it works. I can know how big each worker’s cell is (I put a static variable on the worker cell to get this), so I add that height for each worker to the cell’s base height. Voila!

Now for the worker cell. This is where the magic happens.

First, I lay out my cell in a XIB. I did it this way because that’s how I did it on the work app because the cell gets reused inside of this table and another. In the code, I configure the table view in - awakeFromNib(). That’s where I know that the table view outlet will be filled and I can play with subviews. Any other time and I’ve found problems. So it’s at that point that I assign the table view’s data source, and register my custom cell using - registerNib(_, forCellReuseIdentifier:).

Then comes the implementation of the table view delegate, which is boiler plate essentially. I grab the worker from the company, create my worker cell and configure it with the worker. That’s it!

Now, if my app could target iOS 9 (dropping iOS 8), then this whole thing could be replaced with a stack view. But we target 1 version back, so that was off the table. In the future this may well get ripped out and replaced with a UIStackView.

I’d love to know how this could be improved, too. If you have thoughts, get in touch!

zulily

For the past few months I’ve been contracted out to work at zulily in their mobile team. We are a group of 5 iOS, 4 Android, and 2 mobile API developers building out the mobile app experience for our customers. It’s a tall order, but a great team to work with and a fun environment to work in.

I’m happy to say that I have accepted an offer to join them full time as a senior iOS engineer, which means I will be leaving General UI, starting January 25. There’s a lot of challenges ahead, and fun features and tasks to tackle to make the app even better. We are working on a major new release that will bring some great new features to our customers.

I wasn’t at General UI for long but I’m so thankful for where they placed me, leading to starting this new chapter. I met some great folks over there that I hope to keep in touch with, and I wish them nothing but the best.

I’m excited to see what the Lord will do with this new opportunity. He is faithful – always. I hope that he will use me at zulily however he pleases. And I can’t wait to be a part of it.

Swift Array Mapping Question

I have an array that contains objects, and those objects have arrays that I want to collapse into a single array.

Basically, I want to be able to remove 2 for-in loops and replace them with a line of code (if I can while maintaining readability).

How would you write this? See https://gist.github.com/jsorge/140d42e710b5f39f59ad for the code.

Update: Not 2 minutes after a @brentsimmons retweet, Andy Matuschak gave me the answer:

I’m Hired At General UI

On Friday I accepted an offer from General UI, a local dev shop based in the Seattle’s Fremont neighborhood. Starting Monday I’m going to be their newest iOS developer, and starting on a streaming video project. To say I’m excited is an understatement.

But I want to step back for a moment. I started this software developer journey a few years ago, in early 2012. I started by taking a few online tutorials through CodeAcademy to learn some Javascript. I had the idea for Scorebook about a month later and dove into native iPhone apps. What I dove into was more than a few brick walls, but I dove in nonetheless. It was difficult; the most difficult thing I’ve ever done.

Thankfully, I’m not the hero of this story. I had the motivation and desire to press on through the hard times because the Lord has enabled me to get to this position. I wasn’t sure how I would provide for a family and he’s given me a way. I wasn’t sure how I could possibly learn everything that goes into building great apps, and he’s opened my mind to absorb what I have. He is faithful, through the hard times and the good.

Tomorrow begins a new journey. To be honest, I’m a bit nervous about it all. I have a fairly frequent battle with impostor syndrome and upon hearing some of the possible projects that I could be working on in my new venture, I started wondering if I’m up to it. The thing is, I know that I am. One thing that God has shown me over the last couple of years is that I can do this. I can’t do it without him, but he’s with me. So I can do this. I can learn, solve problems, build elegant code and good user experiences.

Something that has been running through my mind is that I want my next thing to involve working with other people. This is a great opportunity in that I’ll be on teams of 3-8 people and I can work on lots of different project. I’ll probably be working in Objective-C and Swift. iPhone and iPad. Several different clients, with several different developer. All of these are chances for me to learn from other people and it’s going to be great.

When I got the offer from the President, he closed the email with “Great things ahead…”. Bring it on. Let’s do this thing.

Formally Introducing Taphouse Software

Over the last few months I’ve been considering my future. When I made the decision to move on from Solomon Solution, my thoughts turned to what was next. When I launched Scorebook last year I did it under my company, Taphouse Software. Up to this point I was going back and forth as to what Taphouse should be. For sure it would be the place where I would release my own apps, but could it be something more?

The answer to that is a resounding yes. Today I’m really excited to begin offering two new services through Taphouse: iOS App Prototyping and iOS App Insurance.

App Prototyping is a quick project that can bear out a concept that you might have in your head. You can see how the app would function in the real world and decide how to move on from there.

App Insurance is for the App that is already in the App Store, where customers will discover bugs, updates from Apple (iOS itself or new hardware) require some minor tweaks to get things back up and running smoothly. I can take care of those things for you at a monthly rate.

I’ve been pondering these two offerings for a little while now, and I can’t be more happy to put them out there. If you think they could help you, or someone you know, I’d be more than happy to help along the way. Just get in touch!

Finding My Next Thing

My last day working at Solomon Solution was Friday. It's weird to think about, that it's been almost a week. My family and I went over to eastern Washington on a 4 day trip to a remote lake house over the weekend so I have been offline for the last while, and that's why I haven't written about my current job search.

What it boils down to is that I want to do iOS development full-time, and my previous job wasn't going to afford that any time soon. They needed my 100% commitment there, as well, and seeing as how I was doing marketing campaigns and HTML templates they just weren't going to get that from me. So I've moved on.

Thankfully I have lots of good leads on possible work. I'm having a second interview with a place tomorrow and there are some possible contract positions that could work if I don't find a full-time gig. I'm also not turning any opportunity away, so if you know of something please get in touch!

I've also got some really exciting things happening with Taphouse Software that I'm hoping to announce next week. I'm getting into the productized consulting game, that's all I will say for now. But stay tuned.

All in all, I'm not down about where I'm at right now. The prospects are exciting, and the chance to build my own company is very intriguing. I'm hopeful that I'll have a firmer grasp on where I'm going to land in the next couple of weeks. Although if you're so inclined, there are a couple of ways to chip in too. 😊