Search photos on Flickr – Web Service

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.


Comments

One response to “Search photos on Flickr – Web Service”

  1. I encounter this error while try to launch the app,

    The operation couldn’t be completed. (LaunchServicesError error 0.)

Leave a Reply to praveen Cancel reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.