Prototyping with UISliders

  • iOS SDK

Sometimes when prototyping designs, it just comes down to trial and error. Wouldn’t it be great if this can be done without recompiling?

UISlider to the rescue

I created a utility class to setup and attach a UISlider to adjust the constants of a NSLayoutConstraint.

Example 1

let constraint = topView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20))
SliderFactory.main.addSlider(named: "Top", for: constraint, minValue: 20, maxValue: 64)
Example 1
Click to play

Example 2

Here’s a slightly more complicated example.

let adjustableConstraints = [
  ("top", topView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64)),
  ("left", topView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 12)),
  ("right", topView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -12)),
  ("height", topView.heightAnchor.constraint(equalToConstant: 53.0)),
  ("between", bottomView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: 20)),
]
for (name, constraint) in adjustableConstraints {
  NSLayoutConstraint.activate([constraint])
  let min = constraint.constant - 50
  let max = constraint.constant + 50
  SliderFactory.main.addSlider(named: name, for: constraint, minValue: min, maxValue: max)
}
Example 2
Click to play

Example 3

It’s also possible to prototype other types of designs such as a parametric drawings. This is accomplished by passing in a pointer to the variable of interest and adding a callback to redraw.

Example 3
Click to play

Things to explore

These are just a few examples of what is possible with using sliders to dynamically modify variables at runtime. Some other ideas I plan on exploring:

  • Tapping a view brings up sliders specific to the constraints of that view
  • Run an iPhone app inside an iPad with side controls to adjust individual properties

I’d be curious if you have other suggestions. You can find the full source here.

External Dependencies

I make an app that provides a convenient way to check your PRESTO card balance. I started working on this as a side project over a year ago and released it early this year. There is no API available so I had to resort to scraping the data. And of course it would change at the least convenient time.

I planned to make my app free as a promotion during 🇨🇦 Thanksgiving - an impromptu decision probably influenced by a podcast I was listening earlier. The next day I began to do some simple marketing. Thankfully a colleague notified me that the PRESTO website was down before I caused too much damage.

Since the upgrade was going to take place from Friday - Monday, it would mean that my app would stop working for 4 days! It was after the holiday that I discovered that the existing app would not function due to the website changes. At this point I wished I had built some facility for my app to dynamically show a message to users.

Worse, I was in the middle of another project and could not immediately jump on to update the app. This led to some bad reviews which I expected. Most apps either have no reviews, or terrible ones when things go south. It takes a lot for a user to spend time leaving a positive note, but that’s something I’m still aiming for.

The most difficult challenge I faced was really a giant snowball. I had actually migrated to Swift 3 about a week after GM but I put it on hold due to some issues with ATS (before the website was upgraded). Another gotcha was that Swift 3 changed the way that implicitly unwrapped optionals work when converting to strings. But the real problem was testing the migration. I wanted to be real safe that the upgrade would work but I couldn’t download the App Store build because I had taken it down. I also couldn’t make a dev build of the production version because it was Swift 2.2 code, which cannot be loaded on my iOS 10 device.

In the end I did a few tests and opted to just ship it because the production version wasn’t working anyway. Luckily everything went well!

A major point of failure is depending on other services but it’s the reality of current apps. Well, at least the new website supports forward secrecy.

It Has Two Buttons for a Reason

It’s almost that time of the year again and everyone’s excited! I think it’s best to let Apple handle the hardware requests, so I made a wishlist of improvements to existing products that I would love to see.

iOS

Combine Today & Tomorrow Summary Essentially this would become an “Up Next” summary and display Weather, Events, and Alarms. It would always show the next event and alarm, as long as it is occuring within 24 hours. This eliminates the need to scroll to the bottom Tomorrow Summary at the end of the day to check events and alarms for the following day.

Highlight repeating alarms I have a bunch of repeating alarms and it would be nice to easily distinguish between repeating and single ones. It would be even better if there were some official API’s to interact with system alarms.

Tap to scroll up in App Store search tab I can’t believe this hasn’t been addressed yet.

Option to sort contacts by first name Because we know people on a first name basis.

OS X

Activity Moniter preferences I just want it to remember my custom columns and provide keyboard shortcuts to switch between segmented controls!

iOS Simulator - Reveal application folder This would be really useful when debugging files - No more printing out the directory path with the old school NSSearchPathForDirectoriesInDomains. Otherwise, good luck picking the out the correct randomly generated folder of your app :P.

iOS Simulator - Record App Previews Who has every version of the iPhone (except Rene Ritchie:) ?

Better multi-monitor smarts This annoying bug occurs when the dock is on the bottom of the primary monitor. Then, after going to the screen saver, and logging back in, the Cmd+Tab Switcher gets displayed on the secondary monitor. This is because the Switcher view is displayed on the monitor that the dock last appeared on. (Yes my dock is set to auto-hide.)

Watch

Snooze or stop alarm using buttons The current implementation is very weird and annoying: It’s possible to stop the alarm by pressing the Digital Crown, but only if the screen is on. So if you want to stop it without bringing up your wrist, you actually need to press it twice, but with enough of a delay because otherwise it ignores the second press. It would be nice to accept a single press on the Crown to stop, or the Button to snooze.

Scrollable bursts of notifications Ever received Slack messages faster than you can read them on your Watch? Yep. Instead of dismissing to see the new notification, it should just slide in from the bottom. No more “2 new notificaitons”!

There’s sure to be tons of new great features. Only a few more days to find out.

Batch-Updating UICollectionView

  • iOS SDK

Recently I needed to update the contents of a UICollectionView. Simple right? Just call reloadData and be done with it.

However this results in poor UX because users don’t get the visual feedback of how items got reordered. Of course the solution is to animate insert/delete/move items in the performBatchUpdates block, but how can we get from the old list of items to the new one?

Sample Application

Click to play

Suppose we have an array of integers that we are currently displaying in the collection view. When a user taps on a button, we want to display a different list of integers, potentially including numbers that were present in the original list.

Luckily all of the animation is builtin; the only thing we need to do is tell the collection how to change. The order to accomplish this is to delete, then move, and finally insert.

The code

Here is the high level of what we are going to do. Each step is encapsulated inside its own update block to make it more clear, as well as to ensure our data source is in sync with the internal collection view.

func setCollectionViewItems(newItems: [Int]) {
    // self.items = [0, 1, 2, 3, 4, 5, 6, 7, 8]

    // Delete old items
    collectionView.performBatchUpdates({ ... })

    // Move existing items to correct order
    collectionView.performBatchUpdates({ ... })

    // Insert new items
    collectionView.performBatchUpdates({ ... })
}

Step 1: Delete

One thing to note is that UICollectionView only allows a single delete at any given index path inside a performBatchUpdates block. For example, if we want to delete the first two elemnts, we can’t call delete at index path 0 twice like we could an array.

This means that we cannot simply iterate and delete as we go along, because deleting an element shifts every element after by one. Thus, the indices would be incorrect (bad) and we might call delete twice with the same index (worse!). To get around this, we can simply reverse the iteration order so that we start with the last element, as this does not change future indices that are iterated.

// Delete old items
collectionView.performBatchUpdates({
    // Perform this in reverse to maintain correct indices
    for (i, item) in self.items.enumerate().reverse() {
        guard newItems.indexOf(item) == nil else { continue }
        self.items.removeAtIndex(i)
        self.collectionView.deleteItemsAtIndexPaths([ NSIndexPath(forItem: i, inSection: 0) ])
    }
}, completion: nil)

Step 2: Move

The goal of the second step is to move the existing items into the correct order in the new list. (This can be further optimized by creating a dictionary containing the mapping from item to index instead of performing a linear search every time.)

// Move existing items to correct order
collectionView.performBatchUpdates({
    var index = 0
    for item in newItems {
        guard let fromIndex = self.items.indexOf(item) else { continue }
        self.collectionView.moveItemAtIndexPath(
            NSIndexPath(forItem: fromIndex, inSection: 0), toIndexPath:
            NSIndexPath(forItem: index, inSection: 0))
        index += 1
    }
}, completion: nil)

Step 3: Insert

Finally we compute and create index paths that correspond to the new items, assign the newItems to our instance variable, and give the collection view the array of new indices to insert. (Again, a slight optimization is to compute the new index paths inside the guard statement of the second step.)

// Insert new items
collectionView.performBatchUpdates({
    let newIndexPaths = newItems.enumerate().filter({ (index, item) -> Bool in
        return !self.items.contains(item)
    }).map({ (index, item) -> NSIndexPath in
        return NSIndexPath(forItem: index, inSection: 0)
    })

    self.items = newItems
    self.collectionView.insertItemsAtIndexPaths(newIndexPaths)
}, completion: nil)

Summary

Hopefully this shows that it doesn’t take a lot of additional work to create a much better UX with animation. A demo project is available here.

NSNorth 2016

  • NSNorth

Over the past weekend I had a blast at the NSNorth conference in Toronto. There was over 170 attendees from all sorts of places. This was my second year attending and it was great to catch up with old friends and make new ones. The community is very friendly and welcoming to everyone. I really enjoyed the opportunity to talk to fellow developers in the industry and I came back inspired to work on creating amazing products.

NSNorth 2016 logo
We have liftoff

The talks that stood out to me, in chronological order, were:

  • Tom Creighton - “Frenemies: Dev + Design”
    This talk provides another helpful reminder that developing products is a collaborative and iterative process.

  • Ayaka Nonaka - “Clean View Layout with iOS 9 Features”
    I enjoyed following the guided examples of solving specific layout problems using NSLayoutAnchor and UIStackView.

  • Jon Edwards - “Carving out a Space in the Continuum of App Store Success”
    The animations in this presentation were stunning and the segues between topics flowed like story.

  • Michael Gorbach - “The Value of Platform Tourism”
    Sometimes, developers should just drop their bias against other platforms and give them a shot.

  • Robleh Jama - “Charting”
    I was always curious about how other people market their apps. There were several good points such as creating a promo video and providing press kits.

Keynote Karaoke

Now the most interesting part of the conference was the Keynote Karaoke where one would give a lightning talk. The catch, however, was that each presenter did not know the topic ahead of time and needed to improvise on the spot. Sitting as a member of the audience, I could only imagine how difficult and nerve-racking it is to talk about an unknown topic. As one of the karaoke topic goes, “Embrace the Ackward”.

I want to give a huge shoutout to the organizers, Adrienne, Dan, and Phil, as well as the volunteers for making the event possible. It’s hard to imagine how this conference could be even better, but maybe we will find out how next year.