• Skip to main content
  • Skip to primary sidebar

Ravi Shankar

Tweaking Apps

  • Swift
  • Tech Tips

ios

SwiftExpress – Web Application Server in Swift

February 29, 2016 By Ravi Shankar Leave a Comment

Swift Express is a simple yet powerful web application written in Apple’s Swift language. This is an initiative started by Crossload Labs using Play Framework and Express.js

Installation

SwiftExpress GitHub provides a well documented steps for installing the server on Mac OS X and Linux. I tried this on Mac OS X and the whole setup was completed in less 10 minutes and was able quickly run the demo API request.

Features

Feature supported by Swift Express (Note :- re-published from SwiftExpress GitHub repository)

  • ? Linux support with and without Dispatch
  • 100% asynchronous (Future-based API)
  • Flexible and extensible
  • Full MVC support
  • Swift 2.1 and 2.2 compatible
  • Simple routing mechanism
  • Request handlers chaining
  • Typesafe Error Handlers
  • Templeates: Stencil and Mustache
  • Built-in JSON support
  • Easy creation of RESTful APIs
  • Built-in static files serving
  • Multiple contents types built-in support

A Quick Demo

Let us see a quick demo of JSON API Service deployed on SwiftExpress and consumed by iOS App written Swift.

Project Creation

Make sure to install the required libraries before creating the project. Launch terminal window and type the following

[code language=”plain”]swift-express init APIDemo
[/code]

The project creation should kick start the following set of dependancies updates.

[code language=”plain”]*** No Cartfile.resolved found, updating dependencies
*** Fetching Express
*** Fetching Stencil
*** Fetching CEVHTP
*** Fetching PathToRegex
*** Fetching Regex
*** Fetching GRMustache.swift
*** Fetching TidyJSON
*** Fetching BrightFutures
*** Fetching PathKit
*** Fetching ExecutionContext
*** Fetching Result
*** Checking out CEVHTP at "0.1.0"
*** Checking out GRMustache.swift at "bf7d6031d7e0dd862519eaba2b36b2e11a0d25a9"
*** Checking out Result at "1.0.3"
*** Checking out ExecutionContext at "0.3.1"
*** Downloading Regex.framework binary at "v0.5.2: Linux support final"
*** Downloading Express.framework binary at "v0.3.1: OS X binary build"
*** Checking out PathKit at "0.6.1"
*** Downloading TidyJSON.framework binary at "v1.1.0: faster parser"
*** Downloading PathToRegex.framework binary at "v0.2.0: linux support"
*** Checking out Stencil at "0.5.3"
*** Checking out BrightFutures at "0.4.0"
*** xcodebuild output can be found in /var/folders/gs/586wmrks50b9xdpq1309qn9m0000gn/T/carthage-xcodebuild.hzo8CL.log
*** Building scheme "MustacheOSX" in Mustache.xcworkspace
*** Building scheme "PathKit" in PathKit.xcworkspace
*** Building scheme "Result-Mac" in Result.xcodeproj
*** Building scheme "ExecutionContext-OSX" in ExecutionContext.xcodeproj
*** Building scheme "BrightFutures-Mac" in BrightFutures.xcworkspace
*** Building scheme "Stencil" in Stencil.xcodeproj
ld: warning: linking against dylib not safe for use in application extensions: /Users/ravishankar/Downloads/Demo/APIDemo/Carthage/Checkouts/BrightFutures/Carthage/Build/Mac/ExecutionContext.framework/ExecutionContext
Task: "init" done.[/code]

Navigate to APIDemo project folder and launch APIDemo.xcodeproj

[code language=”plain”]cd APIDemo
open APIDemo.xcodeproj[/code]

You should see the following project structure with main.swift file.

Create JSON API Call

Now edit the main.swift file and add app.views.register(JsonView())to enable JSON API service. This can be added below the app.views.register(StencilViewEngine())

Let us create a JSON Service that returns details about Marathon Runs for 2016. We should register a new API route /marathon/ and the response returned by service has been hard code as shown below. The app.get can be added above the app.listen(9999) method call.

[code language=”swift”]app.get("/marathons/") { request in
//compose the response as a simple dictionary
let response = [
[
"name": "Tokyo Marathon",
"date": "28/02/2016"
],
[
"name": "Dong-A Seoul International Marathon",
"date": "20/03/2016"
],
[
"name": "Aalborg Brutal Marathon",
"date": "25/03/2016"
],
[
"name": "Bath Beat Marathon",
"date": "02/04/2016"
],
[
"name": "Freiburg Marathon",
"date": "03/04/2016"
],
[
"name": "Canberra Marathon",
"date": "10/04/2016"
],
[
"name": "NN Rotterdam Marathon",
"date": "10/04/2016"
],
[
"name": "Vienna City Marathon",
"date": "10/04/2016"
],
[
"name": "Haspa Marathon Hamburg",
"date": "17/04/2016"
],
[
"name": "Blackpool Marathon",
"date": "24/04/2016"
]
]
//render disctionary as json (remember the one we’ve registered above?)
return Action.render(JsonView.name, context: response)
}[/code]

Compiling and run the project in Xcode should show “Express was successfully launched on port 9999” in the console message. Accessing the following url should display the JSON Response as shown below.

Now consuming this JSON service from iOS App using NSURLSession class as shown below.

[code language=”swift”]func loadData() {
let url = NSURL(string:"http://localhost:9999/marathons")

let request = NSURLRequest(URL: url!)

let session = NSURLSession.sharedSession().dataTaskWithRequest(request)
{ (data, response, error) -> Void in

if let error = error {
print("Error " + (error.localizedDescription))
return
}

if let data = data {
do {
let results = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! NSArray
for item in results {
print(item)
self.details.append(item as! [String : String])
}

dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
} catch (let error as NSError) {
print("JSON error" + error.localizedDescription)
}

} else {
print("No response")
return
}
}
session.resume()
}

[/code]

Since we are running the service using http, make sure to add Allow Arbitrary Loads to “YES” (under App Transport Security Settings) in info.plist file.

Looking forward to the production release of Swift Express !!!

Filed Under: ios, Swift, Swift 2 Tagged With: SwiftExpress, Web Service

How to programmatically add AutoLayout constraints?

February 18, 2016 By Ravi Shankar 2 Comments

AutoLayout solves the mystery when designing app for more than one screen size and for both Portrait and Landscape orientation. This is done by adding constraints to the views using various Auto Layout options available as part of Interface Builder. As an iOS developer, you can also add these constraints programmatically using NSLayoutConstraints or Visual Format Languages. In this tutorial we will see how to use NSLayoutConstraints for adding constraints to the views.

Screen Design For this Demo

For this demo, we will design something similar to flag of England by adding all the views and constraints programmatically. Funza Academy have published a video tutorial on how to design this screen using Interface Buidler, please check that out.

 

Overview of Steps

The following will be done to create the UI programmatically.

  1. Change the background colour of View to Red
  2. Add four rectangular views of equal widths on top left, top right, bottom left and bottom right of the parent view. The background colour for all these views will be set to red and Super View to red
  3. Pin the four rectangular views to the corresponding corner by adding constraints.
  4. Add constraints to the views to leave constant gap between each views (white background). This would ensure red line is shown in between these views.
  5. Disable AutoResizingMasks for teh four sub views.

Project Setup

Create a new Xcode project by selecting Single View Application for the project template. Also choose the language and Swift and iPhone for Devices.

Create Views

Navigate to ViewController.swift file and add the following code snippet after the class declaration and above the viewDidLoad() function.

[code language=”swift”]private let SCREEN_SIZE = UIScreen.mainScreen().bounds
private let GAP_BETWEEN_VIEWS:CGFloat = 0.08

// Create four Subviews

var topLeftView = UIView()
var topRightView = UIView()
var bottomLeftView = UIView()
var bottomRightView = UIView()[/code]

The above set of code creates two constants which stores the screen size and specifies the gap between the views. Then we have created four variables that holds the instance of four views.

Add the following addViews functions below the viewDidLoad method

[code language=”swift”]
func addViews() {
let heightOfSubView = SCREEN_SIZE.height / 2 – SCREEN_SIZE.height * GAP_BETWEEN_VIEWS/2
let widthOfSubView = SCREEN_SIZE.width / 2 – SCREEN_SIZE.height * GAP_BETWEEN_VIEWS/2

// Calculate the height and size of each views
topLeftView = UIView(frame: CGRect(x: 0, y: 0, width: widthOfSubView, height: heightOfSubView))
topRightView = UIView(frame: CGRect(x: widthOfSubView + (SCREEN_SIZE.height * GAP_BETWEEN_VIEWS), y: 0, width: widthOfSubView, height: heightOfSubView))
bottomLeftView = UIView(frame: CGRect(x: 0, y: heightOfSubView + (SCREEN_SIZE.height * GAP_BETWEEN_VIEWS), width: widthOfSubView, height: heightOfSubView))
bottomRightView = UIView(frame: CGRect(x: widthOfSubView + (SCREEN_SIZE.height * GAP_BETWEEN_VIEWS), y: heightOfSubView + (SCREEN_SIZE.height * GAP_BETWEEN_VIEWS), width: widthOfSubView, height: heightOfSubView))

topLeftView.backgroundColor = UIColor.whiteColor()
topRightView.backgroundColor = UIColor.whiteColor()
bottomLeftView.backgroundColor = UIColor.whiteColor()
bottomRightView.backgroundColor = UIColor.whiteColor()

view.addSubview(topLeftView)
view.addSubview(topRightView)
view.addSubview(bottomLeftView)
view.addSubview(bottomRightView)
}
[/code]

The addViews function, calculates the height and width of each subview (rectangular view with white background). Then the four views are created with positioning them in the corresponding corners i.e topLeft, topRight, bottomLeft and bottomRight. The background colour for these views are set White then they added to parent view using addSubView method.

Now call this addViews function inside viewDidLoad method. Also make sure to set the background colour of parent view to red.

[code language=”swift”]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.redColor()
addViews()
}
[/code]

Compiling and running this app on iPhone 6 simulator, the portrait mode should look as shown below

And in Landscape mode the views are randomly placed. Let us now fix this by adding the required constraints for all the veiws.

Add Constraints

In order to pin the four subviews, we need to add Leading, Traling, Top and Bottom constraints for the views based on their position. For example the top left view needs a leading and top constraint to the superview. The below screenshot should give a better understanding of constraints required for each view. And we will be adding these constraints using NSLayoutConstraint class

Pin the views to the side

Add the following code snippet that which creates Leading and Top constraint for TopLeft view.

[code language=”swift”]func addtopLeftViewConstraints() {
let topLeftViewLeadingConstraint = NSLayoutConstraint(item: topLeftView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal
, toItem: view, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)

let topLeftViewTopConstraint = NSLayoutConstraint(item: topLeftView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal
, toItem: view, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)

NSLayoutConstraint.activateConstraints([topLeftViewLeadingConstraint, topLeftViewTopConstraint])
}
[/code]

First we create a leading constraint to the topLeft view and super view. Then a top constraint is added for topLeft view to the SuperView. Constant refers to the gap between the views using attribute we specify the Leading or Top attributes. Since we do not need any gap between corners the constant value is set to 0. Finally we add these constraints for the view by using the activateConstraints function in NSLayoutConstraint class.

Now let us repeat the above steps topRight. bottomLeft and bottomLeft Views.

[code language=”swift”]func addTopRightViewConstraints() {

let topRightViewTrailingConstraint = NSLayoutConstraint(item: topRightView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal
, toItem: view, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)

let topRightViewTopConstraint = NSLayoutConstraint(item: topRightView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal
, toItem: view, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)

NSLayoutConstraint.activateConstraints([topRightViewTrailingConstraint, topRightViewTopConstraint])
}

func addBottomLeftViewConstraints() {

let bottomLeftViewLeadingConstraint = NSLayoutConstraint(item: bottomLeftView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal
, toItem: view, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)

let bottomLeftViewBottomConstraint = NSLayoutConstraint(item: bottomLeftView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal
, toItem: view, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)

NSLayoutConstraint.activateConstraints([bottomLeftViewLeadingConstraint, bottomLeftViewBottomConstraint])

}

func addBottomRightViewConstraints() {

let bottomRightViewTrailingConstraint = NSLayoutConstraint(item: bottomRightView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal
, toItem: view, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)

let bottomRightViewBottomConstraint = NSLayoutConstraint(item: bottomRightView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal
, toItem: view, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)

NSLayoutConstraint.activateConstraints([bottomRightViewTrailingConstraint, bottomRightViewBottomConstraint])
}[/code]

Leave space between views

We need leave constant space in the middle between these views. This can be achieved by adding constriants between the top/bottom views and left/right views. So basically we need to add two vertical spacing and two horizontal spacing constraints.

The below code snippet adds the required spacing constraings between views by specifying a constant value. Then activate these constraints by calling NSLayoutConstraint.activateConstraint.

[code language=”swift”] func addTopBottomConstraints() {
let verticalSpacing1 = NSLayoutConstraint(item: bottomLeftView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: topLeftView, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: (SCREEN_SIZE.height * GAP_BETWEEN_VIEWS))

let verticalSpacing2 = NSLayoutConstraint(item: bottomRightView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: topRightView, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: (SCREEN_SIZE.height * GAP_BETWEEN_VIEWS))

NSLayoutConstraint.activateConstraints([verticalSpacing1, verticalSpacing2])
}

func addLeftRightConstraints() {
let horizontalSpacing1 = NSLayoutConstraint(item: topRightView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: topLeftView, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: (SCREEN_SIZE.height * GAP_BETWEEN_VIEWS))

let horizontalSpacing2 = NSLayoutConstraint(item: bottomRightView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: bottomLeftView, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: (SCREEN_SIZE.height * GAP_BETWEEN_VIEWS))

NSLayoutConstraint.activateConstraints([horizontalSpacing1, horizontalSpacing2])
}
[/code]

Add Equal Width and Equal Height

Now add Equal Width and Equal Height constraints for topRight, bottomLeft and bottomRight views based on topLeft View. The code snippet for EqualWidth and EqualHeight is given below. The first function adds equal width constaints for all the three views based on TopLeft view. Similarly the second function adds equal height constraints based on the TopLeft view.

[code language=”swift”]
func addEqualWidthConstraints() {
let topRightViewWidth = NSLayoutConstraint(item: topLeftView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: topRightView, attribute: NSLayoutAttribute.Width, multiplier: 1, constant: 0)

let bottomLeftViewWidth = NSLayoutConstraint(item: topLeftView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: bottomLeftView, attribute: NSLayoutAttribute.Width, multiplier: 1, constant: 0)

let bottomRightViewWidth = NSLayoutConstraint(item: topLeftView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: bottomRightView, attribute: NSLayoutAttribute.Width, multiplier: 1, constant: 0)

NSLayoutConstraint.activateConstraints([topRightViewWidth, bottomLeftViewWidth,bottomRightViewWidth ])
}

func addEqualHeightConstraints() {
let topRightViewHeight = NSLayoutConstraint(item: topLeftView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: topRightView, attribute: NSLayoutAttribute.Height, multiplier: 1, constant: 0)

let bottomLeftViewHeight = NSLayoutConstraint(item: topLeftView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: bottomLeftView, attribute: NSLayoutAttribute.Height, multiplier: 1, constant: 0)

let bottomRightViewHeight = NSLayoutConstraint(item: topLeftView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: bottomRightView, attribute: NSLayoutAttribute.Height, multiplier: 1, constant: 0)

NSLayoutConstraint.activateConstraints([topRightViewHeight, bottomLeftViewHeight,bottomRightViewHeight ])
}[/code]

Disable AutoResizingMasks

Finally we need to disable the auto resizing masks for all the views to prevent constraints getting automatically based on the autoResizingMask property. The autoResizingMask property is set to true when the views are added programmtically added so we need make sure that this property for all these views are set to false.

[code language=”swift”]func disableAutoResizingMasks() {
topLeftView.translatesAutoresizingMaskIntoConstraints = false
topRightView.translatesAutoresizingMaskIntoConstraints = false
bottomLeftView.translatesAutoresizingMaskIntoConstraints = false
bottomRightView.translatesAutoresizingMaskIntoConstraints = false
}[/code]

Just to make code little organized, we can create a new function that calls these constraints functions.

[code language=”swift”]func addConstraints() {
addtopLeftViewConstraints()
addTopRightViewConstraints()
addBottomLeftViewConstraints()
addBottomRightViewConstraints()
addTopBottomConstraints()
addLeftRightConstraints()
addEqualWidthConstraints()
addEqualHeightConstraints()
disableAutoResizingMasks()
}
[/code]

Then call the addConstraints function in viewDidLoad method

[code language=”swift”]override func viewDidLoad() {
super.viewDidLoad()

view.backgroundColor = UIColor.redColor()

addViews()
addConstraints()
}[/code]

If you need any assistance in Auto Layout, check out our new iOS 9 Auto Layout Tutorials.

Download the source code from GitHub

Filed Under: Auto Layout, ios Tagged With: NSLayoutConstraint, Swift

iOS Swift – Firebase Demo

November 26, 2015 By Ravi Shankar 4 Comments

Firebase is a platform that allows web and mobile application to store data in cloud. In this article, we will see an example app written in Swift that uses Firebase for storing and retrieving data in real time. The source code for this demo is available under GitHub.

This demo app consists of three fields for capturing name, date and image. These data are then converted in to required data type for storing purpose.

Installing Firebase in iOS SDK Project

The easiest way to include Firebase SDK to your iOS project is by using Cocoapods and the instruction are clearly given in Firebase documentation section. After installing the Firebase iOS sdk make sure to create a bridge file by adding the following import statement.

[code language=”swift”]#import Firebase/Firebase.h
[/code]

Firebase DataStore

User with Google or GitHub account can directly login to Firebase. The data stored in Firebase in JSON format. Find below a screenshot of the data stored by this demo app.

Profiles is top node and under which each row is stored as key/value pairs with name as the identifier for each row. Firebase provides a path (URL ) for storing the data which ends with firebaseio.com. You should be able find this URL in Firebase main screen.

[code language=”plain”]Example :- _unique_identifier_.firebaseio.com[/code]

Saving data to Firebase

You need to create a reference to Firebase class as shown below

[code language=”swift”]let firebase = Firebase(url:”https://_unique_identifer.firebaseio.com/profiles”)[/code]

replace _unique_identifier with the identifier provided for your Firebase account.

The following piece of code is used for saving the information to Firebase.

[code language=”swift”]@IBAction func save(sender: AnyObject) {

let name = nameTextField.text
var data: NSData = NSData()

if let image = photoImageView.image {
data = UIImageJPEGRepresentation(image,0.1)!
}

let base64String = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)

let user: NSDictionary = [“name”:name!,”dob”:dateOfBirthTimeInterval, “photoBase64”:base64String]

//add firebase child node
let profile = firebase.ref.childByAppendingPath(name!)

// Write data to Firebase
profile.setValue(user)
}
[/code]

The above code does the following

  1. Converts image to to JPEG also compresses the size as we will be storing the image as base64EncodedString. 
  2. Creates a dictionary with name, image (String data) and date (as timeinterval).
  3. This dictionary is then added to the FIrebase Datastore by appending the name as the identifier for each row.
  4. And to save the data to Firebase, you need to call profile.setValue by passing the dictionary object.

Retrieving data from Firebase

Here again you need to create a reference to Firebase class by passing the required path as shown below

[code language=”plain”]let firebase = Firebase(url:”https://_unique_identifer.firebaseio.com/profiles”[/code]

In the following price of code, firebase.observerEventType is used for retrieving the data from Firebase account. The data gets refreshed in real time when ever any updates happen in the data store. This is really cool!!!

[code language=”swift”]func loadDataFromFirebase() {

UIApplication.sharedApplication().networkActivityIndicatorVisible = true

firebase.observeEventType(.Value, withBlock: { snapshot in
var tempItems = [NSDictionary]()

for item in snapshot.children {
let child = item as! FDataSnapshot
let dict = child.value as! NSDictionary
tempItems.append(dict)
}

self.items = tempItems
self.tableView.reloadData()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
})
}[/code]

snapshot referes to the collection of records store under a path. You can iterate through the collection to reteive each data item.

Delete a row from Firebase

[code language=”swift”]override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {

let dict = items[indexPath.row]
let name = dict[“name”] as! String

// delete data from firebase

let profile = firebase.ref.childByAppendingPath(name)
profile.removeValue()
}
}
[/code]

 

Removing a row from Firebase can be done by calling removeValue method on the Firebase object reference as shown in the above code snippet.

In this tutorial, we have seen only the code related with Firebase. You can download the full source from here

Filed Under: ios, Swift Tagged With: Cloud, Firebase

What is new in Swift 2.0

October 20, 2015 By Ravi Shankar Leave a Comment

Lots of new feature have been introduced as part of Swift 2.0. The list includes guard, repeat-while, defer, error handling, protocol extensions, print, pattern matching, early exits, UI Testing, UI Stackview etc. Let us see some of these cool features.

guard, try and catch

[code language=”swift”]func printISPDetails() {

let url = NSURL(string: “http://www.telize.com/geoip”)
let request = NSURLRequest(URL: url!)

let session = NSURLSession.sharedSession()

let task = session.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
guard error == nil else {
print(“Error while calling the webservice ” + error!.localizedDescription)
return
}

let status = (response as! NSHTTPURLResponse).statusCode

guard status == 200 else {
print(“Received response status code as \(status)”)
return
}

guard data != nil else {
print(“data not received from webservice”)
return
}
do {
let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
print(dict)
} catch let error as NSError {
print(“Error parsing JSON response ” + error.localizedDescription)
}
}
task.resume()
}
[/code]

The guard statement is used for checking the else part i.e when there is error print the error and exit the function.

[code language=”swift”]guard error != nil else {
print(“Error while calling the webservice ” + error!.localizedDescription)
return
}[/code]

Then we have the try catch statments used for parsing the JSON response. Prior to Swift 2.0, JSONObjectWithData method had an extra argument (NSError) for cpaturing the error. Now in Swit 2.0 this is done in cleaner way by throwing an exception when any errors occur while parsing the JSON response. And you can handle this using do, try , catch block as shown below.

[code language=”swift”] do {
let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
print(dict)
} catch let error as NSError {
print(“Error parsing JSON response ” + error.localizedDescription)
}
[/code]

defer

if you coming from Java background then defer is simular to finally statement. You can use when you call a piece of code irrespective of success of failure of opertation. One common example would be closing the file handle in a function does a read write operation to a file.

[code language=”swift”]defer { ..code… }
[/code]

repeat

We all know how do while works, the gets executed for the first time irrespective of codition in the while loop.

[code language=”swift”]var index = 0
do {
print(index)
index++
} while (index < 10)[/code]

In Swift 2.0, the do keyword is replaced by repeat. Makes more sense right!

[code language=”swift”]var index = 0
repeat {
print(index)
index++
} while (index < 10)[/code]

Filed Under: ios, Swift 2 Tagged With: defer, guard, try and catch

Search photos on Flickr – Web Service

August 11, 2015 By Ravi Shankar 1 Comment

In this tutorial we will see the steps required to write a demo app that retrieves photos from Flickr based on search text and display them in a UICollectionView. This also covers how to integrate 3rd party libaries using CocoaPods.

Flickr API

Register your app in Flickr and get the API key details. You will need the API key to access the Flickr Photo Search API.

Project Setup

Create a new project by selecting Single View Application template and selecting language as Swift.

Install 3rd Party Libraries

We will be using CocoaPods to install third party libraries. First make sure to install CocoaPods on your Mac. Launch terminal window, navigate to the newly created project folder and create a file with name as Podfile. Edit the Podfile and add the following content

use_frameworks!
pod ‘Alamofire’
pod ‘SwiftyJSON’
pod ‘Haneke’

Alamorfire, SwiftyJSON and Haneke are the thirdparty libraries that will be used in this project. Alamofire for WebSerice call, SwiftJSON for parsing JSON data and Haneke for caching images.

After adiding the required libraries, type pod install on the terminal window. This should install the required libraries and create the necessary workspace. Now open <Project Name>.xcworkspace to add the functionality to the project.

Model Class

Add a new file (Photo.swift) to act as a place holder for storing photo details. Photo struct has properties for id, title, farm, secret, server and computed property which constructs the URL of the photo.

[code language=”swift”]
import UIKit

struct Photo {
var id: String
var title: String
var farm: String
var secret: String
var server: String
var imageURL: NSURL {
get {
let url = NSURL(string: "http://farm\(farm).staticflickr.com/\(server)/\(id)_\(secret)_m.jpg")!
return url
}
}
}
[/code]

 

Web Service Integration

Add new Swift file to the project and provide a name as Services.swift. Add the following code snippet to Services.swift

[code language=”swift”]import Alamofire
import SwiftyJSON

protocol FlickrPhotoDownloadDelegate {
func finishedDownloading(photos:[Photo])
}

class Services {

let API_KEY = “"
let URL = "https://api.flickr.com/services/rest/"
let METHOD = "flickr.photos.search"
let FORMAT_TYPE:String = "json"
let JSON_CALLBACK:Int = 1
let PRIVACY_FILTER:Int = 1

var delegate:FlickrPhotoDownloadDelegate?

// MARK:- Service Call

func makeServiceCall(searchText: String) {

Alamofire.request(.GET, URL, parameters: ["method": METHOD, "api_key": API_KEY, "tags":searchText,"privacy_filter":PRIVACY_FILTER, "format":FORMAT_TYPE, "nojsoncallback": JSON_CALLBACK])
.responseJSON { (request, response, data, error) in
if data != nil {
let jsonData:JSON = JSON(data!)
let photosDict = jsonData["photos"]
let photoArray = photosDict["photo"]
var photos = [Photo]()

for item in photoArray {
let id = item.1["id"].stringValue
let farm = item.1["farm"].stringValue
let server = item.1["server"].stringValue
let secret = item.1["secret"].stringValue
let title = item.1["title"].stringValue
let photo = Photo(id:id, title:title, farm:farm, secret: secret, server: server)
photos.append(photo)
}
self.delegate?.finishedDownloading(photos)
} else {
println(error)
}
}

}
}
[/code]

The above code does the following

  • Makes a web service call to the flickr photo search API using Alamofire third party library.
  • Web Service results are parsed using SwiftyJSON library. 
  • The delegates are notified about the completion of the download by pasisng the photos array.

Design User Intrerfaces

Navigate to Main.storyboard, add a UICollectionView and UISearchBar to the ViewController as shown in the below screenshot.

Make sure add Auto Layout constriants so that the UI looks good for both iPad and iPhone. If you need help with Auto Layout then check out this tutorial.

As shown in the above screenshot, you need to add UIImageView to the CollectionViewCell. Now embed this ViewController inside Navigation Controller using the Editor -> Embed In -> Navigation Controller menu option.

Add another second View Controller to the Storyboard which would act as the Detail View Controller for displaying selected Photo.

Add Custom Cell

Create a new file (PhotoCell.swift) by selecting CocoaTouch class and keeping the base class as UICollectionViewCell. Set this class as the Custom Class for UICollectionViewCell in the Interface builder. Then create IBOutlet for the imageView in PhotoCell.

[code language=”swift”]
import UIKit

class PhotoCell: UICollectionViewCell {

@IBOutlet weak var imageView: UIImageView!

}
[/code]

 

Add PhotosViewController

 

You can rename the existing ViewController.swift to PhotosViewController.swift file. In the storyboard, select the ViewController with CollectionView and set the custom class name to PhotosViewController.

Create IBOutlets for UISearchBar and UICollectionView in PhotosViewController.

[code language=”swift”]@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var collectionView: UICollectionView!
[/code]

Implement the SearchBar delegate method and service call for searching photos based on the entered Search Text.

[code language=”swift”]// MARK:- SearchButton

func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchForPhotos()
searchBar .resignFirstResponder()
}

// MARK:- SearchPhotos

func searchForPhotos() {
service.makeServiceCall(searchBar.text)
}
[/code]

Also add the following code snippet that processes the downloaded photos returned from the web service call. All UI calls needs to be done in the main thread hence setting the photos instance variable and update CollectionView are done within dispatch_async(dispatch_get_main_queue()).

[code language=”swift”]// MARK:- Flickr Photo Download
func finishedDownloading(photos: [Photo]) {
dispatch_async(dispatch_get_main_queue(), { () -&gt; Void in
self.photos = photos
self.collectionView?.reloadData()
})
}[/code]

Set the all the delegates and default value for the search bar in viewDidLoad method.

[code language=”swift”]override func viewDidLoad() {
super.viewDidLoad()

collectionView.dataSource = self
searchBar.delegate = self
collectionView.delegate = self
service.delegate = self

// default
searchBar.text = "famous quotes"
searchForPhotos()
}[/code]

Add CollectionViewDataSource methods

In the PhotosViewController add the CollecitonViewDataSource methods for displaying the photos. This can be done by adding an extension to the PhotosViewController class.

[code language=”swift”]extension PhotosViewController: UICollectionViewDataSource {
// MARK:- UICollectionViewDataSource methods

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -&gt; Int {
return 1
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -&gt; Int {
return photos.count
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -&gt; UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotoCell
let photo = photos[indexPath.row]
cell.imageView.frame.size = cell.frame.size
cell.imageView.hnk_setImageFromURL(photo.imageURL)
return cell
}

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let photo:Photo? = photos[indexPath.row]
if let photo = photo {
let detailViewController = storyboard?.instantiateViewControllerWithIdentifier("DetailViewController") as! DetailViewController
detailViewController.photo = photo
presentViewController(detailViewController, animated: true, completion: nil)
}
}
}[/code]

The cellForItemAtIndexPath uses the custom cell (PhotoCell) for displaying the Photo. Also uses the thirdparty library Haneke to load the imageView. And in the didSelectItemAtIndexPath method add the code for calling the DetailViewController by passwing the selecting photo.

Finally in the PhotosViewController add the code that calcuates the Cell Size to be displayed on the CollecitonView. This is done by implementing the UICollectionViewDelegateFlowLayout method.

[code language=”swift”]extension PhotosViewController: UICollectionViewDelegateFlowLayout {
// MARK:- UICollectioViewDelegateFlowLayout methods

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -&gt; CGSize
{
let length = (UIScreen.mainScreen().bounds.width-15)/4
return CGSizeMake(length,length);
}
}
[/code]

Check out this article in stackoverflow for calculating the size of the CollectionViewCell.

Add DetailViewController

Add a new CocoaTouch class of Subclass as UIViewController with name as DetailViewController. Then add the following code to the class file.

[code language=”swift”]
import UIKit
import Haneke

class DetailViewController: UIViewController {

var photo:Photo?
var imageView:UIImageView?

override func viewDidLoad() {
super.viewDidLoad()

if let photo = photo {

imageView = UIImageView(frame: CGRectMake(0, 0, 320, 320))
imageView?.hnk_setImageFromURL(photo.imageURL)
view.addSubview(imageView!)

let tapGestureRecogonizer = UITapGestureRecognizer(target: self, action: Selector("close"))
view.addGestureRecognizer(tapGestureRecogonizer)
}
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}

// MARK:- viewDidLayoutSubviews

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

let size = view.bounds.size
let imageSize = CGSizeMake(size.width,size.width)
imageView?.frame = CGRectMake(0.0, (size.height – imageSize.height)/2.0, imageSize.width, imageSize.height)

}
// MARK:- close
func close() {
dismissViewControllerAnimated(true, completion: nil)
}
}[/code]

The above code does the following

  • Calculates the size of the imageview and aligns it to the centre of the ViewController.
  • Loads the image using the third party library Haneke.
  • Adds a gesture recogonizer that dismisses the ViewContorller on tap gesture.

Dowload the source code from here.

Filed Under: ios, Swift, UICollectionView, WebService, Xcode Tagged With: CocoaPods, Flickr, Photo

UICollectionView Demo in Swift

July 31, 2015 By Ravi Shankar

UICollectionView can be used for displaying set of data in rows and columns The main difference between UICollectionView and UITableView is that CollectionView can be display more than one column. The following topics are covered in this article

  • Simple collection view demo
  • Displaying data in Collection View
  • Implementiing Custom Cell 
  • Adding Section Headers
  • Highlighting Cell
  • Insert Cell
  • Delete Cells

Project Setup

Create a new project by selecting Single View Application template.

Provide the necessary details in the Project options screen and select the language as Swift.

Adding CollectionView

Let us first try out simple collection view to get a better understanding of how various components works. Then let us move on to a demo that displays various fruits grouped in different section. And you will be able insert and delete cells from the Collection View.

Navigate to Main.storyboard, disable Auto Layout and size classes using File Inspector option.

Then drag and drop CollectionView from object library to ViewController. The ViewController with CollectionView should look as shown below.

The square box inside collection view is UICollectionViewCell. Using Attributes Inspector, change the background colour of CollectionView to white. Then select UICollecitonViewCell and enter value for identifier as “CellIdentifier”

Using Assistant Editor, add an IBOutlet to CollectionView in ViewController.swlft file.

[code language=”swift”]@IBOutlet weak var collectionView: UICollectionView![/code]

Implement UICollectionViewDataSource methods

When the CollectionView loads, we need to specify the data for the cells. This can be done by implementing
UICollectionViewDataSource related methods. UICollectionViewDataSource protocols defines the following mandatory and optional methods.

[code language=”swift”]protocol UICollectionViewDataSource : NSObjectProtocol {

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -&gt; Int

// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -&gt; UICollectionViewCell

optional func numberOfSectionsInCollectionView(collectionView: UICollectionView) -&gt; Int

// The view that is returned must be retrieved from a call to -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
optional func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -&gt; UICollectionReusableView
}[/code]

First make sure to set the delegate property of collectionView to self in viewDidLoad function. Then add instance level property for storing Cell Identifier.

[code language=”swift”]let identifier = "CellIdentifier"

override func viewDidLoad() {
super.viewDidLoad()

collectionView.dataSource = self
}[/code]

Now add the implementation for mandatory methods numberOfItemsInSection and cellForItemAtIndexOath in ViewController.swlft. We can do this by adding an extension to ViewController class. Add this extension after the closing parenthesis of View Controller class.

[code language=”swift”]// MARK:- UICollectionViewDataSource Delegate
extension ViewController: UICollectionViewDataSource {

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -&gt; Int {
return 12
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -&gt; UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(identifier, forIndexPath: indexPath) as! UICollectionViewCell
cell.backgroundColor = UIColor.redColor()

return cell
}
}
[/code]

We have temporarily hard coded the number of items to be shown as 12. Using dequeReusableCellWithReuseIdentifier funciton, create cells based on index path. Then change the background color of cell to red. Now when you build and run the project, you should see collection view showing some cells as shown below.

If you want to have 3 rows and 4 columns then use the Collection View’s Size Inspector to make the appropriate changes to Cell Size and Minimum Spacing attributes.

If you Build and Run the project you should notice the changes.

Collection View DataSource

Now for the actual demo project, let us create a seperate class which will act as DataSource. Before creating a datasource, let us create a model class with file name as Fruit.swift. The implementation of the Fruit class should look as shown below

[code language=”swift”]class Fruit {
var name:String?
var group:String?

init(name: String, group: String) {
self.name = name
self.group = group
}
}
[/code]

 

Fruit struct is just a place holder for storing fruit related information. Now create another class for DataSource and name it as DataSource.swift. This class will provide the data related methods to the CollectionView. Durining the initialisation of the class, the data is read from plist and populated to fruits and groups array. Then using the respective helper methods the details will be retrieved by CollectionView.

[code language=”swift”]import Foundation

class DataSource {

init() {
populateData()
}

var fruits:[Fruit] = []
var groups:[String] = []

func numbeOfRowsInEachGroup(index: Int) -&gt; Int {
return fruitsInGroup(index).count
}

func numberOfGroups() -&gt; Int {
return groups.count
}

func gettGroupLabelAtIndex(index: Int) -&gt; String {
return groups[index]
}

// MARK:- Populate Data from plist

func populateData() {
if let path = NSBundle.mainBundle().pathForResource("fruits", ofType: "plist") {
if let dictArray = NSArray(contentsOfFile: path) {
for item in dictArray {
if let dict = item as? NSDictionary {
let name = dict["name"] as! String
let group = dict["group"] as! String

let fruit = Fruit(name: name, group: group)
if !contains(groups, group){
groups.append(group)
}
fruits.append(fruit)
}
}
}
}
}

// MARK:- FruitsForEachGroup

func fruitsInGroup(index: Int) -&gt; [Fruit] {
let item = groups[index]
let filteredFruits = fruits.filter { (fruit: Fruit) -&gt; Bool in
return fruit.group == item
}
return filteredFruits
}
}[/code]

Then add the required images to Images.xcassets, you can download the images for this project from GitHub.

Also add/create a new plist file which contains the information about the fruits and the group they belong to (download it from here).

Add Custom CollectionViewCell

For displaying image and caption in Collection View Cell, create a Custom Cell subclass of UICollectionViewCell. Provide name for the new file as Fruit Cell.

Navigate to Main.storyboard, select CollectionViewCell and using identity inspector set the class as FruitCell.

Drag and drop UIImageView and Label on to CollectionViewCell and add corresponding IBOutlets to FruitCell class.

[code language=”swift”]
import UIKit

class FruitCell: UICollectionViewCell {

@IBOutlet weak var caption: UILabel!
@IBOutlet weak var imageView: UIImageView!
}
[/code]

Display Data

We need to make changes to the ViewController extension for displaying the data form the DataSource with CustomCell. First Create an instance variable for DataSource in ViewController class

[code language=”swift”]let dataSource = DataSource()
[/code]

Then make the following changes to UICollectionViewDataSource extension to reflect the DataSource and FruitCell classes.

[code language=”swift”]
extension ViewController : UICollectionViewDataSource {

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -&gt; Int {
return dataSource.groups.count
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -&gt; Int {
return dataSource.numbeOfRowsInEachGroup(section)
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -&gt; UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(identifier,forIndexPath:indexPath) as! FruitCell

let fruits: [Fruit] = dataSource.fruitsInGroup(indexPath.section)
let fruit = fruits[indexPath.row]

let name = fruit.name!

cell.imageView.image = UIImage(named: name.lowercaseString)
cell.caption.text = name.capitalizedString

return cell
}
}[/code]

Now if you build and run the project, you should see the colleciton view displaying fruits along with the caption.

The rows and columns are not properly aligned, we can fix this calculate the size of the cell based on height and width of collection view. Let us make the Collection View to display 2 cells per row. Navigate to Main,storyboard, update the Cell Size property to 182 as shown below.

Make sure to adjust the UIImageView and Label to fit the changed Collection View cell size. Now if you compile and run the project, the simulator should look as shown below.

Add Section Header

Headers for each can be added by implementing the viewForSupplementaryElementOfKind method defined as part of UICollectionViewDataSource protocol. We already have function in DataSource class that returns caption for each section. Let us add new Custom class for CollectionView section header and map this class to the header view in Interface builder.

Create FruitsHeaderView with subclass as UICollectionResuableView. Then navigate to Main.storyboard, select Collection View -> Attributes Inspector and enable Section Header under Accessories.

Now select the Section Header in the Collection View and set the class as FruitsHeaderView using Identity Inspector. In the Attributes Inspector enter the Identifier as HeaderView

Add UILabel to the header view to display the seciton title and corresponding IBOutlet to FruitsHeaderView class. You can also provide some background colour for the HeaderView,

[code language=”swift”]
import UIKit

class FruitsHeaderView: UICollectionReusableView {
@IBOutlet weak var sectionLabel: UILabel!
}
[/code]

Now add the following viewForSupplementaryElementOfKind implementation to the UICollectionViewDataSource extension in ViewController class.

[code language=”swift”] func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -&gt; UICollectionReusableView {

let headerView: FruitsHeaderView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: headerViewIdentifier, forIndexPath: indexPath) as! FruitsHeaderView

headerView.sectionLabel.text = dataSource.gettGroupLabelAtIndex(indexPath.section)
return headerView
}
[/code]

 

Make sure to add an instance variable let headerViewIdentifier = “HeaderView” in ViewController.class. In viewForSupplementaryElementOfKind function, we are creating an instance of FruitsHeaderView class using dequeueReusableSupplementaryViewOfKind function. Then set the section label by retrieving the caption from DataSource class. Build and run the app on iPhone simulator should show the following

Add Detail View

Now to add a detail View, let us first embed the ViewController in Navigation Controller. Then add another ViewController for using it as Detail View. Create a segue by Control + drag from CollectionView Cell to the new View Controller and select the Segue as Push.

Add UIImageView to the DetailViewController and centre align it to the View. Then add a new class (sub class of UIViewController) and name the file as DetailViewController

Map this class to the Second View Controller in the Interface builder. Then add the IBOutlet for the UIImageView in DetailViewController class.

@IBOutlet weak var imageView: UIImageView!

From the main View Controller, the selected Fruit needs to be passed to the DetailViewController class. Add a new property which is of type Fruit

[code language=”swift”]var fruit: Fruit?[/code]

In viewDidLoad method, add code to populate the title and image.

[code language=”swift”]if let fruit = fruit {
navigationItem.title = fruit.name?.capitalizedString
imageView.image = UIImage(named: fruit.name!.lowercaseString)
}[/code]

Navigate to ViewController.class and implement the prepareForSegue and getIndexPathForSelectedCell function.

[code language=”swift”]// MARK:- prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// retrieve selected cell &amp; fruit
if let indexPath = getIndexPathForSelectedCell() {

let fruit = dataSource.fruitsInGroup(indexPath.section)[indexPath.row]

let detailViewController = segue.destinationViewController as! DetailViewController
detailViewController.fruit = fruit
}
}

func getIndexPathForSelectedCell() -&gt; NSIndexPath? {

var indexPath:NSIndexPath?

if collectionView.indexPathsForSelectedItems().count &gt; 0 {
indexPath = collectionView.indexPathsForSelectedItems()[0] as? NSIndexPath
}
return indexPath
}
[/code]

In the above function using the selected Item indexPath the corresponding fruit is retrieved from DataSource class. Then this information is passed to the DetailViewController.

Highlight Selection

When the user taps any cell, it would nice to see the cell getting highlighted. This can be done by implementing following function as extension.

[code language=”swift”]// MARK:- UICollectionViewDelegate Methods

extension ViewController : UICollectionViewDelegate {
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
highlightCell(indexPath, flag: true)
}
}

func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
highlightCell(indexPath, flag: false)
}
}[/code]

Also make sure to add the collectionView.delegate = self to viewDidLoad function. Then add the following highlight function inside ViewController class.

[code language=”swift”]// MARK:- Highlight
func highlightCell(indexPath : NSIndexPath, flag: Bool) {

let cell = collectionView.cellForItemAtIndexPath(indexPath)

if flag {
cell?.contentView.backgroundColor = UIColor.magentaColor()
} else {
cell?.contentView.backgroundColor = nil
}
}
[/code]

 

Since we want to dehighlight the cell when the user returns from the DetailViewController. implement the viewDidAppear function with de-hightlight functionality.

[code language=”swift”]override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)

if let indexPath = getIndexPathForSelectedCell() {
highlightCell(indexPath, flag: false)
}
}
[/code]

Insert Cell

CollectionView provides insertItemsAtIndexPath method for adding new cell to CollectionView. Navigate to Main.storyboard, add new BarButtonItem to ViewController and set the Identifier as Add.

Add an IBAction to ViewController class and map it to the Add button. In the DataSource class add the following function which inserts new item to fruit model and returns the index.

[code language=”swift”]
// MARK:- Add Dummy Data
func addAndGetIndexForNewItem() -&gt; Int {

let fruit = Fruit(name: "SugarApple", group: "Morning")

let count = fruitsInGroup(0).count
let index = count &gt; 0 ? count – 1 : count
fruits.insert(fruit, atIndex: index)

return index
}
[/code]

Then modify the addNewItem IBAction method with the following piece of code.

[code language=”swift”] // MARK:- Add Cell
@IBAction func addNewItem(sender: AnyObject) {
let index = dataSource.addAndGetIndexForNewItem()
let indexPath = NSIndexPath(forItem: index, inSection: 0)
collectionView.insertItemsAtIndexPaths([indexPath])
}
[/code]

Delete Cell

Add Edit button to the navigation bar to allow users to perform delete operation. This can be done by adding the following line in viewDidLoad method.

[code language=”plain”]navigationItem.leftBarButtonItem = editButtonItem()[/code]

Then add a toolbar with button to delete the selected cell. Navigate to Main.storyboard, drag and drop toolbar on to View Controller. Add a BarButtonItem to the toolbar and select the identifier as Trash.

This toolbar should be displayed when the user taps on Edit button. Create an IBOutlet for the toolbar and add the following line to the viewDidLoad method.

[code language=”swift”]toolBar.hidden = true[/code]

Implement the setEditing function to enable or disable editing operation. In the below function, when editing is enabled, users will be allowed to select and delete multiple cells. The toolbar will be displayed or hidden based on editing flag.

[code language=”swift”]
// MARK:- Editing
override func setEditing(editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
collectionView?.allowsMultipleSelection = editing
toolBar.hidden = !editing
}
[/code]

Now hook up Trash button in the Interface builder to IBAction for performing the delete operation. Add the following code snippet to deleteCells IBAction method. This first retrieves the indexpath for all the selected items. Then iterates through the indexpaths and to get the list of fruits to be deleted from model.

[code language=”swift”]
@IBAction func deleteCells(sender: AnyObject) {

var deletedFruits:[Fruit] = []

let indexpaths = collectionView?.indexPathsForSelectedItems()

if let indexpaths = indexpaths {

for item in indexpaths {
let cell = collectionView!.cellForItemAtIndexPath(item as! NSIndexPath)

collectionView?.deselectItemAtIndexPath(item as? NSIndexPath, animated: true)
// fruits for section
let sectionfruits = dataSource.fruitsInGroup(item.section)
deletedFruits.append(sectionfruits[item.row])
}

dataSource.deleteItems(deletedFruits)

collectionView?.deleteItemsAtIndexPaths(indexpaths)
}
}
[/code]

 

And in the DataSource class, add a function to delete fruits from the model. Also add an extension to array to get the index of the object based on the selected item (This will not be needed in Swift 2.0).

[code language=”swift”]
// MARK:- Delete Items
func deleteItems(items: [Fruit]) {

for item in items {
// remove item
let index = fruits.indexOfObject(item)
if index != -1 {
fruits.removeAtIndex(index)
}
}
}
[/code]

We need to cancel the segue operation when the edit operation is enabled, you can do this by implementing the following method in ViewController class.

[code language=”swift”]
// MARK:- Should Perform Segue
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -&gt; Bool {
return !editing
}[/code]

Download the source code from here

Filed Under: ios, Swift, UICollectionView, Xcode Tagged With: Delete Cell, Insert Cell

Access Control in Swift

July 20, 2015 By Ravi Shankar Leave a Comment

Swift like other programming languages provides option to restrict access to classes, functions, variables, structs, enums etc applying the required Access Control. These restrictions are based on each module, as per Apple documentation a module is defined as

A module is a single unit of code distribution—a framework or application that is built and shipped as a single unit and that can be imported by another module with Swift’s import keyword.

Access Levels

There are three types of Access Control restriction that can be applied to individual types inside a module

public – The least restriction applied to a member and normally used when writing public interfaces

internal – Default access level and a member with this restriction can be accessed only within the module.

private – Most restricted access level and member with this restriction can be accessed only within the source file.

Check out more on Access Controls in Apple documentation Guiding Principles of Access Levels

Now let us see a demo on how these access levels can be used with in Swift projects or frameworks.

Access Control Demo

Create a project using Single View Application template (though this is going to be non-UI demo). Add a new swift file with name as Greetings.swift and following implementation.

[code language=”swift”]class Greetings {
func displayMessage() -> String {
return “Welcome !!!”
}
}
[/code]

The above class has a method named displayMessage that returns String. The access level for both Greetings class and the method is set to internal (default access level). Hence users will be able to access this class and function with in the module.

Let us replace viewDidLoad method in ViewController.swift with the following code snippet.

[code language=”swift”]
override func viewDidLoad() {
super.viewDidLoad()

let greetings = Greetings()
println(greetings.displayMessage())

}
[/code]

You will be able to access Greetings class as well as displayMessage() function. Now if you change the access level for displayMessage() to private then you should see an error message.

[code language=”swift”]
private func displayMessage() -> String {
return “Welcome !!!”
}[/code]

You can define a member type as private when it should be available within the source file (here it is Greetings class).

Add Second Module

Create a new framework within the AccessControlDemo project called RSModule and add new swift file with name as StringExtras and followinng implementation.

[code language=”swift”]public class StringExtras {
public static func makeFirstCharacterUpperCasse(word: String) -> String {
return word.capitalizedString
}
}[/code]

Note that the access level for class and function is set to public as we want to make these members available outside RSModule framework. Also the scope of the function is set to be static as we want to make class level funciton.

Add and import framework

Now to access StringExtras class inside Greetings.swift file, we need to Add and import RSModule to AccessControlDemo project. You can make RSModule available to this project by incuding this as part of the Target Dependcies. Click AccessControlDemo target, navigate to Build Phases and pick RSModule framework.

Navigate to Greetings.swift file, import the framework by adding import RSModule at the begining of the class and call the funciton in StringExtras class which capitalizes the first letter.

[code language=”swift”]
import RSModule

class Greetings {
func displayMessage() -> String {
return StringExtras.makeFirstCharacterUpperCasse(“welcome !!!”)
}
}[/code]

Download the source code from here.

Filed Under: ios, Programming, Xcode Tagged With: Access Control, Target Dependencies

How to customize status bar in iOS

July 19, 2015 By Ravi Shankar 1 Comment

Status Bar appears at the top of your device displaying information such as battery left in your device and carrier details. The default style of status bar is black and looks as shown in the below screenshot.

But if your screen designs are dark then you can change the status bar style to Light Content. This can be done by adding an entry as part of info.plist file or by adding the required code in AppDelegate. And if you want to change status bar style for specific View Controllers then you can override the function preferredStatusBarStyle

Add entry to info.plist

Navigate to info.plist under SupportFiles folder and add a new entry “View controller-based status bar appearance” with value as NO.

This would prevent developers from changing the status bar style for specifc View Controlllers. Now you can speciy the preferred style by selecting the project target and choosing the value for Status Bar Style drop down under Deployment Info

Add code to AppDelegate

Instead of changing the option in Deployment Info, you can also do this through code by adding the following line inside AppDelegate’s didFinishLaunchingWithOptions method. Here we are changing the style to LightContent.

[code language=”swift”]UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent[/code]

Customize style for specific ViewController

If you wish to apply the style for specific View Controllers then overiride the preferredStatusBarStyle function as shown below. This would work only if the entry “View controller-based status bar appearance” in info.plist file is not set NO

[code language=”swift”]override func preferredStatusBarStyle() -&gt; UIStatusBarStyle {
return UIStatusBarStyle.LightContent
}[/code]

Filed Under: ios, Programming, Status Bar, Xcode Tagged With: Style

  1. Pages:
  2. «
  3. 1
  4. 2
  5. 3
  6. 4
  7. 5
  8. 6
  9. 7
  10. 8
  11. 9
  12. 10
  13. »
« Previous Page
Next Page »

Primary Sidebar

TwitterLinkedin

Recent Posts

  • How to set preferred Wifi network on Mac
  • Attribute Unavailable: Estimated section warning before iOS 11.0
  • How to recover Firefox password from Time Machine backup
  • Show hidden files and folders on Mac
  • How to request a refund for apps in App Store

Pages

  • About
  • Privacy Policy
  • Terms and Conditions

Copyright 2022 © rshankar.com

Terms and Conditions - Privacy Policy