Wednesday, July 30, 2014

iOS Snap To Grid For Tap Gestures

I was recently working on an app that required a uniform presentation of objects on the screen as a result of tap gestures. In other words I needed to make sure that approximate, tapped locations became exact points on the screen that the app could keep track of. I thought this might be worth sharing. This tutorial will demonstrate how to create a snap-to grid in an iOS app using a custom UIView and tap gestures.

Here are the app requirements for this tutorial:
When the user opens the app, the user will see a yellow screen with red gridlines. These gridlines will show the intersections or resolution points for the snap-to behavior. When the user taps the screen. A light gray dot will be added to the screen at the nearest intersection to which the tap was made. The user can delete all of the dots with a right swipe gesture.

When learning, it’s best to use as little template help as possible so that you understand the building blocks of an iOS application. For that reason, I will choose to start with an empty application. If your current version of Xcode is missing the ‘Empty Application’ template, please see my earlier post, ‘Nothing From Something’. Let’s get started.

Select File > New > Project > Empty Application



Fill in the project options. If you are using Xcode > 5, select Objective-C as the language. I’ll post a Swift version later.



Make sure that Foundation, UIKit and CoreGraphics frameworks have been added to the project under Build Phases > Link Binary With Libraries.

Highlight the AppDelegate.m file in the project navigator. We will create a new Objective-C class file right below this.

Select File > New > File

In the template drop-down, under the iOS section, highlight Cocoa Touch and click on Objective-C class. Click Next.




If you are using Xcode > 5, under the iOS section, highlight Source and click on Cocoa Touch Class. Click Next.

Let’s name the class ‘Dot’ and make it a subclass of NSObject. This should leave the ‘create XIB file’ checkbox and platform selection unavailable, which is good, because we won’t be using XIB’s. Again, if you’re using Xcode > 5, select Objective-C for the language. Click ‘Create’ on the next screen to save the file.

For now, the ‘Dot’ class will have one property of type CGPoint which we will name ‘location’. CGPoint is a struct declared in the CoreGraphics framework that contains two members: x and y, which are of type CGFloat and define a point in a two-dimensional coordinate system. When the user taps the UI screen, the coordinates of it’s ‘locationInView’ will be adjusted to the closest grid resolution points, an instance of Dot will be created and it’s location will be set to the adjusted values. That dot will then be added to an NSMutableArray and drawn to the screen. Sounds simple enough

Your Dot.h file should look like this:

#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>

@interface Dot : NSObject

@property (nonatomic) CGPoint location;

@end

Now, we need to create our custom UIView class. This class will be responsible for receiving touch events, adding the dots to a collection and drawing those dots to the screen. Use the same steps we used above in creating the ‘Dot’ class however, let’s name this class ’SnapGridView’. Make sure it only inherits from NSObject so there is no starting code in the implementation file. After it is created, change the superclass to UIView. Replace the code in  SnapGridView.h with the following:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface SnapGridView : UIView {
    
    int horizontalSpacing;
    int verticalSpacing;
    NSMutableArray *allDots;
}

@end

We have a couple instance variables that we’ll use to store the spacing for the snap-to grid which is going to be dependent on the UIScreen size and an NSMutableArray for storing the Dot objects.

Now, replace the code in SnapGridView.m with the following:

#import "SnapGridView.h"
#import "Dot.h"

@implementation SnapGridView

static const int kNUM_X_POINTS_PLUS_1 = 5;
static const int kNUM_Y_POINTS_PLUS_1 = 7;

- (id)initWithFrame:(CGRect)r
{
    self = [super initWithFrame:r];
    
    if (self) {
        
        [self setBackgroundColor:[UIColor yellowColor]];
        allDots = [[NSMutableArray alloc] init];
        
        // This is the recognizer to add a dot with a single tap
        UITapGestureRecognizer *tapRecognizer =
        [[UITapGestureRecognizer alloc] initWithTarget:self
                                                action:@selector(tap:)];
        [tapRecognizer setNumberOfTapsRequired:1];
        [self addGestureRecognizer:tapRecognizer];
        
        // This is the recognizer to delete all of the notes with
        // a swipe to the right
        UISwipeGestureRecognizer *swipeRecognizer =
        [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                  action:@selector(swipe:)];
        [self addGestureRecognizer:swipeRecognizer];
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    
    // Calculate horizontal and vertical spacing which is UI screen size dependent
    horizontalSpacing = self.window.bounds.size.width / kNUM_X_POINTS_PLUS_1;
    verticalSpacing = self.window.bounds.size.height / kNUM_Y_POINTS_PLUS_1;
    
    // Draw grid
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 1.0);
    CGContextSetLineCap(context, kCGLineCapSquare);
    [[UIColor redColor] set];
    
    // Draw horizontal grid lines
    for (int y = 0; y < self.window.bounds.size.height; y += verticalSpacing) {
            
        CGContextMoveToPoint(context, 0, y);
        CGContextAddLineToPoint(context, self.window.bounds.size.width, y);
        CGContextStrokePath(context);
    }
    
    // Draw vertical grid lines
    for (int x = 0; x < self.window.bounds.size.width; x += horizontalSpacing) {
        
        CGContextMoveToPoint(context, x, 0);
        CGContextAddLineToPoint(context, x, self.window.bounds.size.height);
        CGContextStrokePath(context);
    }
    
    // Draw Dots
    CGContextRef drawDot = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(drawDot, 25.0);
    CGContextSetLineCap(drawDot, kCGLineCapRound);
    [[UIColor lightGrayColor] set];
    for (Dot *dot in allDots) {
        CGContextMoveToPoint(drawDot, dot.location.x, dot.location.y);
        CGContextAddLineToPoint(drawDot, dot.location.x, dot.location.y);
        CGContextStrokePath(drawDot);
    }
}

- (void)tap:(UIGestureRecognizer *)gr
{
    // Get the initial tap location
    CGPoint loc = [gr locationInView:self];
    
    // Adjust the x value
    if (loc.x < horizontalSpacing) {
        // Adjusted x value should be a minimum of the horizontalSpacing,
        // or the leftmost usuable x position.
        loc.x = horizontalSpacing;
    } else {
        if (loc.x > self.window.bounds.size.width - horizontalSpacing) {
            // Adjusted x value should be (self.window.bounds.size.width - horizontalSpacing),
            // or rightmost usuable x position.
            loc.x = self.window.bounds.size.width - horizontalSpacing;
        } else {
            if (((int)loc.x % (int)horizontalSpacing) < (horizontalSpacing / 2)) {
                // need to subtract the (loc.x % horizontalSpacing)
                loc.x = loc.x - ((int)loc.x % (int)horizontalSpacing);
            } else {
                if (((int)loc.x % (int)horizontalSpacing) >= (horizontalSpacing / 2)) {
                    loc.x = loc.x + (horizontalSpacing - ((int)loc.x % (int)horizontalSpacing));
                }
            }
        }
    }
    
    // Adjust the y value
    if (loc.y < verticalSpacing) {
        // Adjusted y value should be a minimum of verticalSpacing,
        // or the topmost usuable y position.
        loc.y = verticalSpacing;
    } else {
        if (loc.y > (self.window.bounds.size.height - verticalSpacing)) {
            // Adjusted y value should be (self.window.bounds.size.height - verticalSpacing),
            // or the bottommost usuable y position.
            loc.y = (self.window.bounds.size.height - verticalSpacing);
        } else {
            if (((int)loc.y % (int)verticalSpacing) < (verticalSpacing / 2)) {
                // need to subtract the (loc.y % verticalSpacing)
                loc.y = loc.y - ((int)loc.y % (int)verticalSpacing);
            } else {
                if (((int)loc.y % (int)verticalSpacing) >= (verticalSpacing / 2)) {
                    loc.y = loc.y + (verticalSpacing - ((int)loc.y % (int)verticalSpacing));
                }
            }
        }
    }
    
    // Write the final adjusted location value to allDots array
    Dot *dot = [[Dot alloc] init];
    dot.location = loc;
    [allDots addObject:dot];
    [self setNeedsDisplay];
}

- (void)swipe:(UIGestureRecognizer *)gr
{
    [allDots removeAllObjects];
    
    [self setNeedsDisplay];
}

@end

In the ‘initWithFrame’ method, the ‘allDots’ array is initialized, the background color is set and the UITapGestureRecognizer as well as the UISwipeGestureRecognizer are initialized. The swipe recognizer will be for convenience to clear the dots off of the screen without having to close the app. We are going to override the drawRect method of UIView, which in it’s default implementation does nothing. In drawRect, we will set the values of our horizontal and vertical spacing variables by dividing the UIWindow. We can change the number of snap-to grid points by changing the value of constants ‘kNUM_X_POINTS_PLUS_1’ and ‘kNUM_Y_POINTS_PLUS_1’. The ‘PLUS_1’ is a reference to the fact that we need to divide the UIWindow by one more than the number of available grid points in the window. So, if you want four points across, kNUM_X_POINTS_PLUS_1 should equal 5.

Before drawRect is called in a view, a ‘current context’ is created. We will grab a pointer to that context, set the line width and color and use a couple ‘for’ loops to draw the gridlines. We then draw the array of dots. When the tap method is called by the UITapGestureRecognizer, we first get the locationInView then proceed to adjust that position to resolve to one of our snap-to points. Basically, for each x and y coordinate, we first check to see if it’s outside of the outermost points. If it is, we resolve that coordinate to the nearest outermost coordinate. If the coordinate is already within those boundaries, we use the modulus (%) operator to return the difference of the coordinate value divided by the spacing value we obtained using the  kNUM_X_POINTS_PLUS_1 and kNUM_Y_POINTS_PLUS_1 constants. If that difference is less than half of the spacing’s value, it will be adjusted to the lesser grid value by subtracting the difference from the current coordinate value. If the difference is more than half the spacing’s value we adjust it in the other direction. For convenience, a swipe right gesture will erase all of the dots.

In the AppDelegate.m file, add the code, in the ‘applicationDidFinishLaunchingWithOptions’ method, to create an instance of our custom ’SnapGridView’. Don’t forget to import the SnapGridView.h file at the top of the AppDelegate.m file. Also, ignore the warning that application windows are expected to have a root view controller. In a real app this custom view would be set as the view of a view controller. The AppDelegate file should now look like this:

#import "AppDelegate.h"
#import "SnapGridView.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    
    CGRect viewFrame = CGRectMake(0, 0, self.window.bounds.size.width, self.window.bounds.size.height);
    SnapGridView *snapGridView = [[SnapGridView alloc] initWithFrame:viewFrame];
    [[self window] addSubview:snapGridView];
    
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

For brevity, I’ve only shown the top of the AppDelegate.m file down through the ‘applicationDidFinishLaunchingWithOptions’ method body.

Challenge:
There is a slight bug in this implementation that will cause some improperly placed points on the outermost gridlines when certain screen division values are used. (kNUM_X_POINTS_PLUS_1 and kNUM_Y_POINTS_PLUS_1). See if you can change/add the code to fix this problem.


I hope this example is helpful.

Sunday, July 27, 2014

Nothing From Something - Missing 'Empty Application' Template

Is your latest version of Xcode missing the ‘Empty Application Template’? Are you feeling a little insecure about putting all of your eggs in the ‘Storyboard’ basket? (nudge, nudge, wink wink)

Starting with an empty app is a great way to learn iOS programming. With just the AppDelegate and a couple frameworks, you can create the view controller classes programmatically and really gain an understanding of what happens ‘behind the scenes’. It remains to be seen whether Apple will permanently force the issue of Storyboards, however, If you find yourself wanting to start your project without using Storyboard to create your view controllers, you have a couple easy options.

Option 1:

- Start with a single view application. Select File > New > Project > Single View Application.

- Fill in the project options. For this example, select Objective-C for the language.

- In the project navigator, delete the ViewController.h, ViewController.m and Main.storyboard files that were created by the template. Select the ‘Move To Trash’ option when prompted.

- Select the Info.plist file inside the Supporting Files folder. Near the bottom is a key named ‘Main storyboard file base name’. Hover the cursor to the right of the key name. When the ‘plus / minus’ signs pop up, click the minus sign to remove the reference to the storyboard.

- In the project navigator, highlight the project name at the top and select Build Phases > Link Binary With Libraries. Add the Foundation, UIKit and CoreGraphics frameworks to the project.

Now, in the AppDelegate.m file, replace the method body of ‘didFinishLaunchingWithOptions’  with the following code:


self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;


You now have the equivalent of an Empty Application Template. After the ‘Override point’ comment, you can instantiate you’re root view controller class.

Option 2: Copy and paste the empty application template file from Xcode5.

- Control-click the Xcode.app icon in the Applications folder and select ‘Show Package Contents’.

- Navigate to the following folder: Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/Application

- Copy the folder titled ‘Empty Application.xctemplate’.

- Navigate to the following folder: (In the > Xcode5 app): Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project Templates/iOS/Application

- Paste the copy of the template in this location.

The ‘Empty Application Template’ will now be available when you select File > New > Project > …


I hope this is helpful. I saw several posts where users were wondering what had happened to the ‘Empty Application’ template. Look forward to a future post on creating custom templates.

Monday, June 16, 2014

Swift - JSON Demo

Here’s an example using Swift to retrieve and parse JSON data in an iOS app. There are three Swift files. As with other posts, I like to show examples with view controllers created programmatically. I first create a Cocoa class in the Swift Language that only inherits from NSObject. Then change the superclass to UITableViewController for the ListViewController and UIViewController for the WebViewController. This creates the files without creating all of the boiler-plate code. Again, this is a technique seen in The Big Nerd Ranch books and is a way of helping one understand what is really required to instantiate a view controller and the different methods/functions that can be overridden to define the needed custom behavior for the view controller. Also, in a real app, we should create separate connection and handling classes for url requests to better separate the class responsibilities. I will demonstrate that in a later post.

AppDelegate - For brevity, I only show the ‘didFinishLaunchingWithOptions’ function from the AppDelegate.swift file. I have only added the one line below the ‘Override Point’ comment. This creates an instance of ‘ListViewController’ and sets it as the ‘rootViewController’.

//  AppDelegate.swift
//  SwiftJSONDemo
//  Created by Tom Limbaugh on 6/15/14.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
                            
    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
        self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
        // Override point for customization after application launch.
        self.window!.rootViewController = ListViewController(style: .Plain)
        self.window!.backgroundColor = UIColor.whiteColor()
        self.window!.makeKeyAndVisible()
        return true
    }
}

ListViewController  - At the top of the ‘ListViewController’ class, I create three properties. One for holding the parsed JSON data, one for the url titles and one for the url links associated with those titles. I call the fetchData() function from the override of viewDidLoad(). For this example, we’ll create the url variable using the The White House - Policy Snapshots JSON feed. Next, create an NSURLSession and start a ‘dataTaskWithURL’. Remember that an NSURLSessionTask is created in the ‘paused’ state. Hence, the ‘resume()’ call after the completion block definition, which is one of four calls that control an NSURLSessionTask state. Inside the completion block, we can handle the data associated with the response, converting it to JSON and populating arrays with url titles and links. We’ll use the titles array to populate our tableView and the links array to set a property in the WebViewController which will actually display the web content.

//
//  ListViewController.swift
//  SwiftJSONDemo
//  Created by Tom Limbaugh on 6/5/14.
//

import UIKit

class ListViewController: UITableViewController {
    
    var jsonData = NSArray()
    var titles = NSArray()
    var links = NSArray()
    
    override func viewDidLoad() {
        
        self.fetchData()
    }
    
    func fetchData() {
        
        var url = NSURL.URLWithString("http://www.whitehouse.gov/facts/json/progress/education")
        var session = NSURLSession.sharedSession()
        session.dataTaskWithURL(url, completionHandler: {(data, response, error) in
            
            // parse data into json
            self.jsonData = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSArray
            
            // grab the titles for populating the tableview
            self.titles = self.jsonData.valueForKey("url_title") as NSArray!
            self.links = self.jsonData.valueForKey("url") as NSArray!
            
            dispatch_async(dispatch_get_main_queue()) {
                // reload the tableview
                self.tableView.reloadData()
            }
            
        }).resume()
    }
    
    override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        
        return titles.count
    }
    
    override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        
        var cell: UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("UITableViewCell") as? UITableViewCell
        
        if !cell {
            cell = UITableViewCell(style: .Default, reuseIdentifier: "UITableViewCell")
        }
        
        cell!.textLabel.text = titles.objectAtIndex(indexPath.row) as NSString
        
        return cell as UITableViewCell
    }
    
    override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
        
        // Here we can present the view controller to display the contents of the
        // link associated with the url_title
        var wvc = WebViewController()
        wvc.link = links.objectAtIndex(indexPath.row) as NSString
        presentViewController(wvc, animated: true, completion: nil)
        
    }
}

WebViewController - The link property for this controller was set in the ‘ListViewController’ function ‘didSelectRowAtIndexPath’. Since we don’t want the user to have to close the app every time they want to view a new policy snapshot, try adding some code to allow the user to dismiss the WebViewController.

//
//  WebViewController.swift
//  SwiftJSONDemo
//  Created by Tom Limbaugh on 6/15/14.
//

import UIKit

class WebViewController: UIViewController {
    
    var wv = UIWebView()
    var link = NSString()
    
    
    override func loadView() {
        
        // Create an instance of UIWebView as large as the screen
        wv = UIWebView(frame: UIScreen.mainScreen().applicationFrame)
        // Tell web view to scale web content to fit within bounds of webview
        // webView.scalesPageToFit(true)
        
        self.view = wv
        
    }
    
    override func viewDidLoad() {
        var url = NSURL.URLWithString(link)
        var req = NSURLRequest(URL: url)
        
        dispatch_async(dispatch_get_main_queue()) {
            
            self.wv.loadRequest(req)
        }
    }
    
    func webView() -> UIWebView {
        
        return self.view as UIWebView
    }

}

Thursday, June 12, 2014

Swift - Simple TableView Programmatically

Here's a simple example in Swift, of programmatically (no .xib files) creating a UITableViewController that presents a UIViewController with a "Dismiss" button. This is simple code but I've been trying to get comfortable using Swift to do some common tasks. Along the way, I'm discovering some interesting and sometimes quirky things about Swift.

For brevity, I'm only showing the '...didFinishLaunching...' function in the AppDelegate.swift file. I set the 'rootViewController' to my 'ListViewController' and we're off and running.

I use a minimal amount of code to instantiate the UITableViewController, starting with a .swift file that only inherits from NSObject then changing the superclass to UITableViewController once the file is created. I learned this from the Big Nerd Ranch books as a way to understand what goes on under the hood. In a real app, the 'didSelectRowAtIndexPath' would have some sort of selection for different view controllers. In this example, you'll get the one and only 'DetailViewController'.

The 'DetailViewController' is also created with a minimal amount of code and changing the superclass to UIViewController once the file is created. If you are building for arm-64, you don't have to explicitly type 'btnWidth' and 'btnHeight' to CGFloat However, the compiler will complain if you try to build this for 32-bit architecture without explicitly typing these constants as CGFloat. I think this is the preferred way to deal with type mismatches in Swift. I've been finding some discussions about this online and would be interested in hearing others' feedback on this.

Enjoy!


//
//  AppDelegate.swift
//  SwiftDemo
//
//  Created by Tom Limbaugh on 6/3/14.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
                            
    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
        self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
        // Override point for customization after application launch.
        self.window!.rootViewController = ListViewController(style: .Plain)
        self.window!.backgroundColor = UIColor.whiteColor()
        self.window!.makeKeyAndVisible()
        return true
    }

}



//
//  ListViewController.swift
//  SwiftDemo
//
//  Created by Tom Limbaugh on 6/3/14.
//

import UIKit

class ListViewController: UITableViewController {
    
    override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        return 3
    }
    
    override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        
        var cell: UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("UITableViewCell") as? UITableViewCell
        
        if !cell {
            cell = UITableViewCell(style: .Default, reuseIdentifier: "UITableViewCell")
        }
        
        switch indexPath.row {
            
        case 0:
            cell!.textLabel.text = "Item 1"
        case 1:
            cell!.textLabel.text = "Item 2"
        case 2:
            cell!.textLabel.text = "Item 3"
        default:
            cell!.textLabel.text = ""
        }
        return cell as UITableViewCell
    }
    
    override func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
        
        var dvc = DetailViewController(nibName: nil, bundle: nil)
        
        presentViewController(dvc, animated: true, completion: nil)
    }
   
}




//
//  DetailViewController.swift
//  SwiftDemo
//
//  Created by Tom Limbaugh on 6/3/14.
//


import UIKit

class DetailViewController: UIViewController {
    
    init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
        super.init(nibName: nibName, bundle: nibBundle)
        
        self.view.backgroundColor = UIColor.redColor()
        
        let viewFrame: CGRect = UIScreen.mainScreen().bounds
        let btnWidth:CGFloat = 100.0
        let btnHeight:CGFloat = 50.0
        
        let dismissButton:UIButton = UIButton(frame: CGRectMake((viewFrame.width / 2) - (btnWidth / 2), (viewFrame.height / 2) - (btnHeight / 2), btnWidth, btnHeight))
        
        dismissButton.backgroundColor = UIColor.yellowColor()
        dismissButton.setTitle("Dismiss", forState: .Normal)
        dismissButton.setTitleColor(UIColor.blackColor(), forState: .Normal)
        dismissButton.addTarget(self, action: "tappedButton:", forControlEvents: UIControlEvents.TouchUpInside)
        self.view.addSubview(dismissButton)
    }
    
    func tappedButton(sender:UIButton!) {
        self.dismissViewControllerAnimated(true, completion: nil)
    }
}


Saturday, June 7, 2014

NSCoding Protocol - Swift Version

So, Swift is here and I'm working out examples of using Swift with the original frameworks. Here is a version of my NSCoding Protocol post using Swift. I split the Car class and main into two files: Car.swift and main.swift.

Car.swift:

//
//  Car.swift
//  SwiftArchiverDemo
//
//  Created by Tom Limbaugh on 6/7/14.
//

import Foundation

protocol NSCoding {
    
}

class Car:NSObject {
    
    var year: Int = 0
    var make: String = ""
    var model: String = ""
    
    init() {
        super.init()
    }
    
    func encodeWithCoder(aCoder: NSCoder!) {
        aCoder.encodeInteger(year, forKey:"year")
        aCoder.encodeObject(model, forKey:"make")
        aCoder.encodeObject(make, forKey:"model")
    }
    
    init(coder aDecoder: NSCoder!) {
        
        super.init()
        
        year = aDecoder.decodeIntegerForKey("year")
        make = aDecoder.decodeObjectForKey("make") as String
        model = aDecoder.decodeObjectForKey("model") as String
        
    }
}


main.swift creates three instances of the Car class for demonstrating the archiving and unarchiving of the objects.

//
//  main.swift
//  SwiftArchiverDemo
//
//  Created by Tom Limbaugh on 6/7/14.
//

import Foundation

var documentDirectories:NSArray
var documentDirectory:String
var path:String
var unarchivedCars:NSArray
var allCars:NSArray

// Create a filepath for archiving.
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)

// Get document directory from that list
documentDirectory = documentDirectories.objectAtIndex(0) as String

// append with the .archive file name
path = documentDirectory.stringByAppendingPathComponent("swift_archiver_demo.archive")

var car1:Car! = Car()
var car2:Car! = Car()
var car3:Car! = Car()

car1.year = 1957
car1.make = "Chevrolet"
car1.model = "Bel Air"

car2.year = 1964
car2.make = "Dodge"
car2.model = "Polara"

car3.year = 1972
car3.make = "Plymouth"
car3.model = "Fury"

allCars = [car1, car2, car3]

// The 'archiveRootObject:toFile' returns a bool indicating
    // whether or not the operation was successful. We can use that to log a message.
if NSKeyedArchiver.archiveRootObject(allCars, toFile: path) {
    println("Success writing to file!")
} else {
    println("Unable to write to file!")
}
    
// Now lets unarchive the data and put it into a different array to verify
// that this all works. Unarchive the objects and put them in a new array
unarchivedCars = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as NSArray
    
// Output the new array
for car : AnyObject in unarchivedCars {
    println("Here's a \(car.year) \(car.make) \(car.model)")

}

As with the original Objective-C post, if you’re running this as a command line tool, your *.archive file should be found in your '/Users/username/Documents directory'. If you open it with TextEdit.app, you should see a cryptic representation of the string data from the original objects.