Swift Collection View: Phase 8/9

So far, you learned how to implement these features in the SwiftCollectionView application:

Today you will learn how to implement these feature in the SwiftCollectionView application:

  • Display the number of selected cells in the navigation bar.
  • Share selected collection view cell’s photo with friends.

Take a look at this video. It shows how the app will function on a real iOS device; from the moment you tap the share button to the moment you tap the Mail Composer’s Send button.


Video 8-1: App running on an iPhone 4s device

The image below shows what you’ll see when you run the app on the iOS Simulator and tap the share button.

swiftcv-figure8-1

Figure 8-1: Output on the iPhone 4s Simulator

Before You Begin

Before you begin, I assume the following:

  • You created a new branch from the collection-view-plist branch and you called it share-photos.
  • You will use Xcode Version 6.1.1 to implement above features in the SwiftCollection project.
  • You’re a member of Apple iOS Developer Program. If you aren’t, then you won’t be able to test the mailing feature of this version of the SwiftCollectionView app.
  • In Xcode Version 6.1.1 you have to updated the lines that display errors in the  MasterViewController.swift file by adding an ! at the end of self.collectionView. Here’s how to fix the first one.

Change the statement on line 32 from this:

self.collectionView.backgroundColor = UIColor(patternImage: UIImage(named: "purty_wood")!)

to this:

self.collectionView!.backgroundColor = UIColor(patternImage: UIImage(named: "purty_wood")!)

For remaining lines that have an error, add the ! as shown above.

Create and Connect The Share Button in IB

Here are the steps to create the share button on the navigation bar.

  • Click the Main.storyboard file to load it in Interface Builder.
  • Drag a Bar Button from the Object Library and drop it on the left corner of the Photos scene’s navigation Bar.
  • In the Attributes inspector, select Action from the the Identifier menu.
  • Use the Assistant editor to create and connect an IBOutlet variable for the button. Call it shareButton.
  • Use the Assistant editor to create and connect an IBAction function for the shareButton; call it shareButtonTapped. You should create the function at the end of the MasterViewController.swift file.

Add Two rightBarButtons on The Nav Bar

You have to modify the Photos scene’s navigation Bar so it display two buttons on the right side of the navigation bar-see image below. Interface Builder allow you to add only one button on either end of the navigation bar. They are called leftBarButton and rightBarButton. To add two rightBarButtons on the navigation bar, you’ll have to add code in the MasterViewController.swift file’s viewDidLoad() function.

swiftcv-figure8-2

Start by deleting the Edit button from the Photos scene’s navigation bar. Also, delete text from the navigation’s Title field. Switch to the MasterViewController.swift file and delete the Edit button’s IBOutlet variable.

Here is the code to create and add the rightBarButtons on the photos scene’s navigation bar. Add the code in the MasterViewController.swift file’s viewDidLoad() function.

// Set two properties of the counterLabel object
counterLabel.textColor = UIColor(red:1.00, green:0.00, blue:0.50 ,alpha:1.00) // pink color
counterLabel.text = "0 photos selected"

//1. Create two bar Bar Button Items, connect only one of them to an IBAction function
photoCounterButton = UIBarButtonItem(customView: counterLabel)
editButton = UIBarButtonItem(title: "Edit", style: .Plain, target:self, action:"editButtonTapped:") 

//2. Add the buttons in the buttonArray variable
let buttonArray = [editButton, photoCounterButton] 

//3. Put the buttons on the Photos scene's nav bar
self.navigationItem.rightBarButtonItems = buttonArray

Remember; the photoCounterButton’s sole purpose is to display the number of collection view cells selected, so that’s why we did not connect it to an IBAction function. Later, you’ll add code in the MasterViewController.swift file to update the button’s counterLabel properties.

The last thing you have to do to complete the task of setting up the rightBarButtons on the Photos scene’s navigation bar is, add these variable declaration statement at the top of the MasterViewController.swift file.

var photoCounterButton: UIBarButtonItem!
var editButton: UIBarButtonItem!
var shareEnabled = false
var selectedCellItems = [String]()
var counterLabel = UILabel()

Run the application say, in the iPhone 4s simulator. You will only see the Share and edit button on the navigation bar. Later, when you add code to update the photoCounter button, the photoCounter button’s text show up on the Photos view’s navigation bar-see above image. The Edit button should work as before; however the share button doesn’t yet.

How The Share Button Will Work

Before I show you the share button’s code; I’ll explain how it will work. In a nutshell the share button’s job is to put the app in “edit mode” so you can select one more collection view cells’ photos and email them to one or more friends. The share button is connect to the shareButtonTapped() function. Code you’ll add in the function will perform several tasks. Here is a list of the most important ones:

  • Put the app in sharing mode.
  • Crate an object called mailComposer from the MFMailComposeViewController class.
  • Construct an email for the MailComposer view.
  • Display the mailComposer’s view on the device’s screen.
  • Wait for you to enter one or more email addresses in the To: field and to tap/click the send button.
  • Once you tap/click the mailComposer view’s Send button. The app respond by checking the device’s type. If its a real device; the app sends your email to the recipient’s email address. You will hear a swooshing sound-assuming your device’s speaker isn’t muted. If the device is an iOS Simulator, the app will display a message in an alert view.

A not about the last item in above list. If you try to send an email using an iOS Simulator, the app will crash. That’s because the iOS Simulator can’t send email; only a real iOS device can.

Configure The MasterViewController Class to Send Mail

When you tap the share button, code in the shareButtonTapped() function will pass control to an instance of the MFMailComposerViewController class. In code, we’ll call that instance mailComposer. Its job is to present its view on the device’s screen so you can send mail. Before you can use the MFMailComposerViewController class in the MasterViewController.swift file, you have to configure it to use the MFMailComposerViewController class.

Start by executing these steps, which import the MessageUI.framework in the project. The framework have three header files, one of them is the MFMailComposerViewController.h file.

  • Click the root project folder in the project navigator panel.
  • Click the Build Phases tab.
  • Expand the Link Binary With Libraries item. Click the + button.
  • Type mes in the pop up window’s search box.
  • Select the MessageUI.framework in the search results list, then click the Add button. Xcode add the framework in the project.

Now, you have to conform the MasterViewController.swift class file to the MFMailComposeViewControllerDelegate protocol. You do that by changing the class file’s header to this:

class MasterViewController: UICollectionViewController, MFMailComposeViewControllerDelegate {

Finally, you have to import the MessageUI.h file in the MasterViewController.swift file. You do that by adding this import statement in the MasterViewController.swift file.

import MessageUI

Ok, so you’ve configured the MasterViewController.swift class file to send mail. You are ready to add code in the shareButtonTapped() function.

Add Code in The shareButtonTapped() Function

Here is the first set of code to add in the shareButtonTapped() function. It check to see if the device is an iOS Simulator or not. If it is, then code in the if statement block is executed. If it isn’t, then only the print() statement is executed.

@IBAction func shareButtonTapped(sender: AnyObject) {
if deviceIsSimulator() {
  let alertView = UIAlertController(title: nil, message: "Your device can't send mail", preferredStyle: .Alert) 

  alertView.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
  presentViewController(alertView, animated: true, completion: nil)

  // This prevent code below from executing
  return
}

print("The device is not a simulator and shareEnabled = \(shareEnabled)")
}

Notice how the deviceIsSimulator() function is called in the if statement. Here is the code to implement the function. Add the code below the shareButtonTapped() function’s code.

// This function check to see if the current device is an iPhone or iPad Simulator.
// If so, then return the Boolean value true, otherwise return false
func deviceIsSimulator() -> Bool {
  var isSimulator = false
  if UIDevice.currentDevice().model == "iPhone Simulator" || UIDevice.currentDevice().model == "iPad Simulator" {
    isSimulator = true
  } 

  return isSimulator
}

Test The Share Button Code

Now is a good time to run the app in the iPhone 4s simulator to test the share button code. You should see output shown in Figure 8-1 above on the simulator’s screen, when you click the share button. Test the app on a real device, you’ll have to enroll in Apple’s iOS Developer Program. Assuming you’re a member of Apple’s iOS Developer Program, here’s how to test the app on your device.

Connect your real device to your Mac, hit the run button in Xcode. Wait for Xcode to launch the app on your device. Once the app is running on your device, tap the share button. You won’t see above alert view message on your device’s screen; however, you will see the print() function’s message in Xcode’s Debugger window. The reason for this is because the if statement evaluates to false and its code block isn’t executed. 

Add More Code in The shareButtonTapped() Function

Now, delete the print() function statement you added in the shareButtonTapped() function. Next, add this code in the function. Add it below the if statement block’s closing } bracket.

if shareEnabled == true {

} else { 

}

Ok, above code snippet is saying this, if the app is in sharing mode, do this. Otherwise, do that. When is the app put in sharing mode and taken out of sharing mode? The first time you tap the share button, the app is put in sharing mode. When you tap the share button again; the app is taken out out of sharing mode. Here is the code to add in above if statement block.

// Create a Mail Composer View Controller object
let mailComposer = MFMailComposeViewController()

 // Set the mailComposer delegate property
 mailComposer.mailComposeDelegate = self

 // Set the mailComposer subject property
 mailComposer.setSubject("Check out these photos")

 // Create a variable and assign it the HTML table header tag
 var emailBody = "<table style='border-collapse: collapse; border: 0px solid; cellpadding='3'><tbody><tr>"

 // This for loop set up rows for the HTML table
for var i = 0; i < selectedCellItems.count; i++ {
  var imageUrl = selectedCellItems[i]
  emailBody += "<td><img src='\(imageUrl)' width='75' height='75' /></td>"
  if i == 6 { emailBody += "</tr><tr>" }
  if i == 13 { emailBody += "</tr><tr>" }
  if i == 20 { emailBody += "</tr><tr>" }
  if i == 27 { emailBody += "</tr><tr>" }
  if i == 34 { emailBody += "</tr><tr>" }
  if i == 41 { emailBody += "</tr><tr>" }
  if i == 54 { emailBody += "</tr>" }
}

// Append the HTML table footer tags to the emailB
emailBody += "</tbody></table>"

// Set the mailComposer message boady and isHTML parameters
mailComposer.setMessageBody(emailBody, isHTML: true)

// Take the app out of sharing mode
shareEnabled = false

// Set the collection view to single cell selection mode
self.collectionView!.allowsMultipleSelection = false

// Empty out the selectedCellItems array
selectedCellItems.removeAll(keepCapacity: false)

// Update the counter label on the nav bar
updateCounterLabel()

// Show the mail composer view controller's view on the device's screen
self.presentViewController(mailComposer, animated: true, completion: nil)

// Refresh the collection view, so previously highlighted cells gets unhighlighted
self.collectionView!.reloadData()

Here is the code to add in the else block.

// Put the app in sharing mode
shareEnabled = true

// Empty out the selectedCellItems array
selectedCellItems.removeAll(keepCapacity: false)

// Allow the user to select multiple cells in the collection view
self.collectionView!.allowsMultipleSelection = true

// Refresh the collection view, so previously highlighted cells gets unhighlighted
self.collectionView!.reloadData()

Code you entered in the shareButtonTapped() function calls the updateCounterLabel(). Here’s the code to implement it.

// This function is called in the shareButtonTaped() function and the
// collection view's didSelectItemAtIndexPath() function
func updateCounterLabel() {
  // Update the counterLabel text property
  counterLabel.text = "\(selectedCellItems.count) photos selected"

  // Update the counterLabel's width so all text is shown
  counterLabel.sizeToFit()
}

Now, here’s the code to implement the delegate function of the MFMailComposeViewControllerDelegate protocol. Add it in the MasterViewController.swift file.

// This function is fired when the user tap the Send or Cancel button on the mail composer view's navigation bar
func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!) {
  // Close the mail composer view controller's view
  self.dismissViewControllerAnimated(true, completion: nil)
}

That’s it. You are done adding code in the shareButtonTapped() function. Take a look at the table below; its a recap of what code you entered in the shareButtonTapped() does.

Check to see if the device the app is running on is an iOS Simulator. If it is, then do the following:

  • Create and display an alert view on the simulator’s screen.
  • Don’t execute remaining code in the shareButtonTapped() function.

Check the boolean variable, shareEnabled; if it is equal to false, then do the following:

  • Put the app in sharing mode by setting the shareEnabled variable to true.
  • Set the collection view to multiple selection mode by setting its allowsMultipleSelection property to true.
  • Refresh the collection view, so previously highlighted cells are unhighlighted.

Check the boolean variable, shareEnabled; if it is equal to true, then do the follow:

  • Use the MFMailComposeViewController class to create a object called mailComposer.
  • Set the mailComposer’s subject property.
  • Create an object called emailBody and initialize it with HTML text.
  • In a for loop, append more HTML text to the emailBody variable.
  • Outside the for loop, append more HTML text to the emailBody variable.
  • Set the mailComposer’s body and isHTML properties via the mailComposer’s setMessageBody() function.
  • Take the app out of sharing mode by setting the shareEnabled variable to false.
  • Set the collection view to single selection mode by setting its allowsMultipleSelection property to false.
  • Empty out the selectedCellItems array variable.
  • Update properties of the navigation bar’s photoCounterButton by calling the updateCounterLabel() function.
  • Show the mail composer view controller’s view on the device’s screen.
  • Refresh the collection view, so previously highlighted cells gets unhighlighted.

Replace The didSelectItemAtIndexPath() Function’s Code

You have to replace the collection view didSelectItemAtIndexPath() function’s code with this:

override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
  // Put the selected collection view cell's photo url in a variable
  selectedPhotoUrl = cvDataSource[indexPath.row]

  if shareEnabled == true {
    // Add an object in the selectedCellItems array
    selectedCellItems.insert(selectedPhotoUrl, atIndex: 0)

    // Update the counter label on the nav bar
    updateCounterLabel()
  } else {
    // Pass control to the PhotoViewController
    self.performSegueWithIdentifier("photoDetail", sender:self)
  }
}

Replace The didDeselectItemAtIndexPath() Function’s Code

The last task you have to do is replace the collection view didDeselectItemAtIndexPath() function’s code with this:

override func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
  // Put the deselected cell's photo url in a variable
  var itemToDelete = cvDataSource[indexPath.item]

   // Typecast the selectedCellItems array to an Objective-C array
   var objectArray: NSArray = selectedCellItems as NSArray

   // Typecast the objectArray to an Objective-C NSMutableArray
   var mutableObjects = NSMutableArray(array: objectArray)

   if shareEnabled == true {
     // Delete an element from the NSMutableArray
     mutableObjects.removeObject(itemToDelete)

     // Empty out the selectedCellItems array
     selectedCellItems.removeAll(keepCapacity: false)

     // Use double-casting to convert the Objective-C NSMutableArray to a Swift Array,
     // before assigning it to the selectedCellItems array
     selectedCellItems = mutableObjects as NSArray as [String]

     // Update the counter label on the nav bar
     updateCounterLabel()

     /************** DEBUG CODE ****************
     println("\nDeleted item: \(itemToDelete)\n")
     println(finalMutableArray)
     ********************************************/
  }
}

Use the Source Control menu to commit changes made to the project.

Test The Share Button’s Code, Again

Run the app say, in the iPhone 4s simulator. You should see output shown in Figure 8-1 above, when you tap the navigation bar’s share button. If you have a real device, connect it to your Mac, and test the share button’s code. You should see output shown in the QuickTime video presented at the beginning of this workshop. Here’s what I did in the video

  • I tapped the share button once.
  • I tapped several collection view cells.
  • I tapped the share button again. This pop open the Mail Composer View Controller’s view over the Photos view.
  • I entered an email address in the To: field.
  • I tapped the Send button. The Mail Composer View Controller sent the email to the email address I entered in the To: field and close its view.

What’s Next?

That’s it, you’ve entered necessary code in the MasterViewController.swift file to display the number of selected cells in the navigation bar, and to share selected collection view cell’s photo with friends. Next, week you will learn how to do the following:

  • Display the collection view cells in multiple sections.
  • Display section headers and footers in the collection view.
  • Update the Delete button’s code to enable the app user to delete one or more cells from each section of the collection view.

Until then, happy coding! 🙂