iOS + Swift + Parse + UICollectionView + Uploading files — Part 1 of 2

Bizzi Bradbury
Swift Programming
Published in
13 min readJul 7, 2015

--

iOS 8.4+

Swift 1.2

Parse 1.7.4

Over the weeks and months I have had many people ask how they can use Parse with native UI components, how they can allow their users to upload images and other files to Parse, and, how to deal with users forgetting their passwords. I thought I’d wrap all these ideas and questions into a single tutorial and show how easy all of these features are to implement. However as I wrote and wrote and wrote I realised that the tutorial would be far too long. I tried cutting explanation out in order to shorten the tutorial but I was not happy with the result. So instead this is the first of 2 tutorials that I think will end my series on Parse.

Part 1 of 2

In part 1 we will tackle the iOS native collection view (UICollectionView). Yes of course Parse provide their own PFQueryCollectionViewController object that we could use just as we have the Parse table component in previous tutorials, but I wanted to show how easy it is to use Parse and the iOS native components. In part 2 we will work through how to deal with users forgetting or resetting their passwords and how to upload images and files to the Parse platform.

The app is based on our previous tutorials so if you’ve been following along you should feel right at home straight away.

I am worried that I might have attempted too much with this tutorial. I value your input and questions — please give your honest feedback at the end of the page.

Finally, don’t forget that I am all about Swift. My knowledge of ObjectiveC starts and stops with the word AAARRRGGGHHHH (if that is a word).

Like all my posts, there is a shopping list of links at the end of the article. No leaving this page until you’ve read it all!

Goal

Let’s take a look at the FULL finished app.

Part 1 will get us as far as displaying and searching our country data in a collection view and navigating to specific countries.

Strategy

We’re going to achieve our goal via 6 simple steps.

  • Download the starter project from GitHub
  • Update the story board
  • Design the collectionView cell prototype
  • Fill out the View Controller
  • Wire up the “To Detail View” segue
  • Implement search

Like other tutorials we’ll be stopping along the way to run our project and check on progress as we go.

Step 1 — Download and build/run

Over on GitHub you’ll find the starter project. Download this and make sure you can build and run the app.

The story board should look this this….

Starting storyboard

If you build and run the app at this stage the key features available are Sign Up, Sign In and Forgotten and Reset password. If you sign in you’ll be presented a white screen with the title Parse 7.

Step 2 — Update the story board

Right let’s update the story board. We need to remove the “PASRSE 7” label and drag on a search bar and a collection view.

Search Bar

Notice there are 2 types of Search Bar component, select “Search Bar”. Position the Search Bar directly below the navigation bar and configure the component constraints.

Collection View

Next up — the collection view, which we’ll add directly below the search bar. Notice that we are using a “Collection View” — not a “Collection View Controller”.

Don’t forget that I am going to use a NATIVE collection view — there is a Parse PFQueryCollectionViewController object that we could use just as we have the Parse table component in previous tutorials. This tutorial is about showing how you can easily use native controls and Parse.

Like the search bar — set the collection view constraints to that it fills the view below the search bar.

The default background colour of collection views is black. So let’s change that to white.

Notice how the collection view has a small box in the top left corner? This is the “View Collection Cell”, just like in a table view we have prototype cells that are replicated and filled out with our data. You can specify the size of collection view cells within the story board, however the only real benefit of doing that is an increased size on screen to ease laying out the labels which will display data.

Select the cell and drag the bottom right corner until the cell is 200px x 200px. Later we will resize the cell dynamically to best fit the device display.

Before we get on and design the cell now is a good time to specify the space within the collection view and between cells.

Select the collection view and review the Size Inspector. Make the following configuration settings.

Step 3 — Design the collectionView cell prototype

Still within the story board. We need to layout the cell. To save time I am going to keep things simple, 1 x image and 1 x label.

Update the cell so that it looks something like this….. (or what ever you fancy)

Remember to create the appropriate constraints — seeing as this design is simple you’ll probably get away with using the suggested constraints feature.

The label requires a couple of extra configurations : Centred text. 2 lines. Word wrap.

The final step in creating and configuring our prototype cell is to create a class file and “wire” up the image and label to the class.

Create a new file in your project.

  • Type = iOS > Source > Swift
  • Name = CollectionViewCell

Ensure your new file contains ONLY the following.

Before we can wire up the image and label to the class file we need to tell the prototype cell that it’s class file is the file we just created.

With the cell selected ensure the Custom Class is set to CollectionViewCell.

With the cell selected ensure the Identifier is set to cell.

We can now wire up the image and label — switch to assistant editor mode and drag/connect the elements.

Step 4 — Fill out the View Controller

The starter project already has a View Controller file (ViewController.swift) and this is already wired up to the view within the story board.

Update the top of the view controller file like so.

Firstly, we are defining an array of PFObject’s that will store the country data that we fetch from Parse. Swift does not need to know what “type” of PFObject it needs to store, only that the data is of type PFObject.

Secondly, we’ve added code for the story board components — let’s complete the wiring up.

Switch to assistant editor mode and drag between the story board components and our code.

Wire up the collection view cell to the cell custom class.

Notice at this stage our code will probably cause a red alert — this is due to us missing the required methods that support the collection view protocol. We will add the missing methods shortly.

viewDidLoad()

Lets update the viewDidLoad() method in ViewController.swift.

There are 2 segments to this method.

Firstly, we’re configuring the delegate property of the search bar. This will allow us to tap into the search bar events later in our code.

Secondly, we’re adjusting the size of our collection view cell to suit a nice design. The calculation targets iPhones, but with a little work could easily be updated to cover iPads and various orientations. Let’s step through this segment of code.

Get the width of the device display, minus some space for margins and the like, and divide this value by 3.

let cellWidth = ((UIScreen.mainScreen().bounds.width) — 32–30 ) / 3

Get a reference to the collection view layout object. We will use this object to configure the cell size.

let cellLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout

Update the cellLayout object to our earlier calculated width and use the same value for cell height.

cellLayout.itemSize = CGSize(width: cellWidth, height: cellWidth)

loadCollectionViewData()

Let’s implement a method that fetches data from the Parse platform, taking into account any search criteria the user might have provided. We will store this data in the countries[ ] array.

Add this method to ViewController.swift

There is a lot going on here, although if you’ve been following the tutorial series you should recognise most of this. Let’s step through this code.

Create an object that will be used to query the Parse platform.

var query = PFQuery(className:”Countries”)

Check to see if there is any value in the search bar. If there is — then convert the value to lowercase and add a whereKey clause to the query.

(Remember Parse queries are case sensitive — we have a “searchText” column we use for searching)

if searchBar.text != “” { 
query.whereKey(“searchText”, containsString: searchBar.text.lowercaseString)
}

Send our query to the Parse platform with a block that is called when the results are received back.

query.findObjectsInBackgroundWithBlock { 
(objects: [AnyObject]?, error: NSError?) -> Void in

This block is only called when the results or empty results are returned from Parse.

If the error object is empty then clear any values from the countries array, telling Swift to reuse any memory previously assigned. If there are any country objects returned — then convert these into our countries array. Then call the reloadData() method on our collection view so that the search results are displayed.

if error == nil {   // Clear existing country data
countries.removeAll(keepCapacity: true)
// Add country objects to our array
if let objects = objects as? [PFObject] {
countries = Array(objects.generate())
}
// reload our data into the collection view
self.collectionView.reloadData()
}

At this point if we build and run the app there is nothing new to see, so let’s keep going a bit more before we run out next test.

We have a collection view embedded in our story board scene and we have wired the collection view up to an IBOutlet in our ViewController.swift file. We must designate the dataSource and delegate for the collectionView — and — we need to provide the methods required by the collection view protocol.

Collection View dataSource and delegate

Switch to the story board. Select the collection view component. Select the Connections Inspector.

Now drag and connect the dataSource and delegate objects to the view controller.

Okay. Now we can complete the collection view protocol required methods.

numberOfSectionsInCollectionView()

Add the following method to ViewController.swift.

You should be very comfortable with this method. In this case the return value is always 1 as we have a very simple set of data. But imagine if we allowed grouping of the data by continent, then we might change this to 6 — or a count of unique continents in our data set.

numberOfItemsInSection

Add the following method to ViewController.swift.

As before, you should understand the purpose and return value for this method. We are telling the collection view how many cells the view should contain — which is equal to the number of countries in our array. If our collection view had sections within (following the continents idea) then we would need to return the number of cells for “this” section.

cellForItemAtIndexPath

Add the following method to ViewController.swift.

This is the key method that injects the right data into our collection view cells.

There’s a lot happening here so let’s step through it.

Create a reference to the “cell” object.

let cell = collectionView.dequeueReusableCellWithReuseIdentifier(“cell”, forIndexPath: indexPath) as! CollectionViewCell

Taking the target country from our countries array — if there is a value in the “nameEnglish” property then set the cell label called cellTitle to display the found value.

if let value = countries[indexPath.row][“nameEnglish”] as? String { 
cell.cellTitle.text = value
}

Create an Image object and store the question image (from the app assets). Place our question image in the target cell UIImage component.

var initialThumbnail = UIImage(named: “question”) 
cell.cellImage.image = initialThumbnail

First we check to see if the target country has a value in the flag property. If a value exists then, in the background, we request the file from Parse. Once the file is received from Parse we switch out the question image with the new flag image. Because we make this call in the background the collection view is displayed quickly and not held up while we request flag images. On a “normal” connection the flags will download and appear within a couple of seconds. Of course if there is no value in the flag property then the collection view cell is already displaying the question image.

if let value = countries[indexPath.row][“flag”] as? PFFile {

let finalImage = countries[indexPath.row][“flag”] as? PFFile
finalImage!.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
cell.cellImage.image = UIImage(data:imageData)
}
}
}
}

We are nearly ready to make another test of the app — if you run the app now the collection view will be empty. We just need to add the following method to ViewController.swift.

This method calls the loadCollectionViewData() method whenever the collection view is displayed. So when the collection view is displayed the app fetches fresh data.

Right. Let’s test the app so far. Build and run your app. You should see something like this…..

Progress test — Step 4

Right now we can view data from Parse. But we are unable to search or navigate to the detail view for a country. Let’s sort those things out….

Step 5 — Wire up the “To Detail View” segue

Firstly let’s deal with the segue.

Add this method to ViewController.swift.

The method itself is very simple. When a user clicks/selects a cell in our collection view the didSelectItemAtIndexPath method is called and the selected cell is sent to the prepareForSegue() method — talking of which, we’d better add that too!

Now add this method to ViewController.swift.

There is some logic going on in this method — let’s walk the code.

Create an optional object called currentObject this will hold the object we will send to the destination scene. If the sender object holds a country then we need to keep this for the next step. If the sender is empty, then it is likely a future ADD button must have been clicked — so we create a new empty country ready for the next step.

var currentObject : PFObject? if let country = sender as? PFObject {   currentObject = sender as? PFObject  } else {

// No cell selected in collectionView — must be a new country
record being created

currentObject = PFObject(className:”Countries”)
}

Get a reference to the destination story board scene we will be segueing to. We set the destination view controller’s currentObject object to hold the currentObject that we have just processed in the previous block.

iOS automatically performs the segue at the end of the prepareForSegue() method — we don’t need to do anything more to make the segue happen.

var detailScene = segue.destinationViewController as! DetailViewControllerdetailScene.currentObject = (currentObject)

Right. Let’s run another test.

Progress test — Step 5

Next we’ll fix the search.

Step 6 — Implement Search

All of the Search elements have been implemented in the story board, and we have already denoted that ViewController.swift is the delegate for the search bar. So all we need to do now is do things like manage the search cancel button and what to do when a user finishes entering text into the search bar.

The delegate declaration earlier in the ViewController.swift > viewDidLoad() method means that we can now tap into the Search Bar and define methods that will be called when our chosen events fire.

There are some configurations we can chose to make to the Search Bar. With usability in mind I recommend the following :

Search Bar configurations
  • Shows Search Results Button — when entering search criteria, if the user clicks out of the search bar then the keyboard will dismiss. The Search Results Button is a small button in the search bar that commits the search directly, saving the user from editing the text and clicking the keyboard Search key.
  • Shows Cancel Button — allows the user to cancel searching.
  • Capitalization — ensures the search criteria is as the user enters it. Although our search coverts everything to lowercase, there may be times where searching is case sensitive, I think it’s a good idea to practice good practice all of the time.
  • Correction — ensures the search criteria is as the user enters it. This stops iOS from adjusting and correcting the text entered. e.g. iOS will attempt to correct ‘bizzi’ to ‘buzzing’ — very annoying.

Add the following method to ViewController.swift.

It’s a very simple method that is fired when users clicks the Search key. The method dismisses the keyboard and then calls the loadCollectionViewData method. Bingo — we get an updated collection view.

We ‘ll repeat this type of method for a couple of other events.

Add the following 2 methods to ViewController.swift.

I’m sure you’ll understand these methods. The first is called when a user clicks the Search button. The second is called when a user clicks the search Cancel button.

Right. Lets have another test!

You should see something very much like this.

Progress test — Step 6

And that’s it! That’s part 1 done. Give yourself a pat on the back and get ready for Part 2.

Links

Parse
Parse Xcode framework download page
GitHub source for starter project
GitHub source for completed project
Original tutorial blog post

Remember this is Part 1 of 2 — Come back soon now!

Originally published at bizzi-body.com/blog on June 27, 2015. Read all my articles first by visiting bizzi-body.com.

--

--