In this codelab, you'll learn how to use the Firebase platform to easily create iOS applications. You will implement a chat client, and be able to monitor its performance using Firebase.

This codelab is also available in Objective-C.

What you'll learn

What you'll need

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would rate your experience with building iOS apps?

Novice Intermediate Proficient

Clone the GitHub repository from the command line.

Then clone the repo with:

$ git clone https://github.com/firebase/friendlychat

To build the starter app:

  1. In a terminal window, navigate to the ios-starter/swift-starter directory from your sample code download
  2. Run pod update
  3. Open the FriendlyChatSwift.xcworkspace file to open the project in Xcode.
  4. Click the Run button.

You should see the Friendly Chat home screen appear after a few seconds. The UI should appear however at this point you cannot send or receive messages.

Create project

From Firebase home page click Console then click on Create New Project.

Call the project FriendlyChat, then click on Create Project.

Connect your iOS app

From the welcome screen of your new project, click Add Firebase to your iOS app.

Enter any bundle ID, e.g. "com.google.firebase.codelab.FriendlyChatSwift".

Enter the Appstore id as "123456".

Click Continue for all the steps of the initial configuration (you don't need to copy the initialization code), then finally click Finish.

Add GoogleService-Info.plist file to your app

After adding the bundle id and App store id and clicking Continue your browser will automatically download a configuration file that contains all the necessary Firebase metadata for your app. Copy that file to your application and add it to the FriendlyChatSwift target.

Import Firebase module

Start by making sure the Firebase module is imported.

AppDelegate.swift

import Firebase

FCViewController.swift

import Firebase

Configure Firebase in AppDelegate

AppDelegate.swift

Use the "configure" method in FIRApp to configure underlying Firebase services from your .plist file.

  func application(_ application: UIApplication, didFinishLaunchingWithOptions
      launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  FIRApp.configure()
  return true
}

Use Rules To Restrict To Authenticated Users

We will now add a rule to require authentication before reading or writing any messages. To do this we add the following rules to our messages data object. From within the Database section of Firebase console select the RULES tab. Then update the rules so they look like this:

{
  "rules": {
        "messages": {
            ".read": "auth != null",
            ".write": "auth != null"
        }
  }
}

The auth rule variable is a special variable containing information about the user if authenticated. More information can be found in the documentation.

Add Email/Password Sign In via Firebase Authentication

Firebase Authentication allows you to easily sign in users with a wide variety of federated auth providers, Eg: Google, Facebook, as well as email and password.

Before your application can access the Firebase Authentication APIs on behalf of your users, you will have to do enable it:

  1. Go to the Firebase console and open your project.
  1. Open the Authentication page and click the "Sign-in Method" tab. Then enable the Email/Password authentication provider.
  2. Press save.

Confirm Firebase Auth dependencies exist in the Podfile file.

Podfile

pod 'Firebase/Auth'

Add methods to SignUp a user, set display name, Sign In with email/password, remember a signed in user and request a password reset.

SignInViewController.swift

  override func viewDidAppear(_ animated: Bool) {
    if let user = FIRAuth.auth()?.currentUser {
      self.signedIn(user)
    }
  }

  @IBAction func didTapSignIn(_ sender: AnyObject) {
    // Sign In with credentials.
    guard let email = emailField.text, let password = passwordField.text else { return }
    FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
      if let error = error {
        print(error.localizedDescription)
        return
      }
      self.signedIn(user!)
    }
  }
  @IBAction func didTapSignUp(_ sender: AnyObject) {
    guard let email = emailField.text, let password = passwordField.text else { return }
    FIRAuth.auth()?.createUser(withEmail: email, password: password) { (user, error) in
      if let error = error {
        print(error.localizedDescription)
        return
      }
      self.setDisplayName(user!)
    }
  }

  func setDisplayName(_ user: FIRUser) {
    let changeRequest = user.profileChangeRequest()
    changeRequest.displayName = user.email!.components(separatedBy: "@")[0]
    changeRequest.commitChanges(){ (error) in
      if let error = error {
        print(error.localizedDescription)
        return
      }
      self.signedIn(FIRAuth.auth()?.currentUser)
    }
  }

  @IBAction func didRequestPasswordReset(_ sender: AnyObject) {
    let prompt = UIAlertController.init(title: nil, message: "Email:", preferredStyle: .alert)
    let okAction = UIAlertAction.init(title: "OK", style: .default) { (action) in
      let userInput = prompt.textFields![0].text
      if (userInput!.isEmpty) {
        return
      }
      FIRAuth.auth()?.sendPasswordReset(withEmail: userInput!) { (error) in
        if let error = error {
          print(error.localizedDescription)
          return
        }
      }
    }
    prompt.addTextField(configurationHandler: nil)
    prompt.addAction(okAction)
    present(prompt, animated: true, completion: nil);
  }

  func signedIn(_ user: FIRUser?) {
    MeasurementHelper.sendLoginEvent()

    AppState.sharedInstance.displayName = user?.displayName ?? user?.email
    AppState.sharedInstance.photoURL = user?.photoURL
    AppState.sharedInstance.signedIn = true
    let notificationName = Notification.Name(rawValue: Constants.NotificationKeys.SignedIn)
    NotificationCenter.default.post(name: notificationName, object: nil, userInfo: nil)
    performSegue(withIdentifier: Constants.Segues.SignInToFp, sender: nil)
  }

Sign Out

Add the Sign out method

FCViewController.swift

  @IBAction func signOut(_ sender: UIButton) {
    let firebaseAuth = FIRAuth.auth()
    do {
      try firebaseAuth?.signOut()
      AppState.sharedInstance.signedIn = false
      dismiss(animated: true, completion: nil)
    } catch let signOutError as NSError {
      print ("Error signing out: \(signOutError.localizedDescription)")
    }
  }

Test Reading Messages as Signed In User

  1. Click the Run button.
  2. Create a new user by entering a username and password and clicking the "Create Account" link

Import Messages

In your project in Firebase console select the Database item on the left navigation bar. In the overflow menu of the Database select Import JSON. Browse to the initial_messages.json file in the firebase-codelabs directory, select it then click the Import button. This will replace any data currently in your database. You could also edit the database directly, using the green + and red x to add and remove items.

After importing your database should look like this:

Confirm Firebase Database Dependency

In the dependencies block of the Podfile file, confirm that Firebase/Database is included.

Podfile

pod 'Firebase/Database'

Synchronize Existing Messages

Add code that synchronizes newly added messages to the app UI.

The code you add in this section will:

Modify your FCViewController's "deinit", "configureDatabase", and "tableView" methods, replace with the code defined below:

FCViewController.swift

  deinit {
    self.ref.child("messages").removeObserverWithHandle(_refHandle)
  }


  func configureDatabase() {
    ref = FIRDatabase.database().reference()
    // Listen for new messages in the Firebase database
    _refHandle = self.ref.child("messages").observe(.childAdded, with: { [weak self] (snapshot) -> Void in
      guard let strongSelf = self else { return }
      strongSelf.messages.append(snapshot)
      strongSelf.clientTable.insertRows(at: [IndexPath(row: strongSelf.messages.count-1, section: 0)], with: .automatic)
    })
  }


  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Dequeue cell
    let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
    // Unpack message from Firebase DataSnapshot
    let messageSnapshot: FIRDataSnapshot! = self.messages[indexPath.row]
    let message = messageSnapshot.value as! Dictionary<String, String>
    let name = message[Constants.MessageFields.name] as String!
    let text = message[Constants.MessageFields.text] as String!
    cell.textLabel?.text = name! + ": " + text!
    cell.imageView?.image = UIImage(named: "ic_account_circle")
    if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL), let data = try? Data(contentsOf: URL) {
      cell.imageView?.image = UIImage(data: data)
    }
    return cell
  }

Test Message Sync

  1. Click the Run button.
  2. Click the Sign in to get started button to go to the messages window.
  3. Add new messages directly in Firebase console and confirm that they show up in the Friendly-Chat UI.

  1. Can confirm that they show up in the Friendly-Chat UI.

Implement Send Message

Send anonymous messages. Push value to the database, when you use the push method to add data to Firebase Realtime Database an automatic ID will be added, these auto generated IDs are sequential which ensures that new messages will be added in the correct order.

Modify your FCViewController's "sendMessage" method, replace with the code defined below:

FCViewController.swift

  func sendMessage(withData data: [String: String]) {
    var mdata = data
    mdata[Constants.MessageFields.name] = AppState.sharedInstance.displayName
    if let photoURL = AppState.sharedInstance.photoURL {
      mdata[Constants.MessageFields.photoURL] = photoURL.absoluteString
    }
    // Push data to Firebase Database
    self.ref.child("messages").childByAutoId().setValue(mdata)
  }

Test Sending Messages

  1. Click the Run button.
  2. Click Sign In to go to the messages window.
  3. Type out a message and hit send, the new message should be visible in the app UI and in the Firebase console.

Confirm Firebase Storage Dependency

In the dependencies block of the Podfile file, confirm the Firebase/Storage is included.

Podfile

pod 'Firebase/Storage'

Configure FirebaseStorage

FCViewController.swift

  func configureStorage() {
    let storageUrl = FIRApp.defaultApp()?.options.storageBucket
    storageRef = FIRStorage.storage().reference(forURL: "gs://" + storageUrl!)
  }

You can get the name of your storage bucket from the Storage section of the Firebase Console.

Receive images in existing messages

Add code that downloads images from Firebase Storage.

Modify your FCViewController's "tableView" method, replace with the code defined below:

FCViewController.swift

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Dequeue cell
    let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
    // Unpack message from Firebase DataSnapshot
    let messageSnapshot: FIRDataSnapshot! = self.messages[indexPath.row]
    let message = messageSnapshot.value as! Dictionary<String, String>
    let name = message[Constants.MessageFields.name] as String!
    if let imageURL = message[Constants.MessageFields.imageURL] {
      if imageURL.hasPrefix("gs://") {
        FIRStorage.storage().reference(forURL: imageURL).data(withMaxSize: INT64_MAX){ (data, error) in
          if let error = error {
            print("Error downloading: \(error)")
            return
          }
          cell.imageView?.image = UIImage.init(data: data!)
        }
      } else if let URL = URL(string: imageURL), let data = try? Data(contentsOf: URL) {
        cell.imageView?.image = UIImage.init(data: data)
      }
      cell.textLabel?.text = "sent by: \(name)"
    } else {
      let text = message[Constants.MessageFields.text] as String!
      cell.textLabel?.text = name! + ": " + text!
      cell.imageView?.image = UIImage(named: "ic_account_circle")
      if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL), let data = try? Data(contentsOf: URL) {
        cell.imageView?.image = UIImage(data: data)
      }
    }
    return cell
  }

Implement Store and send Images

Upload the image user picked, then sync this image's storage URL to database so this image is sent inside the message.

Modify your FCViewController's "imagePickerController didFinishPickingMediaWithInfo" method; replace with the code defined below:

FCViewController.swift

  func imagePickerController(_ picker: UIImagePickerController,
    didFinishPickingMediaWithInfo info: [String : Any]) {
      picker.dismiss(animated: true, completion:nil)
    guard let uid = FIRAuth.auth()?.currentUser?.uid else { return }

    // if it's a photo from the library, not an image from the camera
    if #available(iOS 8.0, *), let referenceURL = info[UIImagePickerControllerReferenceURL] {
      let assets = PHAsset.fetchAssets(withALAssetURLs: [referenceURL as! URL], options: nil)
      let asset = assets.firstObject
      asset?.requestContentEditingInput(with: nil, completionHandler: { [weak self] (contentEditingInput, info) in
        let imageFile = contentEditingInput?.fullSizeImageURL
        let filePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\((referenceURL as AnyObject).lastPathComponent!)"
        guard let strongSelf = self else { return }
        strongSelf.storageRef.child(filePath)
          .putFile(imageFile!, metadata: nil) { (metadata, error) in
            if let error = error {
              let nsError = error as NSError
              print("Error uploading: \(nsError.localizedDescription)")
              return
            }
            strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
          }
      })
    } else {
      guard let image = info[UIImagePickerControllerOriginalImage] as! UIImage? else { return }
      let imageData = UIImageJPEGRepresentation(image, 0.8)
      let imagePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
      let metadata = FIRStorageMetadata()
      metadata.contentType = "image/jpeg"
      self.storageRef.child(imagePath)
        .put(imageData!, metadata: metadata) { [weak self] (metadata, error) in
          if let error = error {
            print("Error uploading: \(error)")
            return
          }
          guard let strongSelf = self else { return }
          strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
      }
    }
  }

Test Sending and Receiving Image Messages

  1. Click the Run button.
  2. Click Sign In to go to the messages window.
  3. Click "add a photo" icon to pick a photo, the new message with the photo should be visible in the app UI and in the Firebase console.

Firebase Remote Config allows you to remotely configure your active clients. Friendly messages are restricted to a maximum length. While this length can be defined directly in the client. Defining this maximum length with Firebase Remote Config allows an update to the maximum length to be applied to active clients.

Add Config Rules in Firebase console

In Firebase console, select the "Remote Config" panel and click "Add your first parameter". Set the parameter key to friendly_msg_length and the parameter value to 10. Select Publish Changes to apply the updates.

Confirm Firebase RemoteConfig dependency

Confirm the pod 'Firebase/RemoteConfig' dependency exists in your Podfile file.

Configure Firebase Remote Config

FCViewController.swift

  func configureRemoteConfig() {
    remoteConfig = FIRRemoteConfig.remoteConfig()
    // Create Remote Config Setting to enable developer mode.
    // Fetching configs from the server is normally limited to 5 requests per hour.
    // Enabling developer mode allows many more requests to be made per hour, so developers
    // can test different config values during development.
    let remoteConfigSettings = FIRRemoteConfigSettings(developerModeEnabled: true)
    remoteConfig.configSettings = remoteConfigSettings!
  }

Request and Use Config

Create a fetch request for config and add a completion handler to pick up and use the config parameters.

FCViewController.swift

  func fetchConfig() {
    var expirationDuration: Double = 3600
    // If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
    // the server.
    if (self.remoteConfig.configSettings.isDeveloperModeEnabled) {
      expirationDuration = 0
    }

    // cacheExpirationSeconds is set to cacheExpiration here, indicating that any previously
    // fetched and cached config would be considered expired because it would have been fetched
    // more than cacheExpiration seconds ago. Thus the next fetch would go to the server unless
    // throttling is in progress. The default expiration duration is 43200 (12 hours).
    remoteConfig.fetch(withExpirationDuration: expirationDuration) { (status, error) in
      if (status == .success) {
        print("Config fetched!")
        self.remoteConfig.activateFetched()
        let friendlyMsgLength = self.remoteConfig["friendly_msg_length"]
        if (friendlyMsgLength.source != .static) {
          self.msglength = friendlyMsgLength.numberValue!
          print("Friendly msg length config: \(self.msglength)")
        }
      } else {
        print("Config not fetched")
        print("Error \(error)")
      }
    }
  }

Test Remote Config

  1. Click the Run button.
  2. Check that the Friendly Message character limit has been set to 10. Update the Remote Config value from 10 to 30, then publish. From the navigation bar tap fresh config and confirm that the Friendly Message character limit is now 30.

Firebase Analytics provides a way for you to understand the way users move through your application, where the succeed and where they get stuck and turn back. It can also be used to understand the most used parts of your application.

Initialize Measurement

Add measurement helper methods.

MeasurementHelper.swift

  static func sendLoginEvent() {
    FIRAnalytics.logEvent(withName: kFIREventLogin, parameters: nil)
  }

  static func sendLogoutEvent() {
    FIRAnalytics.logEvent(withName: "logout", parameters: nil)
  }

  static func sendMessageEvent() {
    FIRAnalytics.logEvent(withName: "message", parameters: nil)
  }

If you want to view this activity in your console, select Product ... Scheme... Edit Scheme in Xcode. In the Arguments Passed on Launch section, click the + to add a new argument and add add `-FIRAnalyticsDebugEnabled` as the new argument.

AdMob gives you a way to easily monetize the application, you simply add the AdView placeholder and Google handles the ad delivery for you.

Confirm the AdMob dependency

Confirm the pod 'Firebase/AdMob' dependency exists in your Podfile file.

Podfile

pod 'Firebase/AdMob'

Load Ad request

FCViewController.swift

  func loadAd() {
    self.banner.adUnitID = kBannerAdUnitID
    self.banner.rootViewController = self
    self.banner.load(GADRequest())
  }

Test AdMob

  1. Click the Run button.
  2. Verify that the Test Ad shows at the bottom of the screen.

Firebase Crash allows your application to report when crashes occur and log the events leading up to the crash.

Confirm Firebase Crash dependency

Confirm the pod 'Firebase/Crash' dependency exists in your Podfile file.

FCViewController.swift

  func logViewLoaded() {
    FIRCrashMessage("View loaded")
  }

Initiate crash

FCViewController.swift

  @IBAction func didPressCrash(_ sender: AnyObject) {
    FIRCrashMessage("Cause Crash button clicked")
    fatalError()
  }

Test Firebase Crash

  1. Click the Run button.
  2. Stop Xcode
  3. Run the app again without Xcode attached.
  4. Click the "Crash" button.
  5. Verify that your app is crashed.
  6. Click the Run button again to run your app in Xcode
  7. After a moment, you should see the message "Firebase Crash Reporting: Crash successfully uploaded" in the console
  8. After a minute or two, visit the Crash section of the Firebase console. You should see your crash being logged
  9. Note that we did not do the work to upload the symbols file in this codelab. In a real situation, you would have a much more readable stack trace to help you find the error.

You have used Firebase to easily build a real-time chat application.

What we've covered

Next Steps

Learn More