Adoption Curve Dot Net

Where Has the iOS8 Simulator Gone?!?

| Comments

With Xcode 6 and the new iOS8 Simulators, Apple thought it would be a good idea to move the location of the Simulator files. That’s not usually a problem, because there’s only a few specific circumstances when you need to access the files underlying the Simulator’s functions.

If you do need to access those files, the chances are that you can’t find them anymore.

Instead of looking in

~/Library/Application Support/iPhone Simulator/

you’ll find them instead in

~/Library/Developer/CoreSimulator/Devices/*

Which at least is easier to embed into scripts thanks to the lack of spaces in the path.

Working With Size Classes in Interface Builder

| Comments

Android used to be notorious amongst iOS developers for its practically infinite permutations of interface size. Viewed from the iOS world, this used to look like a problem, because iOS didn’t really provide much in the way of support for building interfaces of different sizes.

If you were building a universal app that supported both iPhone and iPad, there was a tendency to end up with a lot of if deviceType == kIpad-style code.

AutoLayout was the first part of fixing that problem, and the job’s been completed with iOS 8 and size classes. This is probably the least-sexy feature introduced in 8, but it’s definitely one of the more important.

Some quick background on size classes

There are currently two size classes – horizontal and vertical, and each one comes in two sizes – regular and compact. The current orientation of the device can be described as a combination of the sizes:

  • Horizontal regular, vertical regular: iPad in either orientation
  • Horizontal compact, vertical regular: iPhone portrait
  • Horizontal regular, vertical compact: no current device
  • Horizontal compact, vertical compact: iPhone landscape

Storyboards and nib files now support these size classes – so you can think of them as having up to four different layouts contained within the same file. At the bottom of the Interface Builder window, there’s now a control that allows you to switch between each combination:

Every control or AutoLayout constraint can exist in one, several or all of the size classes. This means that you can build interfaces that change depending on device type and/or orientation without any code. Controls can appear or disappear, change size, or change arrangements – all based on the layout that you create in Interface Builder.

The Many Forms of Swift Functions

| Comments

There are a somewhat bewildering variety of forms that a Swift function can take, depending on the permutations of parameters and return values that you want to use. Here’s a cheat summary:

Here’s a downloadable PDF version.

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.

This code has been updated for Swift 1.0 and Xcode 6.0.1

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
50
51
52
53
54
55
56
57
58
59
60
61
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.append("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.Default,
                                    handler: {
                                        (alert: UIAlertAction!) in println("An alert of type \(alert.style.hashValue) was tapped!")
                                    }))
        
        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.