Parse + Native UICollectionView + Uploading images — Part 2 of 2

iOS 8.4+
Swift 1.2
Parse 1.7.4

Welcome back to Part 2! of our 2 part series where we’re connecting the parse platform to a native Collection View. In this part we will add password forgotten and reset features, and, image upload capabilities. A bit of a mixed bag — but I wanted to get several reader requests fulfilled as quickly as possible.

Of course, don’t forget that I am all about Swift. My knowledge of ObjectiveC is hampered by a feeling of nausea when ever I hear the words — so there’s no ObjC here only Swift.

It is important to recognise that in this sample code there I am not validating data provided by users. Whenever you collect information from users you should always validate the information provided before you act or process the information provided — this is basic security 101 and I am assuming that you are aware/happy/comfortable/doing those things.

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 got us as far as displaying and searching our country data in a collection view and navigating to specific countries.

Part 2 delivers the ability to reset our user password and upload new flag images.

Strategy

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

  • Download the work in progress project from GitHub
  • Update the Sign Up / In storyboard scene
  • Update the forgottenPassword() method
  • Add “Upload flag” button to Detail View Controller
  • Add image selection to the Detail View Controller
  • Add an “Add” button to the collection view

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 work in progress project. Download this and make sure you can build and run the app.

Your app should have the following capabilities.

Step 2 — Update the Sign Up / In storyboard scene

Right. Let’s view the storyboard and drag on 2 buttons. Label these buttons Forgotten Password and Reset Password.

Don’t forget to give the buttons some constraints.

Okay. Now let’s wire up the first button to the SignUpIn view controller.

We’ll call the new method forgottenPassword.

Step 3 — Update the forgottenPassword() method

Before we update the forgotten password method let’s discuss the workflow behind this feature.

The Parse platform provides several methods specific to the PFUser class. We’re already using some of these methods in our app — PFUser.logInWithUsernameInBackground() and PFUser.signUpInBackgroundWithBlock().

In order to deal with situations when a user wishes to change their password or reset their password, say when they might have forgotten it, Parse provides the method PFUser.requestPasswordResetForEmail(). This method is used for both of our target scenarios — forget and reset.

The method sends an email to the user that contains a web link. the link directs the user to a web page where they can provide a new password.

To keep our app simple we will reuse the sign up/in email address field to capture the target user account.

It is important to recognise that in this sample code there I am not validating the email address. I am not checking that it is the right format for an email address. When ever you collect information from users you should always validate the information provided before you act on the information — this is basic security 101 and I am assuming that you are aware/happy/comfortable/doing those things.

A couple of things to note:

  • The password reset web pages can be customised
  • The user account is not locked or blocked during the reset process
  • The reset link is single use
  • The reset link is valid for some days (I have been testing to see how long the links last — definitely + 2 days)

In order to customise the email message and password reset web pages visit the Parse portal and select SETTINGS > EMAIL. (I’ll not explain how these settings work — you should be able to figure that out for yourself)

When we wired up the button Xcode auto inserted a stub method. Let’s update this method…. to this.

Now this all looks like a lot of code. But if you look through it you’ll realise most of it is preparing and displaying Alert messages.

Let’s step through the code.

Get the user’s emailAddress. Remember to keep things simple we’re reusing the Sign Up/In email field. We need to convert the email address to lowercase — remember Parse users accounts are case sensitive.

let targetEmailAddress = emailAddress.text.lowercaseString

If the email address is empty then create an Alert view controller with a message that the user needs to provide an email address — and display the Alert.

if targetEmailAddress == “” { 
let alertController = UIAlertController(
title: “Missing Email Address”,
message: “Please provide an email address so that we can process your password request.”,
preferredStyle: UIAlertControllerStyle.Alert
)
alertController.addAction(
UIAlertAction(
title: “Okay”,
style: UIAlertActionStyle.Default,
handler: nil
)
)
self.presentViewController(
alertController,
animated: true,
completion: nil
)
}

If the email address looks okay then call the Parse requestPasswordResetForEmail() method.

Remember what I said earlier about validating the email address. I am not checking that it is the right format for an email address. Whenever you collect information from users you should always always always validate the information provided before you act or process anything.

else {
// We have an email address — attempt to initiate a password reset request on the parse platform
// For security — We do not let the user know if the request to reset their password was successful or not
let didSendForgottenEmail = PFUser.requestPasswordResetForEmail(targetEmailAddress)

We do not wait for a response from Parse. We immediately present an alert view stating that an email has been sent.

// Create an alert — tell the user an email has been sent — maybe
let alertController = UIAlertController(
title: “Password Reset”,
message: “If an account with that email address exists — we have sent an email containing instructions on how to complete your request.”,
preferredStyle: UIAlertControllerStyle.Alert
)
alertController.addAction(
UIAlertAction(
title: “Okay”,
style: UIAlertActionStyle.Default,
handler: nil
)
)
self.presentViewController(
alertController,
animated: true,
completion: nil
)
}

At this point we have assumed that the user has provided a correct and valid email address. We have assumed that Parse will send an email to the user and we have assumed that the user will follow the instructions in the email.

Obviously you should/could provide more information to help the user. Maybe you could add text about waiting a few minutes, maybe prompt to check their spam folder or request the reset email again if they can’t find the email.

It’d also a good idea to record in a separate table how many times a user requests a password reset. You should consider recording information like : Time/Date, IP address, Email address. This sort of information could point to possible usability issues with your app. It could even point to attacks.

Just ensure that in trying to help your users you do not provide information to would be hackers that might alert them that a user exists.

Excellent. Time for a test.

The final step is to wire the Reset Password button up to the same forgottenPassword() method. Parse does not differentiate between forgetting or resetting a password. So. Let’s drag a connection from the forgottenPassword() method to the storyboard button.

That’s it. Passwords all done. Finished.

Step 4 — Add “Upload flag” button to Detail View Controller

The Upload Flag feature workflow looks like this:

  • Request permission to access the device photo library — permission is only requested once, unless the user later resets their privacy permissions
  • Present the Image Picker user interface
  • User selects an image
  • Capture details of the selected image and append this meta data to the country record
  • Upload the selected image to Parse

Lets add the button — open the storyboard and highlight the detail view scene.

The type of button we need to add to the scene is a Button Bar Item. The Tab Bar already has a spacer — drop the Button Bar Item immediately after the spacer.

Update the button title to “Upload Flag”.

Excellent. Now we’re ready to write some code in the detail view controller.

Step 5 — Add image selection to the Detail View Controller

There are 2 key changes we need to make to the existing detailViewController.swift. Firstly we need to implement code to present and manage the Image Picker view. Secondly we need to update the existing saveButton() method so as to include the flag image data. Let’s get started.

Image Picker

The Image Picker View has a delegate protocol that we need to implement. Add the UIImagePickerControllerDelegate protocol to the class definition.

We need to define an object that will hold a reference to the Image Picker. Add the following lines to the top of the class.

var imagePicker = UIImagePickerController()
var imageDidChange = false

The top of the class definition should look like this.

In order to display the Image Picker View we need 2 methods. The first method is connected to the Upload Flag button and presents the Image Picker. The second method captures the selected image meta data.

CTRL + Drag to connect the Upload Flag button to the Detail View Controller.

Let’s call the action method uploadFlagImage.

Update this new method.

There is not much code here, but lets still step through it.

Check to see if the Image Picker has permissions to connect to the Photo Album.


if UIImagePickerController.isSourceTypeAvailable(
UIImagePickerControllerSourceType.SavedPhotosAlbum)
{

Set the delegate property of the Image Picker — this allows us to monitor events, like selecting an image. Disable the Image Picker image editing features. Point the Image Picker to the device Photo Library.

 imagePicker.delegate = self 
imagePicker.allowsEditing = false
imagePicker.sourceType = .PhotoLibrary

Display the image picker.

presentViewController(imagePicker, animated: true, completion: nil)

Right. Let’s test progress.

Notice how if the user does not grant access to the Photos library — then future requests to access the library cause an ugly error type permissions message. Also, notice how although we can select an image — the app does nothing with the selection. Lets fix that right now.

We need to add a method that is called when a user selects an image. Add this method to DetailViewController.swift.

This method is called when the user selects an image and comes courtesy of the delegate protocol. The code is really simple so you should understand what’s happening. But just in case…..

If an image was selected — place a reference to that image in the object pickedImage.

if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {

Set the UIImageView control (named flag) to hold the selected image. Scale the image to fit constraints.

flag.image = pickedImage
flag.contentMode = .ScaleAspectFit

Set a flag so that we know the image has changed when it comes to saving the Country record back to Parse.

imageDidChange = true

Close the Image Picker control.

dismissViewControllerAnimated(true, completion: nil)

So at this point the image has been captured and placed into the UIImageView control. It has not been saved back to Parse, so if the device were to crash or loose power then the selection would likely be lost.

Save Button

Lets update the saveButton() method so that we correctly capture data changes.

Replace the saveButton() method with the code below — remember to copy/paste/overwrite ONLY the method body otherwise the wiring up to the interface will be lost

The key changes to this method are that we’re looking to see if this is a new Country record, or if we are updating an existing Country record. The code also checks to see if the image has been changed, if true then the image is saved to Parse.

Let’s check over the code that deals with the image.

If the image has been changed then… Remember we have a flag that we only set if the user selects a new image. Without this check we would likely resend images to Parse each time the Country record was updated — even if no changes to the image were made.

// Upload any flag image 
if imageDidChange == true {

Take the flag image data and store in an object called imageData. Notice the method is keyed to the image being a PNG. There is a sister method called — UIImageJPEGRepresentation which can be used for those files.

let imageData = UIImagePNGRepresentation(flag.image)

Compute a file name for the flag image.

let fileName = nameEnglish.text + “.png”

Create a Parse PFFile object. Name the file using our previously created file name. Fill the file with the image data.

let imageFile = PFFile(name:fileName, data:imageData)

updateObject holds a reference to the target Parse object (we set it in code earlier in the method). Set the flag property of the target Parse object to our new PFFile object.

updateObject[“flag”] = imageFile

Later in the saveButton() method we call updateObject.save() — it is this call that sends the file to Parse.

That’s all there is to it. Saving files to Parse is very similar to saving other types of data to Parse. It’s all very simple, straight forward. Elegant even.

Step 6 — Add an “Add” button to the collection view

Let’s finish up by adding an “Add” button to the collection view, after all we need a way of creating new countries!

Switch to the storyboard and add a new Bar Button Item to the Collection View. Give the button a title of “Add”.

Now switch to assistant editor and drag/connect the button to ViewController.swift. Create an action method called addCountry().

Now we just need to update this method so that we get a new blank country when clicked. Update the addCountry() method.

The method simply calls the CollectionViewToDetailView segue. It’s as easy as that!

Well done!

And this is the functionality your app should now have.

Links


Originally published at bizzi-body.com/blog on July 23, 2015.