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.