• Skip to main content
  • Skip to primary sidebar

Ravi Shankar

Tweaking Apps

  • Swift
  • Tech Tips

Xcode

Swift – WebView demo

November 6, 2019 By Ravi Shankar 24 Comments

Updated for Swift 5

In this short tutorial, we will see an example in Swift programming language using UIWebView. The WebView will load a webpage and provide option to refresh, stop, go back and go forward. This tutorial should give a brief overview on how to declare IBAction, IBOutlets and use Objective-C classes (NSURL and NSURLRequest) in Swift


Interface Design

Step 1: Create a Single View Application Project and make sure to select the programming language as Swift

Choose Swift Language in Xcode

Step 2: Select Main.Storyboard under Project navigator. then drag and drop WebView and Toolbar from Object Library to the View Controller.

Step 3: Now place four bar button items on Toolbar to provide stop, refresh, go back and go forward functionality. You can also use flexible and fixed separators for aligning the bar button items.

Use Suggested Constraints in Xcode 6

Step 4: Make sure to use the SuggestedConstraints for WebView and Toolbar. You can do this by selecting the controls and using the Reset to Suggested Constraints available as part of Resolve Auto Layout option. This would ensure that the controls gets adjusted automatically depending upon the device’s screen width and height.

Reset to Suggested Constraints

Updated – 28/08/2014

Since some users are facing problems with Reset to Suggested Constraints in Xcode 6 Beta 6, you can use Pin option to define the four constraints using the default values. Then click Add 4 Constraints available at the bottom of the screen.

201408281213.jpg

Write Code

Step 5: Navigate to ViewController.swift file on the Project Navigator. Add the following line after class ViewController: UIViewController which defines the IBOutlet element for WebView.

@IBOutlet var webView: UIWebView!

Then connect this IBOutlet element to the WebView on InterfaceBuilder.

Step 6: In the viewDidLoad function, create URL and NSURLRequest for the webpage and associate this with the WebView by calling loadRequest method.

override func viewDidLoad() 
{   
  super.viewDidLoad() 
  let url = NSURL(string: "https://rshankar.com")
  let request = NSURLRequest(url: url! as URL)       
  webView.delegate = self
  activityIndicator.hidesWhenStopped = true
  activityIndicator.startAnimating()
  webView.loadRequest(request as URLRequest) 
}

Step 7: Now add the corresponding IBAction methods for the four buttons stop, refresh, go back and go forward. And connect these IBActions to the buttons on Interface builder.

func webViewDidFinishLoad(_ webView: UIWebView) 
{ 
      activityIndicator.stopAnimating()   
}                                                                                           

@IBAction func doRefresh(_: AnyObject) {                                                              
  webView.reload()
}

@IBAction func goBack(_: AnyObject) 
{   
   webView.goBack()    
}

@IBAction func goForward(_: AnyObject) {
  webView.goForward()
}

@IBAction func stop(_: AnyObject) 
{
   webView.stopLoading()
}

Step 8: Compile and run the project by selecting a suitable simulator.

Download the souce code from GitHub.

Filed Under: ios, Programming, Xcode Tagged With: Swift, WebView, Xcode

Assertions supported in XCTest

March 23, 2017 By Ravi Shankar Leave a Comment

Here you can find the list of Assertions supported by XCTest and it is essential to know all these assertion if you are practicing Test Driven Development in IOS. You can get this list from XCTestAssertions.h

  • XCTFail(<#format…#>) – This unconditionally fails the test.
  • XCTAssertNil(<#a1#>, <#format…#>) – Failure message when object is not nil.
  • XCTAssertNotNil(<#a1#>, <#format…#>) – Failure message when object is nil
  • XCTAssertEqual(<#a1#>, <#a2#>, <#format…#>) – Failure message when expressions(a1 & a2) are not equal.
  • XCTAssertNotEqual(<#a1#>, <#a2#>, <#format…#>) – Failure message when expressions(a1 & a2) are equal.
  • XCTAssertEqualObjects(<#a1#>, <#a2#>, <#format…#>) – Failure message when objects(a1 & a2) are not equal.
  • XCTAssertNotEqualObjects(<#a1#>, <#a2#>, <#format…#>) – Failure message when objects(a1 & a2) are not equal.
  • XCTAssertEqualWithAccuracy(<#a1#>, <#a2#>, <#accuracy#>, <#format…#>) – Failure message when a1 is not equal to a2 with + or – accuracy.
  • XCTAssertNotEqualWithAccuracy(<#a1#>, <#a2#>, <#accuracy#>, <#format…#>) – Failure message when a1 is equal to a2 with + or – accuracy.
  • XCTAssertNoThrow(<#expression#>, <#format…#>) – Failure message when expression does throw exception.
  • XCTAssertNoThrowSpecific(<#expression#>, <#specificException#>, <#format…#>) – Failure message when expression throws specific exception.
  • XCTAssertNoThrowSpecificNamed(<#expression#>, <#specificException#>, <#exception_name#>, <#format…#>) – Failure message when expression throws specific class with specific name.
  • XCTAssertThrows(<#expression#>, <#format…#>) – Failure message when expression does not throw exception.
  • XCTAssertThrowsSpecific(<#expression#>, <#specificException#>, <#format…#>) – Failure message when expression does not throw specific exception.
  • XCTAssert(<#expression#>, <#format…#>) – Failure message when expression is false.
  • XCTAssertTrue(<#expression#>, <#format…#>) – Failure message when expression is false.
  • XCTAssertFailure(<#expression#>, <#format…#>) – Failure message when expression is true.

Filed Under: Develop, ios, Swift, Xcode Tagged With: Assertions, Xcode, XCTest

Content Priority in Auto Layout

March 4, 2016 By Ravi Shankar 1 Comment

Auto Layout brings in lots of good features to ease the life of an iOS developer when designing User Interfaces. In this example, we will see how to use Content Priorities such as Content Hugging and Comrpession Resistance.

I have created a project with Single View Template and dispayed Size Classes as we are going to focus only on iPhone family.

Drag and drop two labels adjacent to each other.

Add Leading Space and Vertical Space constraints for Label A. Similarly add Vertical Space and Trailing Space constraints for Label B

 

Constraints for Label A

Constraints for Label B

Click the Update Frames option under the Resolve AutoLayout Issues menu. This should align the frames to reflect the constraints changes.

Change the background colour of Labels to Yellow and Green respectively. This would help us to identity the growth of each labels. Now add the Horizontal Spacing constraint between these two labels and set constant to 5.

Now Label B has grown and covered the space between both labels. Please note that this would be in random nature some times even Label A can fill up the space between these labels. And in this article, we will see how to control this behaviour. You will also notice the warning message that “2 views are horizontally ambitguous”. This is because Auto Layout does not know which label should grow and shrink in size.

Content Compression Resistance Priority

Let us change the text for Label in left hand side as “Content Hugging”. Simialrly enter the text for right hand side label as “Compression Resistance”. Then Update Frames to refelect the Intrinsic Content Size changes. You will notice that the Compression Resistance has a truncated trail.

If you want to this label to resist shrinking its size then set the Content Compression Resistance Priority (Under Size Inspector) to value higher than the label on left hand side.

After changing the value from 750 to 751, you will notice the warning messages “Frame will be different at run time”. Now Updating the Frames for All Views in Container shoud reflect as shown in the below screenshot.

Content Hugging Priority

Now Content Hugging label is partially hidden and it cannot grow horizontally as right hand side label has a higher compression resistance priority. Let us set the number of lines for this lable to 0 (which means it can grow based on the content size). This should again result in “Content Priority Ambiguity” error. This time we can fix this by telling Auto Layout that the label on left hand side has least resistance for growth. Select the label on left hand side, navigate to Size Inspector and set the Content Hugging Priority to lower than right hand side label i.e from 251 to 250.

Click Update Frames to resolve the warning message “Frame for Content Hugging will be different at run time”. The Content Hugging label in shown in two lines.

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

Filed Under: Auto Layout, Xcode Tagged With: Compression Resistance, Content Hugging

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

Auto Layout in iOS

July 25, 2015 By Ravi Shankar 5 Comments

Using Auto Layout feature developers can align UI controls for different devices, screen sizes and orientation. This reduces the code to be maintained and makes life easy for the developers. Auto Layout solves UI design issues by creating relationship between the UI elements. This follows constraint based layout system where you can define contraints for each UI elements. Let us see some basics of Auto Layout by looking at four different scenarios

The objective of this article is to provide details on the following

  • Different ways to add constraints to UI elements.
  • How to centre align a control to both vertically and horizantally to container.
  • Arrange three controls in single column with centre aligned horizontally to the container.
  • Design simple login form by embeding the controls inside view
  • Design a form where the width of controls gets adjusted based on device orientation.
  • Clear and Edit constraints.
  • Pinning constraint and Updating frames

Center Align image Vertical and Horizontal to the container

For this demo, let us start with a single view application project. Drag and drop an UIImageView from object libary on to View Controller. Copy an image to your project and set that image to UIImageView using Attributes Inspector (Download the image from GitHub project).

You might be familiar with Size Inspector under Xcode’s Utilies Pane. This helps us to enter the height, width, x and y position for any UI elements.

Another alternate way to enter the size and postion for elements by adding constraints using the Auto Layout option which is available at the bottom of the Interface builder.

Select the Click the Pin option, enter value for Width and Height as 300 then Add these 2 Constraints.

After applying the constraint, you should notice orange dashed line indicating that your control is out of position. Let us update the frame afer applying all the required constraints.

Click Align option (first option) and select Horizontal and Vertical Center in Container then Add these 2 Constraints.

If the ImageView on the ViewController is in misplaced position, the document outline will show an yellow color indication mark at top right hand corner. You can select that icon to find more details about the issue.

Document Outline

Document Outline in the Storyboard lists down the installed controls for a scene. Also you can the list of constraints added for these controls. If you do not need any constraint then you select it and delete them in Document Outline.

The missplaced views can be fixed by selecting Update Frames option under Resolve Auto Layout Issues (Auto Layout option with a triangle).

Preview User Interface changes

Now you can preview user interface changes on various device by using the Preview option. Click the icon for showing Assistant Editor window. Navigate to Preview and select Main.storyboard option as shown in the below screenshot.

You can use + sign to preview the screen in a specific device and also use rotate screen option to preview the changes in different screen orientation.

Arrange three Buttons in single column

In the next demo, we wil try to arrange three buttons aligned horizontally center to View and in a single column. Add a View Controller to the storyboard then drag drop three buttons on the View Controller. Though we have used the guidelines to align these buttons they don’t look the same when you preview them.

Now let us see how to fix these UI issues by adding constraints.

First select the Button with caption as Auto Layout 2 and make it Horizontal and Vertical Center in Container then Add these two constraints. This would make sure the Auto Layout 2 button is aligned center to the View both vertically and horizontally.

Select the button with caption as Auto Layout 1. Use the Aign option and add Horizontal Center in Container constraint.

Now you need to specify the Centre X and Verical Spacing constraint for this button. This can be done by control dragging from Auto Layout 1 button to Auto Layout 2 and you will be presented with the following option.

Select Vertical Spacing to position the control at constant distance from Auto Layout 2. Repeat the same control drag from Auto Layout 1 and Auto Layout 2 and this time choose Centre X.

Now repeat the above steps for Auto Layout 3 with Auto Layout 2 i.e add Centre X and Vertical Spacing constraints.

 

Scenario 3:- Adjust control width based on screen orientation

This scenario explains how to add constraints so that controls width increases or decreases based on the screen size. For example in the below screenshots, the width for textfields provided for entering name and Age gets adjusted based on screen orientation

Add a new View Controller on to Storyboard then add 2 labels and 2 textfields and a button. Provide the caption for both labels as Name, Age and Submit (button) respectively. We need to add constraints to labels so that their width and position are fixed at the specified location. And for both the textfields, constraints have to be added to trailing edge so that width increases or decreases based on the orientation

Add three constraints to the label with caption as Name using the pin option as shown in the below screenshot.

Repeat the same for label with caption as Age

Select the textfield adjacent to Name label, control drag from textfield to the container then select Trailing Space to Container Margin.

Need to align the textfield with the label, control drag from text field to name label and select Centre Y from the list of constraints.

Keeping the textField selected, navigate to Size Inspector (Utilities Pane) and cick Edit on Trailing Space constraint. Set the value for the constant as 10 as we want to maintain the space between textfield and container to 10 in both landscape and portrait mode.

Repeat the above steps done for Age textfields as well.

Finally we need to add two constraints for the Submit button to make sure it is aligned on the right hand side at certain distance from the Top Layout. Control drag from button to top of the container and select Top Space to Top Layout Guide.

Again repeat the above step by this time to side of the container and select Trailing Space to Container Margin.

Now prevewing the screen in Portait and Landscape mode should look as shown below.

Scenario 4:- Apply constraints to Embedded View

In this demo, let us see how to embed controls within a view and apply constraints to the embedded View instead of applying to the individual constraints.

Note :- Button has been incorrect named as Password it needs to be Register. The source code has been updated with this name change.

Add 2 textfields, 2 buttons and provide name and placeholder text as shown below.

Select these controls and embed them inside a View by navigating to Editor menu -> Embed -> View

Now change the background colour of the View and buttons to dark gray and orange.

Select the Embedded View, click Align option and make the View centre align to both Horizontal and Vertical to the Container.

Also add the width and height constraints for the Embedded View keeping the current value.

Now you might see a dashed orange colour line indicating the view is in misplaced position. You can fix this by selecting Update Frame under Resolve Auto Layout Issues option. Please make sure to select Update Frame option under Selected Views and not the container

Previewing the screen in both portrait and landscape should look as shown below

We have seen an introduction to Auto Layout using Storyboard. You can also add constraints via code using NSLayoutConstraint API or Visual Formatting language.

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

Download the source from GitHub

Filed Under: Auto Layout, Programming, Xcode

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. »
« Previous Page
Next Page »

Primary Sidebar

TwitterLinkedin

Recent Posts

  • How to block keywords in Jio broadband
  • How to disable opening an app automatically at login in Mac
  • 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

Pages

  • About
  • Privacy Policy
  • Terms and Conditions

Copyright 2022 © rshankar.com

Terms and Conditions - Privacy Policy