Swift 3.1 URLSession, URLSessionDataTask Sample Code

In this tutorial we will create a simple application that demonstrates the use of URLSession and URLSessionDataTask in Swift 3.1

Note: As this article focusses primarily on the above stated aspects we will leave some UI functionality like showing a progress indicator when the web service is being called out of the scope of this post.

We will have a cocoa application that has two view based NSTableView. In one of the table view we will show a list of food menu items and once the user clicks one of the food menu item we will trigger a webservice call to get the list of sub menu items/ dishes for the selected food category.

Earlier while taking a course in angularjs at Coursera, I had created two webservices and will be using the same here.

https://knowstack-angularjs.herokuapp.com/categories.json gives a list of available menu items

https://knowstack-angularjs.herokuapp.com/menu_items.json?category=L gives the list of sub menu items/ dishes for a selected category.

Our final application will look as below

Swift 3.1 URLSession, URLSessionDataTask Sample Code

Swift 3.1 URLSession, URLSessionDataTask Sample Code

Lets get started with the code
Step 1: Create two classes that represent the response object for the two table views

class MenuItem{
    var itemId:String?
    var shortName:String?
    var longName:String?
    var specialInstructions:String?
    var url:String?
}

class SubMenuItem{
    var itemId: String?
    var shortName:String?
    var longName:String?
    var itemDescription:String?
    var priceSmall:String?
    var priceLarge:String?
    var smallPortionName:String?
    var largePortionName:String?
    
}

Step 2: Create two NSTableCellView Subclasses for using in the ViewBased table views

class MenuItemTableCellView: NSTableCellView {
    @IBOutlet weak var itemIdTextField: NSTextField!
    @IBOutlet weak var shortNameTextField: NSTextField!
    @IBOutlet weak var longNameTextField: NSTextField!
    @IBOutlet weak var specialInstructionsTextField: NSTextField!
    @IBOutlet weak var urlTextField: NSTextField!
}
class SubMenuItemTableCellView: NSTableCellView {
    @IBOutlet weak var itemIdTextField: NSTextField!
    @IBOutlet weak var shortNameTextField: NSTextField!
    @IBOutlet weak var longNameTextField: NSTextField!
    @IBOutlet weak var itemDescriptionTextField: NSTextField!
    @IBOutlet weak var priceSmallTextField: NSTextField!
    @IBOutlet weak var priceLargeTextField: NSTextField!
    @IBOutlet weak var smallPortionNameTextField: NSTextField!
    @IBOutlet weak var largePortionNameTextField: NSTextField!
}

Now lets have a look at the view controller which hosts the two table views and first web service is called when the view is loaded.

//  ViewController.swift
//  Swift-URLSession
//  Created by Debasis Das on 5/24/17.
//  Copyright © 2017 Knowstack. All rights reserved.

import Cocoa

class ViewController: NSViewController {
    let menuItemURLString = "https://knowstack-angularjs.herokuapp.com/categories.json"
    let subMenuItemURLString = "https://knowstack-angularjs.herokuapp.com/menu_items.json?category="
    
    @IBOutlet weak var menuItemTableView:NSTableView!
    @IBOutlet weak var subMenuItemTableView:NSTableView!
    
    var menuItems = [MenuItem]()
    var subMenuItems = [SubMenuItem]()
    
    let defaultSession = URLSession(configuration: URLSessionConfiguration.default)
    var dataTask:URLSessionDataTask?
    var subMenuDataTask:URLSessionDataTask?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        menuItemTableView.delegate = self
        subMenuItemTableView.delegate = self
        menuItemTableView.dataSource = self
        subMenuItemTableView.dataSource = self
        loadMenuItems()
    }
    
    func loadMenuItems(){
        menuItems = []
        let searchURL = URL(string: menuItemURLString)
        let urlRequest = URLRequest(url: searchURL!)
        dataTask = defaultSession.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
            if let error = error {
                print(error.localizedDescription)
            }
            self.updateMenuItems(data: data!)
        })
        dataTask?.resume()
    }
    
    func loadSubMenuItems(searchCriteia:String){
        //print("loading sub menu items")
        subMenuItems = []
        let searchURL = URL(string: (subMenuItemURLString + searchCriteia))
        let urlRequest = URLRequest(url: searchURL!)
        //print(urlRequest)
        subMenuDataTask = defaultSession.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
            if let error = error {
                print(error.localizedDescription)
            }
            self.updateSubMenuItems(data: data!)
        })
        subMenuDataTask?.resume()
    }
    
    func updateMenuItems(data:Data?){
        if let data = data{
            do {
                let response = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions(rawValue:0)) as! [AnyObject]
                
                for item in response{
                    let newMenuItem = MenuItem()
                    newMenuItem.itemId = "\(String(describing: item["id"] as! Int))"
                    //newMenuItem.itemId = item["id"] as? String
                    newMenuItem.shortName = item["short_name"] as? String
                    newMenuItem.longName = item["name"] as? String
                    newMenuItem.specialInstructions = item["special_instructions"] as? String
                    newMenuItem.url = item["url"] as? String
                    menuItems.append(newMenuItem)
                }
                DispatchQueue.main.async {
                    print("updateSearchResults = \(Thread.isMainThread ? "Main Thread":"Background Thread")")
                    self.menuItemTableView.reloadData()
                }
            }
            catch let error as NSError{
                print("The error in the catch block is \(error)")
            }
            catch
            {
                print("Catch Block")
            }
        }
    }
    
    func updateSubMenuItems(data:Data?){
        if let data = data{
            do {
                let response = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions(rawValue:0)) as! [String:AnyObject]
                if let results = response["menu_items"] as? [AnyObject]{
                    for item in results{
                        let newSubMenu = SubMenuItem()
                        newSubMenu.itemId = "\(String(describing: item["id"] as! Int))"
                        newSubMenu.shortName =  item["short_name"] as? String
                        newSubMenu.longName =  item["name"] as? String
                        newSubMenu.itemDescription =  item["description"] as? String
                        newSubMenu.priceSmall =  item["price_small"] as? String//"\(String(describing: item["price_small"] as! Int))"
                        newSubMenu.priceLarge =  item["price_large"] as? String//"\(String(describing: item["price_large"] as! Int))"
                        newSubMenu.smallPortionName =  item["small_portion_name"] as? String
                        newSubMenu.largePortionName =  item["large_portion_name"] as? String
                        subMenuItems.append(newSubMenu)
                    }
                }
                DispatchQueue.main.async {
                    print("updateSearchResults = \(Thread.isMainThread ? "Main Thread":"Background Thread")")
                    self.subMenuItemTableView.reloadData()
                }
            }
            catch let error as NSError{
                print("The error in the catch block is \(error)")
            }
            catch
            {
                print("Catch Block")
            }
        }
    }
}

extension ViewController:NSTableViewDataSource, NSTableViewDelegate{
    func numberOfRows(in tableView: NSTableView) -> Int {
        switch tableView {
        case menuItemTableView:
            return menuItems.count
        case subMenuItemTableView:
            return subMenuItems.count
        default:
            return 0
        }
    }
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?{
        switch tableView {
        case menuItemTableView:
            let result:MenuItemTableCellView = tableView.make(withIdentifier: "defaultRow", owner: self) as! MenuItemTableCellView
            result.itemIdTextField.stringValue = menuItems[row].itemId!
            result.shortNameTextField.stringValue = menuItems[row].shortName!
            result.longNameTextField.stringValue = menuItems[row].longName!
            result.specialInstructionsTextField.stringValue = menuItems[row].specialInstructions!
            result.urlTextField.stringValue = menuItems[row].url!
            return result
            
        case subMenuItemTableView:
            let result:SubMenuItemTableCellView = tableView.make(withIdentifier: "defaultRow", owner: self) as! SubMenuItemTableCellView
            result.itemIdTextField.stringValue = subMenuItems[row].itemId!
            result.shortNameTextField.stringValue = subMenuItems[row].shortName!
            result.longNameTextField.stringValue = subMenuItems[row].longName!
            result.itemDescriptionTextField.stringValue = subMenuItems[row].itemDescription!
            if let smallPrice = subMenuItems[row].priceSmall{
                result.priceSmallTextField.stringValue = smallPrice
            }
            else{
                result.priceSmallTextField.stringValue = "Unknown"
            }
            if let largePrice = subMenuItems[row].priceLarge{
                result.priceLargeTextField.stringValue = largePrice
            }
            else{
                result.priceLargeTextField.stringValue = "Unknown"
            }

            if let smallPortion = subMenuItems[row].smallPortionName{
                result.smallPortionNameTextField.stringValue = smallPortion
            }
            else{
                result.smallPortionNameTextField.stringValue = "Unknown"
            }

            if let largePortion = subMenuItems[row].largePortionName{
                result.largePortionNameTextField.stringValue = largePortion
            }
            else{
                result.largePortionNameTextField.stringValue = "Unknown"
            }
            return result

        default:
            return nil
        }
    }
    func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
        switch tableView {
        case menuItemTableView:
            return 140
        case subMenuItemTableView:
            return 80
        default:
            return 23
        }
    }
    
    func tableViewSelectionDidChange(_ notification: Notification) {
        if self.menuItemTableView == notification.object as? NSTableView{
            //On selecting a menu item, the corresponding food items are retrieved and displayed on the second table
            loadSubMenuItems(searchCriteia: menuItems[self.menuItemTableView.selectedRow].shortName!)
            
        }
    }
}

You can download the code from Swift-URLSession Sample Code

Posted in Swift, Swift 3.1 Tagged with: , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

*