Inscrutable Swift/Xcode Errors

Ugh, what a horrible day of coding. I've been playing around with a small Swift app that's using Core Data and displaying the contents in a Table View.

There are plenty of tutorials on Swift + Core Data on the web but the majority were written before Apple released the 'final' version of the language with Beta 6 so any and all code samples you see online have to be audited as you use them. Today I discovered that you need to use that same auditing mentality when you're using Apple's boilerplate code as well.

Xcode will generate a lot of boilerplate code when you chose certain project templates when you begin a new project. It so happens that if you choose the Master-Detail Application template and choose "Use Core Data" you'll get a starting point for the very app I was wanting to build.

Since the purpose of this app was to learn all of the ins and outs of Core Data access using Swift I decided to use the boilerplate code as a reference but rewrite it all myself.

The problems started while overriding the controller:didChangeObject method. Using Xcode's autocomplete I wrote the following code:

    // MARK: FetchedResultsControllerDelegate
    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
        case .Insert:
            tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        default:
            return
        }
    }

I was getting a weird error for tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade):

Could not find member 'Fade'

What?

If I replaced .Fade with nil I then got:

'(UITableView, numberOfRowsInSection: Int) -> Int' does not have a member named 'insertRowsAtIndexPaths'

WHAT DOES THAT EVEN MEAN?

This is the code in the boilerplate I was working from:

    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath) {
        switch type {
            case .Insert:
                tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
            case .Delete:
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            case .Update:
                self.configureCell(tableView.cellForRowAtIndexPath(indexPath)!, atIndexPath: indexPath)
            case .Move:
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
                tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
            default:
                return
        }
    }

Notice the difference? It took me a couple of hours of debugging to see it.

It's the method signature, in the auto generated code newIndexPath and indexPath are optionals and in the boilerplate they aren't.

If this helps out one other person I will be happy.

Bug report time.


Words by brett ohland

An Interview Technical Task

Recently I applied for a job as an iOS dev. As a part of the final interview process I was given a requirements document and told to build an app against it.

(Since I don't want this to come up if someone searches XXXX technical task, I'm not mentioning the company name)

Create a clean XCode 5 iOS app project which presents a collection of 25 bundled images of your choosing, of various sizes, with at least the 2 following layout modes:

- Mode 1: One image per row, stacked above one another.
- Mode 2: Multiple images per row, starting a new row when there's not enough room for the next image on the current row.
- Bonus layout modes:
    - Randomize the width at which each image is displayed (range 10% to 90% of screen width) maintaining aspect ratio.
    - Tap an image to select it, then tap a second image, which causes the two to trade places.
    - Or surprise us :)

Also write a couple paragraphs exploring the strengths and weaknesses of your solution's approach to presenting images from a (theoretical) 25,000 image collection. Focus on your presentation solution, not on the challenges of storing/bundling 25,000 images.

Requirements:  
    - Provide a simple interface for mode selection.
    - All image layout changes resulting from mode switching or device rotation *must* be animated. This is true for bonus layout modes as well.
    - No third party libraries.

The end result was the following project on GitHub:

IMG GRDVW

This is the paragraph I ended up writing about it:

IMG GRDVW

I decided, first off, to bring back the wild and heady days of "Web 2.0" and removed all of the vowels from the title of the app. I figured that would be a good first step.

I chose 25 of my own photographs as the data source, making sure that there were various sizes.

I implemented the following modes:

  • Mode 1: One image per row, stacked above one another.
  • Mode 2: Multiple images per row, starting a new row when there's not enough room for the next image on the current row.
  • Bonus Mode: Tap an image to select it, then tap a second image, which causes the two to trade places.
  • Bonus Mode: Cells will load an optimized image based on its current size to help performance and the app's memory footprint.
  • Added Bonus: The app/icon isn't blue.

The app starts in Mode 2 and a pinch/zoom gesture will animate you to Mode 1. Tapping cells will activated the bonus mode.

The requirements were all delivered:

  • Provide a simple interface for mode selection (gestures)
  • All image mode and device orientation changes must be animated
  • No 3rd party libraries

The task requirements made the decision to use a UICollectionView very easy to make. It's flexible, Apple loves it (read: well supported) and it gives you a lot of the animation niceties that the requirements needed.

Because it's a UICollectionView, the grid can handle an image set of 25, 50, 100, 1000 or 25,000 images with relative ease. iOS will create, remove and cache cells as needed. The only real limitation is how long you can handle scrolling a grid. That said, the app was designed to handle just 25 as per required.

Scaling up the app to 25,000 images is an interesting thought experiment. Setting aside the questions surrounding getting those images into the app we end up with some issues that would need to be addressed:

  • The app will only display between 1 - 3 columns of images. This would need to be changed in order to better support such a large data source. Adding more supported columns would help but ultimately there is a limit to how many images a resource constrained device can have in memory at one time.
  • Taking Retina/Non-retina into account, each image has 6 versions that are currently included in the App's bundle. Each one had to be generated externally. Either network requests would need to be made to acquire smaller versions of the 25,000 image dataset or an image resizing step would need to be included in the app before a cell is populated.
    • Touch areas will get smaller as the images become more dense. Implementing a system similar to the iOS7 photos app where dragging a finger shows a larger preview would indeed help.
    • Swapping the positions of two cells that are thousands apart would be difficult, the chance of mis-tapping is too great on such a large list.

The app falls short in several ways. If this were a shipping app I would add the following to the v2.0 to do list:

  • Implement a waterfall UICollectionViewFlowLayout class that stacks the images more compactly based on size. The current app simply fits the images into a square box, resulting in too much whitespace around some images.
  • Scrolling performance on the smallest image size isn't optimized as much as it could be. The externally generated images aren't as small as they could be.
  • Resize all images on a background thread to not require multiple copies to be manually included in the app bundle.
  • Add a REST API to acquire the images, including them into the app bundle is inefficient.
  • Implement a drag to reorder system (similar to the system's springboard) to change order.
  • Add iPad support.

Hot&Cold UX

I happened to watch the App Prototyping WWDC '14 session and was pleasantly surprised that their methods are similar to mine.

Essentiallay it's:

  • As cheaply as possible, make some mock ups.
  • Show people
  • Get feedback

Hot&Cold is a bit different, since I had two technologies I wanted to try out first.

I managed to find some time between unpacking boxes and full time dadding to draw out some screens and run them by my crack focus group (Wife, 1 yr old, cat). I ended up covering 4 legal sheets with screens, ended up refinind them to these:

4 screens total. Toggle feature to switch between hide and seek, taunt functionality to harass your friend, some room for some neat transitions between screens.


Wikipedia Mobile iOS Critique

I've used a couple of different iOS Wikipedia readers over the years (most notably Articles and Wikipanion), but I hadn't spent much time using the official Wikipedia Mobile app.

Doing a small bit of research, it looks like the official app was released in April of 2012 and updated last in April of 2013.

All around the current Wikipedia Mobile App is well built. It nails the core functionality of finding and reading content but ultimately looks dated and has some odd design choices.

Overview

Initially, the app itself seems to be a hybrid application. The functionality is solid but there are non-standard controls scattered throughout (the title bar menus are a dead give-away)

The launch blog post confirmed that the app was built using Cordova and PhoneGap, and 2012 was the height of when hybrid apps were considered the ultimate strategy for cross-platform development.

LinkedIn was probably the biggest supporter of this strategy back in 2012. It came as a big shock when they moved over to native applications just a year later citing issues with running out of memory in-app.

Wikipedia Mobile is in need of a native rewrite. And if job postings are any indication, they're looking to hire some iOS devs to make it happen.

The App Itself

Since "Content is King" and Wikipedia's content is among the most vast and unique in the world, the current mobile app does this job very well.

Other things done well:

  • Search is prominent (although the search box needs more left padding).
  • The content is well presented using Wikipedia's mobile look and feel.
  • Links to related articles are obvious and discoverable and back/forward functionality is obvious.

Some things that could be better:

  • The "W" button that's a part of the search box gets accidentally hit many times. It's an unintuative button.
  • I would question the inclusion of a language selector in the bottom tab bar. Do people change languages that often?
  • The bookmark icon opens up an action sheet to access the nearby, saved pages and history. The iconography is unclear.
  • The tab bars are non iOS standard. On iOS the back button is labeled either 'back' or the name of the previous view controller title. In this app, the back button is seemingly labelled with the current view title.
  • No gestures are implemented, the iOS standard 'swipe to go back' functionality isn't implemented.
  • The app is very obviously iOS 6. The design should be updated to reflect iOS 7 conventions.
  • The 'Contact Us' link is broken.

An Awesome Future

The app needs a re-write to bring it up to modern standards. With the impending release of iOS 8 the app will look more and more dated as time goes by.

The upcoming WKWebView framework in iOS 8 will (finally) allow for full speed rendering of all web content. This could help immensely for large pages.

A interesting idea for the future might be an app aimed specifically at Wikipedia Editors. An app that allows you to view edits on watched pages, includes edit functionality as well as ways of getting involved in discussions could be a great tool.


Swift Multipeer Rewrite

The amount of code that ran the Multipeer Protype was small, so I decided to just rewrite the damn thing in Swift to get a feel for the new language.

I decided for the hell of it to write something up journal style. Very stream of though-y, not sure how easy it is to read or not.

I posted the journal here

I posted the code on GitHub

I really like Swift, it's amazing just how much extranious code just falls away when you start getting the hang out it. It really reminds me much more of writing in Javascript or Ruby.

I've got to hand it to Apple, they didn't make any of my Cocoa knowledge obsolete at all. It's writing the same calls against the same frameworks tha that's amazing.


Multipeer Swift Rewrite Journal

(This is a companion post to this one)

June 9, 2014

14:20:

Created a new project in Xcode 6, created a single view application and choosing Swift as my language. Took a quick poke around, SO WEIRD to see .swift extensions and no .m/.h files.

Looked at the Storyboard and saw the new Adaptive UI stuff. Good thing I shotgunned the What's New in Cocoa Touch session last night.

14:30

Figured out the Adaptive UI stuff pretty quickly, was able to get it up and running with some Auto Layout Magic™ easily.

14:40

Testing layouts with a toddler on your lap is difficult.

14:50

Started writing my first bits of Swift. Got all of the IBOutlets and IBactions created just like I would for Objective-C.

Read the docs, got the view controller saying that it can be delegates for the various multipeer framework objects.

import UIKit  
import MultipeerConnectivity

class ViewController: UIViewController, MCNearbyServiceAdvertiserDelegate, MCSessionDelegate, MCNearbyServiceBrowserDelegate  {  
    @IBOutlet var myPeerIDLabel : UILabel
    @IBOutlet var theirPeerIDLabel : UILabel
    @IBOutlet var theirCounterLabel : UILabel
    @IBOutlet var counterButton : UIButton

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func incrementCounterAndSend(sender : UIButton) {

    }

}

I can't seem to get the autocomplete to help me out when it comes to implementing all of the required protocols for these various objects though. Still looking.

15:10

Yeah, looks like there's just weird issues with this version of Xcode 6. Once, and only once did I get the autocomplete to spit out

func advertiser(advertiser: MCNearbyServiceAdvertiser!, didReceiveInvitationFromPeer peerID: MCPeerID!, withContext context: NSData!, invitationHandler: ((Bool, MCSession!) -> Void)!) {}  

But I did manage to get the compiler to stop complaining 'ViewController' does not conform to protocol MCNearbyServiceAdvertiserDelegate.

15:20

It looks like it's something that's up with typing 'advertiser' that the autocomplete doesn't kick in. Bug report time.

15:30

Weird. Setting the class properties kept giving me an error ClassviewController' has no initializers`.

Setting them as optionals fixes it

    var session: MCSession?
    var advertiser: MCNearbyServiceAdvertiser?
    var browser: MCNearbyServiceBrowser?
    var localPeerID: MCPeerID?
    var connectedPeers = []

Ah, the docs make it clear:

Classes and structures must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created. Stored properties cannot be left in an indeterminate state.

Properties of optional type are automatically initialized with a value of nil, indicating that the property is deliberately intended to have “no value yet” during initialization.

They need to be set as optionals since they have no initial state. Gotta remember to unwrap them.

15:33

Booleans are false and true. Whole lot of muscle memory is going to keep me writing YES and NO.

15:50

Using the optionals to initialize a local instance isn't making any sense. I think I need to watch more sessions

20:30

Okay, what was I smoking a few hours ago. It's pretty simple. I had declared optionals and for some reason I was attempting to unfold the values before assigning them:

browser! = MCNearbyServiceBrowser(peer: localPeerID!, serviceType: HotColdServiceType)  

It's that one "!" that's the issue. Of course I don't want to unwrap the value, I'm trying to set the value.

The rest of this should go much more smoothly

20:45

Almost rewritten. Not really taking advantage of many of the new Swift features but that can be forgiven considering this is the first bit of actual Swift code I'm writing in earnest.

Had my first run-in with the array literal syntax. I needed to create a class level property for storing MCPeerIDs, ended up having to declare it like so:

var connectedPeers: MCPeerID[] = []

It's kind of weird declaring what the array will store when you initialize it, but I could get used to it.

21:15

Grand Central Dispatch! I was hitting my head as to how to pass a block into the dispatch_async method when I realized that I just pass in a closure.

dispatch_async(dispatch_get_main_queue(), {  
    self.theirPeerIDLabel.text = peerID.displayName
    self.counterButton.enabled = true
})

Honestly, it couldn't be any more simple. Interesting that it explicitly yelled at me to call self within the closure, I was expecting that to just error out.

21:20

So, I guess Swift native strings can't be created from NSData. Kind of odd, using native strings everywhere and then having to write some good ol' NSString. I think this is something that will confuse new programmers when they start.

let message = NSString(data: data, encoding: NSUTF8StringEncoding)  

21:25

Must be getting the hang of this, just converted a string to NSData in one line:

let data = "\(buttonCounter) times".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)  

21:30

Started debugging and immediately started getting EXC_BAD_ACCESS errors on the MCSessionDelegate methods. Ugh. Done for the night.

June 10, 2014

9:40

Decided to look at my testing strategy. I was using an instance of the app in the simulator and an instance of my old Objective-C app. Instead, this morning I changed the deployment target to iOS 7 and ran it on my two testing devices.
Lo and behold, it started working.
The last thing I did was I rewrote all of the NSLog statements as println statements. I think that was the last bit of objective-c habits I need to purge.

All done