Adoption Curve Dot Net

A Minimum Viable tableView in Swift

| Comments

This GitHub repo is a minimum viable implementation of a UITableView in Swift. Here’s a swift (badum, tish) tutorial on creating a UITableView using the new language.

The project consists consists of a single storyboard with a table view control, and a view controller written in Swift:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    let cellIdentifier = "cellIdentifier"
    var tableData = String[]()
    
    @IBOutlet var tableView: UITableView
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Register the UITableViewCell class with the tableView
        self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: self.cellIdentifier)
        
        // Setup table data
        for index in 0...100 {
            self.tableData += "Item \(index)"
        }
    }

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

    // UITableViewDataSource methods
    
    func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
        return 1
    }
    
    func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        return tableData.count
    }
    
    func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        var cell = tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier) as UITableViewCell
        cell.textLabel.text = self.tableData[indexPath.row]
        return cell
    }

    // UITableViewDelegate methods
    
    func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
        let alert = UIAlertController(title: "Item selected", message: "You selected item \(indexPath.row)", preferredStyle: UIAlertControllerStyle.Alert)
        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil))
        self.presentViewController(alert, animated: true, completion: nil)
    }
  
}

Creating a Draggable UICollectionViewCell

| Comments

So here’s the situation – you’re creating an interactive UICollectionView, and you want to be able to drag a cell around the screen with a touch. To provide user feedback, you want the contents of the cell to follow the user’s finger as it moves around.

The problem is that unless you’re using a completely custom collection view layout, you can’t move the cell itself. The collection view is in charge of where things are displayed, and it’s a major pain to override this – especially if you’re using a flow layout. Reimplementing UICollectionViewFlowLayout from scratch is a decidedly non-trivial undertaking.

The answer lies in a hack. Create a copy of the contents of the cell as an image, then drag this around the screen underneath your finger. Much easier.

Here’s an example – it assumes that you’ve previously created and attached a UIPanGestureRecognizer to the collection view, and tied this to a method called handlePan: in your view controller. There’s also a UIImageView property on the view controller called movingCell.

A draggable UICollectionViewCelllink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(void)handlePan:(UIPanGestureRecognizer *)panRecognizer {

    CGPoint locationPoint = [panRecognizer locationInView:self.collectionView];

    if (panRecognizer.state == UIGestureRecognizerStateBegan) {

        NSIndexPath indexPathOfMovingCell = [self.collectionView indexPathForItemAtPoint:locationPoint];
        UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPathOfMovingCell];

        UIGraphicsBeginImageContext(cell.bounds.size);
        [cell.layer renderInContext:UIGraphicsGetCurrentContext()];
        UIImage *cellImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        self.movingCell = [[UIImageView alloc] initWithImage:cellImage];
        [self.movingCell setCenter:locationPoint];
        [self.movingCell setAlpha:0.75f];
        [self.collectionView addSubview:self.movingCell];

    }

    if (panRecognizer.state == UIGestureRecognizerStateChanged) {
        [self.movingCell setCenter:locationPoint];
    }

    if (panRecognizer.state == UIGestureRecognizerStateEnded) {
        [self.movingCell removeFromSuperview];
    }
}

When the pan gesture recognizer fires, it calls the handlePan: method with itself as a parameter.

A UIPanGestureRecognizer has three states that we’re interested in – UIGestureRecognizerStateBegan (which is fired as the first touch starts), UIGestureRecognizerStateChanged(which fires as the touch moves) and UIGestureRecognizerStateEnded (which fires as the finger is lifted).

We hook into the UIGestureRecognizerStateBegan event, and get the location where the pan gesture is occurring:

CGPoint locationPoint = [panRecognizer locationInView:self.collectionView];

Then if the touches have just begun, we grab the cell in which the touch started:

NSIndexPath *indexPathOfMovingCell = [self.collectionView indexPathForItemAtPoint:locationPoint];

UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPathOfMovingCell];

and create a UIImage out of the cell’s layer:

UIGraphicsBeginImageContext(cell.bounds.size);

[cell.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage *cellImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

Finally, we use this UIImage to populate a UIImageView property, and update the center of the UIImageView so that it lies underneath the current location of the touch. I’ve also tweaked the image’s opacity to make it slightly translucent:

self.movingCell = [[UIImageView alloc] initWithImage:cellImage];

[self.movingCell setCenter:locationPoint];

[self.movingCell setAlpha:0.75f];

[self.collectionView addSubview:self.movingCell];

Following the touches is just a case of updating the centre of the UIImageView:

if (panRecognizer.state == UIGestureRecognizerStateChanged) {

    [self.movingCell setCenter:locationPoint];

}

And when the touches end, we remove the UIImageView from the collectionView completely:

if (panRecognizer.state == UIGestureRecognizerStateEnded) {

    [self.movingCell removeFromSuperview];

}

This implementation simply removes the pseudo-cell from the screen when the touch finishes, but there’s no reason why you can’t do something like insert it back into the collection view at the point where it was ‘dropped’. I’ll put the code for this up in another post.

Mocking UICollectionViewLayouts

| Comments

At the heart of custom UICollectionViewLayouts are lots of calculations, and creating/debugging these by hand can be painful. It’s easier in the long run to write tests to help with this – but setting up the stack of objects to make the tests run can be a bit involved.

Here’s how I’m doing it – using XCTest and OCMock, although there’s no reason why this approach won’t work with other test/mock frameworks like Kiwi etc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)testCalculateSpokeRadiusReturnsCorrectValueForTwoItems {

    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 500, 500) collectionViewLayout:self.customLayout];

    id collectionViewMock = OCMPartialMock(collectionView);
    [[[collectionViewMock stub] andReturnValue:@(1)] numberOfItemsInSection:0];
  
    [collectionViewMock setCollectionViewLayout:self.customLayout];

    [self.customLayout setItemSize:CGSizeMake(100, 100)];
    [self.customLayout setSidePadding:10.0f];

    XCTAssertEqual([self.customLayout calculateSpokeRadius], 190.0f, @"should be 190.0f");

}

The process isn’t too gnarly. First, create a real, live UICollectionView instance and give it your custom layout (in this test, I’d previously instantiated the custom layout object in the test setup):

UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 500, 500) collectionViewLayout:self.customLayout];

Then create a partial mock:

id collectionViewMock = OCMPartialMock(collectionView);

With this you, you can then stub out the numberOfItemsInSection: method and return the number of items you want to run the calculations for – by mocking out this method, you’ve got no dependencies on your datasources.

The advantage of using a partial mock is that you only need to stub out the methods that you want to control – you can use everything else as you would with the real, live object.

[[[collectionViewMock stub] andReturnValue:@(1)] numberOfItemsInSection:0];

Now link the custom layout and the collection view together:

[collectionViewMock setCollectionViewLayout:self.customLayout];

A few custom layout settings (these will obviously depend on how you’ve implemented your layout):

[self.customLayout setItemSize:CGSizeMake(100, 100)];`
[self.customLayout setSidePadding:10.0f];`

Finally, after all this, you can actually fire the test:

XCTAssertEqual([self.customLayout calculateSpokeRadius], 190.0f, @"should be 190.0f");

Here, I’ve created a helper method inside the custom layout to calculate the radius from the centre of the collection view for various sizes of layout. That’s often an easier approach to take – calculating layout attributes like item centre often involves some fiddly maths, so by breaking it up into chunks of helper methods you can test each bit piece-by-piece.

This tends to be easier in the long run than doing everything in one fell swoop, because you can spend a long time down the rabbit hole of figuring out where the layout is going wrong. With this test, I can throw various sizes of collection view at the layout, and check that things will still work out OK.

Time for a Change

A quick update, as it’s easier to put it up here than condense down into 140 characters for a tweet. As of this week, I am no longer with Centralway – I remain under NDA, so that’s all the details I can share.

This means I’m available for hire. I do several things:

  • application architecture: figuring out how the user interactions, front-end, back-end and data pieces of a service need to work in order to play nicely together.

  • project management: running Agile-style projects with mixed technical and non-technical teams.

  • team management: building and running development teams, with all the challenges that are involved in a group of different skills and personalities.

  • iOS development: hands-on code-cutting of apps.

All of these I’ve done in a variety of organisation shapes, sizes and cultures – so I’ve got to be quite good at the spinning plates and herding cats that tend to be involved in getting a service up and running in today’s online world. Ideally, I’m looking for something that draws on all four of those areas.

Location-wise, I’m interested in pretty-much anywhere in mainland Europe. Somewhere English/German-speaking would be a bonus.

Any leads will be gratefully received.

Why Swift Isn’t Going to Change the App Industry Just Yet

| Comments

One of the common reactions to Monday’s announcement of Apple’s new Swift language was that it’s lowered the bar for iOS development. We can look now forward to the dawning of a halycon age where apps have never been easier and cheaper to create.

The other way of looking at this idea is that as an established iOS developer, your industry might be about to get invaded by millions of newbies who haven’t had to earn their stripes learning the intricacies of ObjectiveC. Your rates are about to go down, and the age of demand exceeding supply is over.

Both those are simplistic, and missing the bigger point. The bar to entry to this industry hasn’t moved at all – if anything, it’s been raised.

And that’s because becoming a competent developer who can earn a professional living writing code isn’t about mastering one area, it’s about mastering FOUR.

It doesn’t matter if you’re writing mobile apps in a language that was announced 24 hours ago; or banking systems in a language that was designed by people who have since died of a ripe old age. If you don’t understand these four areas, you’ll suck as a developer.

You need to know the language, the paradigms, the frameworks and the environment of the platform that you’re building for.

Knowledge of the language

In order to build any kind of software, you need a working knowledge of the language that you’re using. To learn a language can be the work of a few hours; to master it can be the work of a lifetime.

Irrespective of where you lie along the continuum, there’s a certain minimum amount of expertise that you will need to get by. You need a working knowledge of grammar in order to make yourself understood in speech, and coding is no different – understanding the syntax of a language is a prerequisite to being able to work with it.

Knowledge of the paradigms

When you think hard about what programming actually is, it gets philosophical in a way that you might not expect. You’re in the business of taking tangible, real-world problems and reconstructing them in an intangible mental domain.

We talk about objects as if they’re concrete physical entities – but building an app is actually a process of making a whole series of imaginary constructs, and then getting them to interact with each other.

To do that, you need a working knowledge of the underlying paradigms. This is taking the physical form of the language and making use of it. In order to use objects to break down a problem, you need to understand what they are.

Knowing only the details of a specific language isn’t going to much use here – without the ability to comprehend the patterns of mental abstractions that you are expressing using the code of the language that you’ve chosen.

Knowledge of the frameworks

Knowing what an object is, and how you create one with Swift or Objective-C or whatever language you’re working with, still isn’t enough. Next, you need a knowledge of the frameworks.

While it might be possible to build every aspect of an iOS app from scratch, that’s not a practical way to work. So instead we rely on the frameworks that Apple provides. Table views are a standard control – but they behave in a particular way, and you need an understanding of how they fit together and operate in order to exploit them.

There are so many frameworks involved in iOS that it’s probably not a practical proposition to even attempt to understand all of them.

But some are unavoidable – you’re not going to get far without knowing even the basics of UIKit, for example.

To understand this takes not just a knowledge of the language that they’re built in, but also the underlying paradigms that they’re exploiting. Knowing how to set a table view’s delegate is one thing, but you also need to know what a delegate actually is.

Knowledge of the environment

Assuming a working ability with the language, the paradigms and the frameworks, you’re now in a position to start building things. But only knowing those three is missing one vital part – and that’s the understanding of the environment in which you’re operating.

The shorthand way of referring to this is user experience – why your interface is laid out the way it is, and the workflows that your app provides in order to solve the particular problem it is dealing with.

But this is a complex mix of physical contraints and psychology. Force your users to rely on voice input in a noisy environment – or large amounts of text input on a tiny keyboard – and they’ll struggle to interact with your app. Forcing them to think about how they need to interact, and they’re likely to give up and find an easier alternative.

Where does this leave Swift?

Swift doesn’t change the fundementals of what it takes to be a competent developer AT ALL. It’s possible that it might make picking up the basics slightly easier, although I’m sceptical. Go beyond the first few pages of the Swift guide, and this doesn’t feel like a toy language designed specifically for ease of learning.

What Swift doesn’t do is lower the bar to understanding the paradigms, the frameworks, or the environment. In fact I’d argue it actually raises the bar, at least for a while. It will probably be several years before you can fully get to grips with iOS without any knowledge of ObjectiveC, so for the interim you’re going to need to learn the details of two languages if you’re just starting out.

It might make building some chunks of functionality quicker, and you might find the syntax more expressive or the structure clearer to read.

But without knowing the abstract concepts of programming, and the details of Cocoa Touch, and the constraints of tiny screens in a world of continuous partial attention, you’re not going to succeed in this industry. So I’m not going to worry about being priced out of my chosen field just yet.