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(), { () -> 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) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photos.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> 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) -> 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.
I encounter this error while try to launch the app,
The operation couldn’t be completed. (LaunchServicesError error 0.)