# Lab 5 – Yelp Chat :::warning ☝️ **NO submission is required for labs** ::: ## Parse Chat In this lab you will build a chat client using [Parse](http://docs.parseplatform.org/ios/guide/) to explore the features of its ORM and backend service. We'll explore how to authenticate a user, design schema, and save and retrieve data from a Parse server. At the end of the exercise your app will look like this: ![Chat|250](https://imgur.com/XIdD7Bb.gif) ### Getting Started - The checkpoints below should be implemented using the Pair Programming method ## Milestone 1. Project Setup and Initialize Parse 👷‍ 1. Use the Yelp Project Starter. - If you want to build off your old project you will need to implement a Tab Bar, Login View, and a Chat View along with their associated files to use them. :::success **@[[assets/yelpy_starter_5.zip]]** ::: :::info The storyboard and files are already updated and ready for you to implement your code. ***Note:*** The files we will use are - AppDelegate - SceneDelegate - LoginVC - ChatVC ::: 1. In this lab, we'll be sharing a Parse Server that has already been created for us. To use this account, the `applicationId` and `server` are provided in the code snipped below: - In any Swift file that you're using Parse, add `import Parse` to the top of the file. - In the AppDelegate, register Parse in the `application:didFinishLaunchingWithOptions:` method: ```swift Parse.initialize(with: ParseClientConfiguration(block: { (configuration: ParseMutableClientConfiguration) in configuration.applicationId = "CodePath-Parse" configuration.server = "http://45.79.67.127:1337/parse" })) ``` ## Milestone 2: Login and Sign Up ✍️ 1. New user can tap "Sign Up" button to sign up :::warning Guides: - [Parse User](http://guides.codepath.org/ios/Building-Data-driven-Apps-with-Parse#parse-user-pfuser) - [User Registration](http://guides.codepath.org/ios/Building-Data-driven-Apps-with-Parse#user-registration) - [User Login](http://guides.codepath.org/ios/Building-Data-driven-Apps-with-Parse#user-login) - [Alert Controllers](https://guides.codepath.org/ios/Using-UIAlertController) ::: ### Sign Up In the sign up function we need to check to see that is the username and password is not empty. Once this is verified we need to store the user info to the `PFUDer()` by setting the username and password attributes. Next we can pass the user info to the `newUser.signUpInBackground` ![Sign Up](https://i.imgur.com/ZYGRKzB.png) ### Log In Existing user can tap "Login" button to login this function will follow a similar format to the sign up function. First we will set the user info and pass it in to `PFUser.logInWithUsername` User sees an alert if either username *or* password field is empty when trying to sign up - 💡 Use the `isEmpty` property of a text field's text to check if it's empty. Use the `||` operator to require one condition *or* the other. User sees an alert with error description if there is a problem during sign up or login ![Log In](https://i.imgur.com/zkKhnJe.png) ## Milestone 3: Display the chatView 🖼 Because we only want to display the login screen when the user first signs in and we dont want to display a back button on all the views we will go into the ```SceneDelegate.swift``` and control all the scenes. 1. Create a listener In the `scene` function lets add a listener for when the user logs-in and lets have it call a function called `login()` when we receive a notification ![ChatView](https://imgur.com/rXYdziX.png) <br><br> :::info We will also need a listener for when the user logs out ::: 1. Change the view After a successful sign up or login from the Login View, we need to change the view programmatically. <img src="https://imgur.com/EN79bWi.png"/> <br><br> ## Milestone 4: Send a Chat Message 💬 ![Compose Chat|250](http://i.imgur.com/RIyFAHQ.png)<br> The Chat Screen will allow the user to compose and send a message. 1. We will need an array to store our messages and a chat message object ```swift var messages: [PFObject] = [] ``` 1. When the user taps the "Send" button, create a new Message of type PFObject and save it to Parse - Use the class name: `Message` (this is case sensitive). ```swift let chatMessage = PFObject(className: "Message") ``` - Store the text of the text field in a key called `text`. (Provide a default empty string so message text is never `nil`) ```swift chatMessage["text"] = chatMessageField.text ?? "" ``` - Call `saveInBackground(block:)` and print when the message successfully saves or any errors. ```swift chatMessage.saveInBackground { (success, error) in if success { print("The message was saved!") } else if let error = error { print("Problem saving message: \(error.localizedDescription)") } } ``` - On successful message save, clear the text from the text chat field. ## Milestone 4: View a List of Chat Messages 1. Setup the a TableView to display the Chat Messages 1. Add a tableView to the Chat View Controller and a custom cell that will contain each message. - For now, the cell will only contain a UILabel (multi-line) for the message.<br> ![Table View Cell|200](http://i.imgur.com/G8zPlPS.png)<br> 1. Create a new file for the custom cell, "ChatCell" as a subclass of UITableViewCell and associate it with the Chat Cell in storyboard. 1. Set the "Reuse Identifier" to "ChatCell". 1. Create an outlet for the table view and set it's delegate property. 1. Declare the Chat View Controller to be a `UITableViewDataSource` and conform to the data source protocol by implementing the required methods. 2. Pull down all the messages from Parse: 1. Create a refresh function that is [run every second](http://guides.codepath.org/ios/Using-Timers). 1. [Query Parse](http://guides.codepath.org/ios/Building-Data-driven-Apps-with-Parse#fetching-data-from-parse-via-pfquery) for all messages using the `Message` class. 1. You can [sort the results](http://guides.codepath.org/ios/Building-Data-driven-Apps-with-Parse#query-constraints-filter-order-group-the-data) in descending order with the `createdAt` field. ```swift query.addDescendingOrder("createdAt") ``` 1. Once you have a successful response, save the resulting array, `[PFObject]` in a property (instance variable) of the Chat View Controller and reload the table view data. ## Milestone 5: Associating Users with Messages 1. When creating a new message, add a key called `user` and set it to `PFUser.current()` 1. Add a `username` label to the Chat cell to display the chat message author's username.<br> ![Username Label|250](http://i.imgur.com/7JyFSNa.png)<br> - Note: You will need to adjust the autolayout constraints and cell layout to accommodate the username label. Try removing the chatTextLabel's top constraint to the cell content view and then creating a new top constraint to the username label. You can then pin the username label's leading, top and trailing constraints to the cell's content view. 1. When querying for messages, add an additional query parameter, `includeKey(_:)` on the query to instruct Parse to [fetch the related user](http://docs.parseplatform.org/ios/guide/#relational-queries). ```swift query.includeKey("user") ``` 1. In cellForRow(atIndexPath:), if a chat message has the *user* property set, set the username label to the user's username. Otherwise ```swift if let user = chatMessage["user"] as? PFUser { // User found! update username label with username cell.usernameLabel.text = user.username } else { // No user found, set default username cell.usernameLabel.text = "🤖" } ``` ## Milestone 6: Persist Logged in User 1. On app launch, if current user is found in cache, user is taken directly to Chat Screen 1. In the SceneDelegate, check if there is a current logged in user in the scene function . - Parse automatically caches the current user on sign up or login. The current user can be accessed using, `PFUser.current()` ```swift if PFUser.current() != nil { login() } ``` 1. Programmatically load the Chat View Controller and set as root view controller. ```swift let storyboard = UIStoryboard(name: "Main", bundle: nil) let chatViewController = storyboard.instantiateViewController(withIdentifier: "ChatViewController") window?.rootViewController = chatViewController ``` :::success 🎉 Congrats! 🥳 You have finished all the required user stories. ::: ## Optional Stories 1. Create a Setting page where a user can logout 2. User sees an activity indicator while waiting for authentication. 3. User can pull to refresh Chat feed 4. Add an "Adorable Avatar" for each user by requesting an avatar from the [Adorable Avatars API](https://github.com/adorableio/avatars-api). - Pass in the username in the url - Install the [AlamofireImage](https://github.com/Alamofire/AlamofireImage#cocoapods) Pod and use the `af_setImage(withURL:)` UIImageView instance method to fetch and set the image at the specified url. 5. Chat Bubble Style Design - Remove table view row outlines in viewDidLoad() ```swift tableView.separatorStyle = .none ``` - Add a view to serve as your speech bubble. Move the label as a subview of the bubble view and re-do autolayout constraints - Set desired color (In code or IB) - Configure rounded edges in code. ```swift bubbleView.layer.cornerRadius = 16 bubbleView.clipsToBounds = true ``` ![Speech Bubble|200](http://i.imgur.com/AhzpOl9.png)<br>