iOS Tutorial: Getting started with Flickr Search API

http://justmobiledev.com/wp-content/uploads/2017/11/flickr-tutorial-1.pnghttp://justmobiledev.com/wp-content/uploads/2017/11/flickr-tutorial-1.pnghttp://justmobiledev.com/wp-content/uploads/2017/11/flickr-tutorial-1.pnghttp://justmobiledev.com/wp-content/uploads/2017/11/flickr-tutorial-1.pnghttp://justmobiledev.com/wp-content/uploads/2017/11/flickr-tutorial-1.pnghttp://justmobiledev.com/wp-content/uploads/2017/11/flickr-tutorial-1.pnghttp://justmobiledev.com/wp-content/uploads/2017/11/flickr-tutorial-1.pnghttp://justmobiledev.com/wp-content/uploads/2017/11/flickr-tutorial-1.pnghttp://justmobiledev.com/wp-content/uploads/2017/11/flickr-tutorial-1.pngiOS Tutorial: Getting started with Flickr Search API

This post is a quick starter tutorial for getting started with Flickr Search API.
As prerequisite for the tutorial, you should have a recent version of XCode installed. You should have basic knowledge or XCode and Swift and should know how to create outlets and work in the Storyboard interface.

Sign up for a Flickr API Key

First, head over to the Flickr web site and sign up for an API key here.
While you’re on the Flickr web site you may want to check out the complete API documentation here.
You may want to also familiarize yourself with the Flickr Photos Search API documentation here.
After logging in, follow the steps to apply for an API Key.

First, select the ‘Non-Commercial’ option for the sake of this tutorial, unless you are planning to build a commercial app.

Next, add a description for your app and check the checkboxes at the bottom:

On the following screen, you should see your API key and your secret. Please store the credentials in a safe place.

Project Setup

Open XCode and create a new Single View application from the File / New / Project menu.

Add a project name and organization name on the next dialog:

ViewController Setup

Next, click on the LaunchScreen.storyboard in your left Project Navigator and add an ImageView, a Text Field, and a Button to the View.

Next, you want to create Outlets for the Image and Text Field. Open the Storyboard and Assistant editor, and click on the widgets and Control-drag over to the ViewController file. On the pop-up select ‘Outlet’ and create a name for each UI element:

Next, select the Search Button in the storyboard and click-drag it to the ViewController code. In the pop-up, select ‘Action’ as the type and label it as ‘searchButtonAction’.

You should now have created two Outlets for the ImageView and TextField and one Action for the Search Button.

Let’s add a helper function in the ViewController that will allow us to present an error alert whenever we need to:

1
2
3
4
5
6
func displayAlert(_ message: String)
{
let alert = UIAlertController(title: "Alert", message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Click", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}

Building the Search URL

For the purpose of building the search URL, let’s create several structs of Constants, since there are quite a few parameters.
Create a new swift file called ‘Constants.swift’ and create a new struct for the Flickr URL base parameters.

1
2
3
4
5
6
7
8
struct Constants {

struct FlickrURLParams {
static let APIScheme = "https"
static let APIHost = "api.flickr.com"
static let APIPath = "/services/rest"
}
}

Next, we want to create two more structs – one to hold the Flickr API query keys and one to hold the values.
Don’t forget to place your API key in the second struct below.

For an explanation of the purpose of each of the parameters, please refer to the Flickr Search API documentation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct FlickrAPIKeys {
static let SearchMethod = "method"
static let APIKey = "api_key"
static let Extras = "extras"
static let ResponseFormat = "format"
static let DisableJSONCallback = "nojsoncallback"
static let SafeSearch = "safe_search"
static let Text = "text"
}

struct FlickrAPIValues {
static let SearchMethod = "flickr.photos.search"
static let APIKey = "6018ce76bba90c3eff10d2f95093f634"
static let ResponseFormat = "json"
static let DisableJSONCallback = "1"
static let MediumURL = "url_m"
static let SafeSearch = "1"
}

Next, we need to create a function, that takes the search text from the Text Field and builds the Flickr API base url and adds all the needed query parameters. The output of this function is an URL we can use to perform the request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private func flickrURLFromParameters(searchString: String) -> URL {

// Build base URL
var components = URLComponents()
components.scheme = Constants.FlickrURLParams.APIScheme
components.host = Constants.FlickrURLParams.APIHost
components.path = Constants.FlickrURLParams.APIPath

// Build query string
components.queryItems = [URLQueryItem]()

// Query components
components.queryItems!.append(URLQueryItem(name: Constants.FlickrAPIKeys.APIKey, value: Constants.FlickrAPIValues.APIKey));
components.queryItems!.append(URLQueryItem(name: Constants.FlickrAPIKeys.SearchMethod, value: Constants.FlickrAPIValues.SearchMethod));
components.queryItems!.append(URLQueryItem(name: Constants.FlickrAPIKeys.ResponseFormat, value: Constants.FlickrAPIValues.ResponseFormat));
components.queryItems!.append(URLQueryItem(name: Constants.FlickrAPIKeys.Extras, value: Constants.FlickrAPIValues.MediumURL));
components.queryItems!.append(URLQueryItem(name: Constants.FlickrAPIKeys.SafeSearch, value: Constants.FlickrAPIValues.SafeSearch));
components.queryItems!.append(URLQueryItem(name: Constants.FlickrAPIKeys.DisableJSONCallback, value: Constants.FlickrAPIValues.DisableJSONCallback));
components.queryItems!.append(URLQueryItem(name: Constants.FlickrAPIKeys.Text, value: searchString));

return components.url!
}

Next, we create a function to send a http request to Flickr and parse the JSON result.
The Flickr Search API returns a JSON in the format below. You can use the ‘stat’ property to check the result of the API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
["stat": ok, "photos": {
page = 1;
pages = 11028;
perpage = 100;
photo = (
{
farm = 5;
"height_m" = 375;
id = 38490098461;
isfamily = 0;
isfriend = 0;
ispublic = 1;
owner = "130522166@N05";
secret = 0a7ff927e4;
server = 4577;
title = "dummy name";
"url_m" = "https://farm5.staticflickr.com/4577/38490098461_0a7ff927e4.jpg";
"width_m" = 500;
}
}
]

In the evaluation of the response, we first grab the ‘photos’ key from the JSON, and then get the ‘photo’ array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
private func performFlickrSearch(_ searchURL: URL) {

// Perform the request
let session = URLSession.shared
let request = URLRequest(url: searchURL)
let task = session.dataTask(with: request){
(data, response, error) in
if (error == nil)
{
// Check response code
let status = (response as! HTTPURLResponse).statusCode
if (status < 200 || status > 300)
{
self.displayAlert("Server returned an error")
return;
}

/* Check data returned? */
guard let data = data else {
self.displayAlert("No data was returned by the request!")
return
}

// Parse the data
let parsedResult: [String:AnyObject]!
do {
parsedResult = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:AnyObject]
} catch {
self.displayAlert("Could not parse the data as JSON: '\(data)'")
return
}
print("Result: \(parsedResult)")

// Check for "photos" key in our result
guard let photosDictionary = parsedResult["photos"] as? [String:AnyObject] else {
self.displayAlert("Key 'photos' not in \(parsedResult)")
return
}
print("Result: \(photosDictionary)")

/* GUARD: Is the "photo" key in photosDictionary? */
guard let photosArray = photosDictionary["photo"] as? [[String: AnyObject]] else {
self.displayAlert("Cannot find key 'photo' in \(photosDictionary)")
return
}

// Check number of photos
if photosArray.count == 0 {
self.displayAlert("No Photos Found. Search Again.")
return
} else {
// Get the first image
let photoDictionary = photosArray[0] as [String: AnyObject]

/* GUARD: Does our photo have a key for 'url_m'? */
guard let imageUrlString = photoDictionary["url_m"] as? String else {
self.displayAlert("Cannot find key 'url_m' in \(photoDictionary)")
return
}

// Fetch the image
self.fetchImage(imageUrlString);
}

}
else{
self.displayAlert((error?.localizedDescription)!)
}
}
task.resume()
}

Next, we create a function in the ViewController to fetch the image asynchronously. The response will be cast into a UIImage and then set in our image view on the main thread.

For simplicity, we are just going to grab the first image from the results, and display it in our image view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private func fetchImage(_ url: String) {

let imageURL = URL(string: url)

let task = URLSession.shared.dataTask(with: imageURL!) { (data, response, error) in
if error == nil {
let downloadImage = UIImage(data: data!)!

DispatchQueue.main.async(){
self.flickrImageView.image = downloadImage
}
}
}

task.resume()
}

Finally, we need to hook up our IBAction searchButton action up to call the methods to generate the Flickr URL and perform the search:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@IBAction func searchButtonAction(_ sender: Any) {

let searchText = searchTextField.text
if (searchText!.isEmpty)
{
displayAlert("Search text cannot be empty")
return;
}

let searchURL = flickrURLFromParameters(searchString: searchText!)
print("URL: \(searchURL)")

// Send the request
performFlickrSearch(searchURL)

}

And that’s it. You can find the complete solution on my GitHub page.

 

Author Description

justmobiledev

No comments yet.

Join the Conversation