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
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
Leave a Reply