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.

Friday, April 4, 2014

NSCoding Protocol

For many applications to have real functionality,  you need a way to persist data. This simple Objective-C example shows you how to do just that by conforming to the NSCoding protocol within a user-defined data type (class). For this example, we will create and run this code as a command line tool, defining the interface and implementation in the same file.

In Xcode, select: File > New > Project.

In the OS X Templates select: Application > Command Line Tool > Next.



Enter the project options.
Let's call it: NSCodingDemo
Type your organization or company name.
Type your company identifier which should follow the reverse domain convention.


We can put our interface, implementation and driver code into the main.m file that is created with the command line tool project template.

First, add the interface and implementation code just after the import and before the 'main' method.

#import <Foundation/Foundation.h>

// 1.)
@interface Car : NSObject <NSCoding>

// 2.)
@property (nonatomic) int year;
@property (nonatomic, strong) NSString *make;
@property (nonatomic, strong) NSString *model;

@end

// 3.)
@implementation Car

- (void)encodeWithCoder:(NSCoder *)aCoder {
    // 4.)
    [aCoder encodeInt:_year forKey:@"year"];
    [aCoder encodeObject:_make forKey:@"make"];
    [aCoder encodeObject:_model forKey:@"model"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    
    self = [super init];
    if (self) {
        // 5.)
        [self setYear:[aDecoder decodeIntForKey:@"year"]];
        [self setMake:[aDecoder decodeObjectForKey:@"make"]];
        [self setModel:[aDecoder decodeObjectForKey:@"model"]];
    }
    
    return self;
}


@end

1.) The 'Car' class inherits from NSObject and conforms to the NSCoding protocol.
2.) The interface declares three properties - an int for the 'year' and NSString for the 'make' and 'model'.
3.) Since the Car class conform to the NSCoding protocol, we must implement both required methods. 'encodeWithCoder:' and 'initWithCoder:'
4.) Encode the objects for archiving.
5.) Decode the objects when unarchiving.

Next, replace the 'main' method with this code which will create three instances of the 'Car' class and demonstrate Archiving and Unarchiving the data.

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        // 6.)
        Car *car1 = [[Car alloc] init];
        Car *car2 = [[Car alloc] init];
        Car *car3 = [[Car alloc] init];
        
        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";
        
        // 7.)
        NSArray *documentDirectories =
        NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                            NSUserDomainMask,
                                            YES);
        
        // Get document directory from that list
        NSString *documentDirectory = [documentDirectories objectAtIndex:0];
        
        NSString *path = [documentDirectory   stringByAppendingPathComponent:@"archiver_demo.archive"];
        // 8.)
        NSMutableArray *allCars = [[NSMutableArray alloc] initWithObjects:car1, car2, car3, nil];
        
        // The 'archiveRootObject:toFile' returns a bool indicating
        // whether or not the operation was successful. We can use that to log a message.
        // 9.)
        if ([NSKeyedArchiver archiveRootObject:allCars toFile:path])
            NSLog(@"Success writing to file!");
        else
            NSLog(@"Unable to write to file!");
        
        // 10.)
        NSMutableArray *carsAgain = [[NSMutableArray alloc] init];
        
        // Unarchive the objects and put them in a new array
        carsAgain = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        
        // Use fast enumeration to output the new array
        for (Car *c in carsAgain)
            NSLog(@"\nHere's a %d %@ %@", c.year, c.make, c.model);
        
    }
    return 0;

}

6.) Create three instances of 'Car' and set the properties of each.
7.) Create a file path for archiving.
8.) Create an array of the three 'Car' instances. This will be the root object that gets archived.
9.) In the NSKeyedArchiver class method 'archiveRootObject:toFile:', returns a BOOL value indicating success or failure of the operation. We'll use this value to log a message to the console.
10.) Now we unarchive the data and put it into a new array to verify that the data is in tact.

If you’re running this as a command line tool, as in this example, 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. Typically, an application would create a separate datastore class and call the ‘archiveRootObject:toFile:’ and ‘unarchiveObjectWithFile:’ methods from methods within the datastore class. These datastore methods could then be called from application launching and closing methods.