# Yelpy - Part 1
<img src="https://i.imgur.com/JL1snRo.gif" width=200 /></br>
### Overview
In this lab, you'll build the first part of an app that will allow users to view Yelp restaurants. You'll work in collaborative pairs--Pair Programming--to apply the skills you've learned so far building your Flix App Assignment. Just like the Flix App, you'll create a network request to fetch data from the web, however instead of movies, you'll be getting blog restaurant data from the Yelp API.
### Pair Programming - Working in Collaborative Pairs
The checkpoints below should be implemented as pairs. In pair programming, there are two roles: navigator and driver.
- **[How to Collaborate in Labs - Pair Programming Video](https://www.youtube.com/watch?v=9yCn03s5mzI):** Check out this short video before getting started with the lab.
- **Navigator:** Makes the decision on what step to do next. Their job is to describe the step using high level language ("Let's print out something when the user is scrolling"). They also have a browser open in case they need to do any research.
- **Driver:** is typing and their role is to translate the high level task into code ("Set the scroll view delegate, implement the didScroll method").
- After you finish each checkpoint, switch the Navigator and Driver roles. The person on the right will be the first Navigator.
### User Stories - What will our app do?
1. User can view and scroll through a list of Yelp restaurants.
## Let's Get Building
<details>
<summary style="font-size:1.25em; font-weight: 600">
Milestone 1: Setup Project + Storyboard
</summary>
#### 0. Download the Yelpy Starter Lab: @[[assets/yelpy_starter_1.zip]], and then double-click the `Yelpy.xcworkspace` file to open the project in Xcode.
#### 1. Add TableView to our `Main.storyboard`
- From the `+` objects library button, search and add the tableView to your storyboard:
- ==NOTE:== You can reference **steps 1-4** of the [Basic Table View Guide](http://guides.codepath.org/ios/Using-UITableView#basic-table-view)

- Connect your tableView outlet to your `RestaurantsViewController` file
- Open the your `RestaurantsViewController` file by holding the <kbd>alt/option</kbd> key and click on the file
- Hold the <kbd>control</kbd> key, then drag and drop the tableView to your View controller:

#### 2. Add a Cell to our tableView
- Add a `tableViewCell` from `+` objects library
- [**Reference:** Creating a Custom Cell Guide](http://guides.codepath.org/ios/Using-UITableView#step-1-create-the-custom-cell)
- Inside the cell, add a `label` and an `Image View` as well:

- Create `RestaurantCell` file under the `Cells` folder
- Configure cell's Class and Identifier on the 'Identity Inspector' + 'Attribute Inspector' to `RestaurantCell`:

- Create cell's label + image outlets on the `RestaurantCell` file:

</details>
<br>
<details>
<summary style="font-size:1.25em; font-weight: 600">
Milestone 2: Hook up the Yelp API
</summary>
On the lab starter, take a look at the `exampleAPI.json` file to get yourself familiarized with the JSON format of the response data from the API.
**Note:** For the purposes of this lab, we won't go into a lot of detail about the network request code because it's mainly a lot of repetitive configuration that--for the purposes of this course--won't ever change. Whats important for you to know are the next steps:
:::warning
:bulb: However, you can learn more about networks and APIs in our [course guides](https://guides.codepath.com/ios#networking-and-models) and in this [in-depth slide](https://docs.google.com/presentation/d/1XNZFyECpxZNzfCI-tXfGVxjaSRZQ3PmI9IKz0qJ2a_g/edit?usp=sharing) deck about API requests in Swift.
:::
#### 1. [Create a Yelp Account](https://www.yelp.com/developers/v3/manage_app) and generate an API Key
- Fill out the form to create a Yelp App to obtain an API Key. Once filled out you should your API key would look something like this:

- Once the API key is generated, your `Network` folder and copy/paste your API key on the `API.swift`.
- Next, lets add the logic to our API request:
- Under the "TODO" section, type this:

Here is the flow of the code:
- Traverse the data in JSON Format and convert it to a dictionary.
- From the dictionary, the "businesses" value is an array of businesses, so we convert it to an array of dictionaries that represent the data of each restaurant
- Return the array of dictionaries representing the restaurants
- "What happens in closures, stay in closures." In order to get the data inside the closure, we use the @escaping method using the variable `completion` to return it.
:::warning
**Closures** are basically functions within a function. Make sure to review the [**Swift fundamentals**](https://guides.codepath.com/ios/Understanding-Swift#closures) to understand them!
:bulb: Tip: If you are having trouble understanding it at first glance, don't worry! No one really understands code at first glance. It takes a while to fully get the flow of how code works. But be sure to always ask questions!
:::
#### 2. Connect `RestaurantsViewController` to API data
- Initialize your `restaurantsArray` on your `RestaurantsViewController` file. It should look something like this:
```Swift
var restaurantsArray: [[String:Any?]] = []
```

The `restaurantsArray` is our placeholder for storing all the data we get from the API request
- Create a function `getAPIData()` that retrieves the data from the API and stores the data in our `restaurantsArray` variable:

</details>
<br>
<details>
<summary style="font-size:1.25em; font-weight: 600">
Milestone 3: Build the basic Restaurants Feed
</summary>
#### 1. Configure tableView
- Add the tableView.delegate + tableView.dataSource on `viewDidLoad()`:
```Swift
// ––––– TODO: Add tableView datasource + delegate
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
```
- Add the protocols for `UITableViewDelegate` and `UITableViewDataSource` to your `RestaurantsViewController` class:
```Swift
class RestaurantsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
...
}
```
- An error should pop up. In that error, click on "fix" to automatically add the protocol stubs and place them at the bottom of all your code:

- **==Note:==** The `numberOfRowsInSection` method simply tells the table view how many rows, or cells in this case, to create. How many cells do we want? Well, as many as we have restaurants. We can get that number by calling the `count` method on our `restaurantsArray` array. So, instead of returning a hard coded number like `5` we will want to `return restaurantsArray.count`. This is where you can get into trouble if `restaurantsArray` contains `nil` which is why we initialized `restaurantsArray` as an empty array because although empty, it is not `nil`.
#### 2. Configure tableView protocol stubs for cells
- Configure `numberOfRowsInSection` protocol stub:

- Configure `cellForRowAt` protocol stub:

### ^^ What's happening here?
- **References:** [Setting up the Image View](https://guides.codepath.org/ios/Working-with-UIImageView) in your Custom Cell:
- Each cell will need a single `UIImageView`. Make sure to create an outlet from the image view to your **RestaurantCell** class and not your RestaurantsViewController class; after all, we created a the custom cell class to control the properties of our reusable cell. **DO NOT** name this outlet `imageView` to avoid colliding with the default `imageView` property of the `UITableViewCell` base class.
- **NOTE:** The `tableView(_:cellForRowAt:)` method is called each time a cell is made or referenced. Each cell will have a unique `indexPath.row`, starting at `0` for the first cell, `1` for the second and so on. This makes the `indexPath.row` very useful to pull out objects from an array at particular index points and then use the information from a particular object to populate the views of our cell.
- In the `tableView(_:cellForRowAt:)` method, pull out a single `restaurant` from our `restaurantsArray` array
```swift
let restaurant = restaurantsArray[indexPath.row]
```
- Getting the image from the restaurant dictionary:
1. It's possible that we may get a `nil` value for an element in the `restaurantArray`, i.e. maybe no images exist for a given restaurant. We can check to make sure it is **not** `nil` before unwrapping. We can check using a shorthand swift syntax called **if let**
1. `restaurant` is a dictionary containing information about the restaurant. We can access the `restaurantArray` array of a `restaurant` using a **key** and **subscript syntax**.
- Implementation to getting the image:
- 💡 This is the url location of the image. We'll use our AlamofireImge helper method to fetch that image once we get the url.
1. Get the image url string from the restaurant dictionary
1. Get the convert url string –> url
1. set image using the image url with AlamofireImage
```swift
// 1.
if let imageUrlString = restaurant["image_url"] as? String {
// 2.
let imageUrl = URL(string: imageUrlString)
// 3.
cell.restaurantImage.af.setImage(withURL: imageUrl!)
}
```
- Set the image view
1. We'll be bringing in a 3rd party library to help us display our restaurant image. To do that, we'll use a library manager called CocoaPods. If you haven't already, **[install CocoaPods](http://guides.codepath.org/ios/CocoaPods#installing-cocoapods)** on your computer now.
1. Navigate to your project using the Terminal and create a podfile by running, `pod init`.
1. Add `pod 'AlamofireImage'` to your `podfile`, this will bring in the AlamofireImage library to your project.
1. In the Terminal run, `pod install`. When it's finished installing your pods, you'll need to close your `xcodeproj` and open the newly created `xcworkspace` file.
1. import the AlamofireImage framework to your file. Do this at the top of the file under `import UIKit`. This will allow the file access to the AlamofireImage framework.
```swift
import AlamofireImage
```
1. call the AlamofireImage method, `af_setImage(withURL:)` on your image view, passing in the url where it will retrieve the image.
```swift
cell.restaurantImage.af.setImage(withURL: imageUrl!)
```
#### 3. Last Step! Update the table view to display any new information
- Our table view will likely be created before we get our data back from the network request. Anytime we have fresh or updated data for our table view to display, we need to call:
- Do this inside the **getAPIData()** function, right after we load the data we got back into our `restaurantsArray` property.
```swift
// ––––– TODO: Get data from API helper and retrieve restaurants
func getAPIData() {
API.getRestaurants() { (restaurants) in
guard let restaurants = restaurants else {
return
}
self.restaurantsArray = restaurants
self.tableView.reloadData() // reload data!
}
}
```
- Call our `getAPIData()` from `viewDidLoad()`
``` Swift
override func viewDidLoad() {
...
getAPIData()
}
```
💡 **Tip:** Are your images looking like squished hamburgers like this?
- <img src="https://i.imgur.com/tDOjRsI.png" height=100>
#### Yes! How do I fix this?
Inside your `viewDidLoad()` function, configure your tableView rowHeight property. Something like this, depending on your liking:
```swift
override func viewDidLoad() {
...
tableView.rowHeight = 150
}
```
:::success
### :tada: Congrats! You have finished the first part of Yelpy!
:::
</details>
<br>
## Bonus
- [ ] Make your Yelpy app look like the one shown in the GIF introduction of the lab!
- **Note:** You can download the star ratings images from Yelp [**here**](https://www.yelp.com/developers/display_requirements)
## Gotchas
- If your app crashes with the exception: `Unknown class RestaurantsViewController in Interface Builder file`, try following the steps in this [stackoverflow answer](http://stackoverflow.com/questions/24924966/xcode-6-strange-bug-unknown-class-in-interface-builder-file/24924967#24924967).
- Compile Error: "No such module AlamofireImage"
- Make sure you are in the `.xcworkspace` file
- Try cleaning and building your project. <kbd>Command</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> and <kbd>Command</kbd> + <kbd>B</kbd>
:::warning
☝️ **NO submission is required for labs**
:::
# Yelpy - Part 2
<img src="https://i.imgur.com/SEyigmC.gif" width=200 /><br>
### Overview
Extend your Yelpy app by building a Detail View. To do that, you'll be passing data between view controllers and implementing push (show) navigation.
### Pair Programming
- The checkpoints below should be implemented using the Pair Programming method
### Project Setup
Download the Lab Starter below (we are working on the previous unit's lab):
#### @[[assets/yelpy_starter_2.zip]]
:::danger
:warning: **Please do not move forward before creating your Yelp API Key for the project!**
:::
#### How to get Yelp API Key
1. [Create a Yelp Account](https://www.yelp.com/developers/v3/manage_app) and generate an API Key
- Fill out the form to create a Yelp App to obtain an API Key. Once filled out you should your API key would look something like this:

- Once the API key is generated, go to your `Network` folder and copy/paste your API key on the `API.swift`.
## Milestone 1: Build the Restaurant Class
### Step 1: Create Restaurant Class File
- Create a new Swift file under `Models` folder called `Restaurant.swift`

<br>
### Step 2: Configure Restaurant Class
- Create variables + initializer method for the `Restaurant` class. It should look something like this:
:::warning
:bulb: Before viewing the code below, make sure to review your [Swift Fundamentals](https://guides.codepath.org/ios/Understanding-Swift#classes) about classes!
:::

## Milestone 2: Set up Cell Properties using Restaurant Object
### Step 1: Configure Cell properties with didSet
- On your `RestaurantCell.swift` file, lets configure the labels inside that file. It should like this:

<br>
:::warning
:bulb: **Further readings:** In this section, we are implementing property observers, which allows the cells to update its properties more efficiently. Read more about [**Property Observers**](https://guides.codepath.org/ios/Understanding-Swift#property-observers).
:::
### Step 2: Configure API for Restaurant Array
In our `Networks/API` file, change the code to return an array of restaurants`[Restaurant]`:
- First, update the escaping method to return `[Restaurant]?` instead of `[[String:Any]]?`
```Swift
// Update escaping method to [Restaurant]?
static func getRestaurants(completion: @escaping ([Restaurant]?) -> Void) { ...
}
```
- Next, update the code to convert the JSON Dictionary data to an array of Restaurants:
```Swift
let dataDictionary = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
// Get array of restaurant dictionaries
let restDictionaries = dataDictionary["businesses"] as! [[String: Any]]
// Variable to store array of Restaurants
var restaurants: [Restaurant] = []
// Use each restaurant dictionary to initialize Restaurant object
for dictionary in restDictionaries {
let restaurant = Restaurant.init(dict: dictionary)
restaurants.append(restaurant) // add to restaurants array
}
return completion(restaurants)
```
:warning: In our `RestaurantsViewController`, make sure to update the variable `restaurantsArray` as well to an array of restaurants `[Restaurant]`.
### Setp 3: Configure Protocol Stub for cellForRowAt
Remember this code?

We will be refactoring all this to **4 lines!**
- Set the cell's `r` variable to the restaurant object from indexPath. Your refactored protocol stub should look something like this:

Run your app to see if it works before moving on to the next step!
## Milestone 3: Build the Details Screen
This milestone will have you work with Navigation Controllers and transitions between screens while passing data. Review our [quickstart for Navigation Controllers](http://guides.codepath.org/ios/Navigation-Controller-Quickstart) and [Navigation Controllers guide](http://guides.codepath.org/ios/Navigation-Controller) for more info.
**Step 1 –** Create a new View Controller for the details screen(`RestaurantDetailViewController`):
- In our `Controllers` folder, create a new Cocoa Touch Class (without a XIB, subclass of UIViewController). (`File -> New -> File...`)
- Using the Object Library, add a UIViewController to the `Main.storyboard` by dragging it from the Object Library and placing it on the storyboard:

- Set the custom class of the View Controller to `RestaurantDetailViewController` (similar to what we did for the PhotosViewController above):

**Step 2 -** Implement `RestaurantDetailViewController`:
- The view should consist of a single `UIImageView`.
- Your **Detail View Controller** should have a single public property for the photo Url.
**Step 3 –** Embed the **Details View Controller** inside of a Navigation Controller to allow it to push the details screen onto the nav stack.

**Step 4 –** Make the cell take you to the RestaurantDetailViewController when tapped on a restaurant cell
- In the storyboard, *ctrl-drag* from the cell to the RestaurantDetailViewController and choose "Show".

- In `RestaurantsViewController`, implement the `prepareForSegue` method to pass the restaurant object to the details screen under the tableView protocol stubs. Your code should should like this on the view controller:

- On your `RestaurantDetailViewController`, setup the image using AlamoFireImage and the restaurant object:

## Bonus User Stories
The following bonus user stories are optional and meant to serve as an extra challenge if you'd like to take your app further.
### 1. Implement SearchBar for your Restaurants
- Follow this guide to learn implement search bar into your application: [SearchBar Guide](https://guides.codepath.org/ios/Search-Bar-Guide)
<img src="https://i.imgur.com/NX1OS0j.gif" width="200"/><br>
### 2. Infinite Scrolling
Add [infinite scrolling](https://guides.codepath.org/ios/Table-View-Guide#adding-infinite-scroll) to the main photo feed.
- Be sure to avoid loading more if there is already a request in-flight or you've reached the end of the feed.
# Lab 3 - Auto Layout
## Overview
This week we will be covering Auto Layout. This is a super important topic as we need out apps to work with a variety of screen sizes and orientations. This is a topic that will follow you thought your mobile career.
### Required User Stories
We want our app to run on many devices so we need to make our UI adaptive in two ways.
:::info
- Orientation
- Screen Size
:::
We will do this by adding constraints and StackViews to our main storyboard.
## Intro to AutoLayout
Auto Layout allows you to specify how your app looks based on the view and the orientation. Many of you have played around with this and it can be very intimidating. If you have done web development you can think of Auto Layout as a responsive design. Or if you have done Android development you will most likely have been exposed to Auto Layout because of the many devices. The goal of AutoLayout is to make the app look as intended depending on the size and orientation. Today we have many different iPhone iPad and watch sizes and we need to account for them.
:::warning
:heavy_check_mark: Check it out :exclamation:
[Apple Documentation](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html)
[Outside Resource](https://www.twilio.com/blog/2018/05/xcode-auto-layout-swift-ios.html)
[Outside Resource](https://www.appcoda.com/learnswift/auto-layout-intro.html)
[Outside Video Resource](https://www.youtube.com/watch?v=27TFuaOpUsE&list=PL23Revp-82LI-MTPyLtvzTCDl-vJKwjlU)
:::
### How it works
If you have taken algorithm classes then you may have heard of [***dynamic programming***](https://en.wikipedia.org/wiki/Dynamic_programming). If here is a quick overview, Dynamic Programming is trying to find the best solution given a list of constraints. Auto Layout uses this idea and tries to estimate the best placement of views based on the orientation and size of the device given the constraints we provide.
Before we begin the lab, let's discuss different ways that AutoLayout can be implemented in Xcode...
### Implementing AutoLayout
#### There are 4 ways to use AutoLayout
**1 - Code** :face_with_head_bandage:
This is a great way to handle AutoLayout if you are working in a large team or want full control of your views. Although this is more difficult to master it's worth taking a look into because it is still used in some large projects to avoid merge conflicts. To do this we have a few options:
:::info
- Creating instances of constraints(NSLayoutConstraint) and adding them to the view
- Visual Format Code which is a micro language that is inserted into strings and passed into views
- Creating constraints using the Interface Builder *NOTE: We will use this to make minor changes*
:::
```Swift
// Get the superview's layout
let margins = view.layoutMarginsGuide
NSLayoutConstraint(item: myView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: 0.0).isActive = true
func addConstraints() {
//Collect Views to apply VFL
let buttonsDictionary = ["button1": flagButton1,
"button2": flagButton2,
"button3": flagButton3]
//Metrics establish Fixed Constants
let metrics = ["topSpacing": 80, "bottomSpacing": 20, "buttonHeight": 20, "buttonSpacing": 20]
//Note that priorities can be set using @. 1000 for Required. < 100 for Optional. Example: @999
//Horizontal constraints
for buttonName in buttonsDictionary.keys {
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[\(buttonName)]-|", options: .allZeros, metrics: nil, views: buttonsDictionary))
}
//Vertical constraints
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(==topSpacing)-[button1(>=buttonHeight@997)]-(==buttonSpacing@999)-[button2(==button1)]-(==buttonSpacing@999)-[button3(==button1)]-(>=bottomSpacing@998)-|", options: .allZeros, metrics: metrics, views: buttonsDictionary))
}
```
:::warning
[Apple constraint example](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html)
:::
**2 - Xib Files**
This was the standard before storyboards. They are much simpler to work with than storyboards because they only have one scene. It is good to read up and get some practice with Xib files because some apps still use them and are easy to use when reusing a view controller.
:::warning
[Outside Resource: What is a XIB?](https://medium.com/@tjcarney89/whats-a-xib-and-why-would-i-ever-use-one-58d608cd5e9b)
[Outside Resource: Creating a XIB](https://medium.com/better-programming/swift-3-creating-a-custom-view-from-a-xib-ecdfe5b3a960)
:::
**3 - Constraints Through StoryBoards** 🧐
This is the current standard and what you may have been using in our class we will use this method along with StackViews. They allow us to layout multiple views and scenes. Because storyboards are static meaning they don't change with the screen we need to add constraints to elements so they can be orientated correctly.
:::warning
[Apple Anatomy of a Constraint](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/AnatomyofaConstraint.html)
[Apple Constraints in Interface Builder](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithConstraintsinInterfaceBuidler.html)
:::
<img src="https://i.imgur.com/FIwF8I8.png"/>
<br><br>
**4 - Stack Views** :+1: :yum:
This is the easiest way to implement AutoLayout. We will use this method first and then use the Interface Builder to help us get the look we want.
**What is a StackView?**
A StackView is a view that contains sub-views, and they allow us to all the views it contains. A StackView can organize views vertically and horizontally.
:::info
:bulb: *Tips:*
- Use StackViews first then add constraints later
- Work from the inside out. Focus on one area and work your way out.
:::
**Properties of Views:**
:::warning
[**Outside Resource: View Properties**](https://medium.com/@abhimuralidharan/ios-content-hugging-and--compression-resistance-priorities-476fb5828ef)
:::
- **Intrinsic Content Size:** The default size of the content displayed. Labels with different text lengths have different sizes
- **Compression:** Determines how the view will behave and how likely it is to change when the display size decreases.
- **Hugging:** Determines how the view resists being changed
:::info
[Outside Resource Hugging](https://medium.com/@abhimuralidharan/ios-content-hugging-and-content-compression-resistance-priorities-476fb5828ef)
:::
**Properties of StackViews:**
:::warning
[**Apple StackView Properties**](https://developer.apple.com/documentation/uikit/uistackview)
:::
- **Axis:** This Property allows the StackView to arrange the views vertically or horizontally. The default is horizontal, but this can be changed based on our needs. This property can be animated and changed in runtime.
- **Spacing** Determines the spacing between the horizontal views this can be a positive or negative value. *Note:* Spacing takes priority over the intrinsic content size, meaning this it will shrink the view before the spacing.
- **Distribution:** This allows you to control how the views behave on the axis
- [***Learn The different properties of Distribution***](https://developer.apple.com/documentation/uikit/uistackview/distribution)
- **Alignment:** Determines how the vies will take up vertical space.
## Project Setup
- The checkpoints below should be implemented using the Pair Programming method
- Use your Week 2 Yelpy project as a starting point.
- You'll be using the same GitHub repository you've used for each iteration of your Yelpy App.
Here is a project starter if you don't have the lab from last week
:::success
**@[[assets/yelpy_starter_3.zip]]**
:::
## Implementing Auto Layout
<img src="https://i.imgur.com/M5fq70K.png" height=400/>
<br>
### Step 1: How do you want it to look? 🤔
Determine the UI and layout of the subviews
- How do you want the layout to look?
- How do you want the app to look with different screen sizes?
- What orientation do you want the app to be in?
<img src="https://i.imgur.com/GRPnh0f.png" height=400/>
### Step 2: Configure the StackView
Place the items into the StackView. It is best to start small work from then inside out. Place the StarsImage and the ReviewsLabel into a StackView by command clicking each element and then going to the embed in button and select stack view.
:::info
***Note:***
Based on where the items are placed on the storyboard Xcode will estimate whether to put them into a vertical or horizontal StackView. You can always change it later.
:::

Description: Here is a gif showing how you add elements into a StackView
<img src="https://imgur.com/ycsgpyq.png" width="200" />
<br>
<br>
Description: See how the Reviews label and the Stars Image changed size
:::info
***Note***: Why did this change our item sizes?* They were changed back to there Intrinsic Content Size and the were distributed to the available empty space. We can fix this later.
:::
### Step 3: Continue Using StackViews when possible
Now we can build out and embed all of our elements into StackViews.
- Add more StackViews one for the
- One containing the Resturant Image
- One containing the Name Label, Category Label, and Phone label
- One containing both StackViews
<img src="https://i.imgur.com/Pqv6E5i.png"/>
### Step 4: Add constraints
Now we have all of our stack Views we need to add constraints.
---
- Remember that we need 4 constraints (2 Horizontal and 2 Vertical)
:::warning
:bulb:***Note:**** If out item has an intrinsic content size then we only need 2 (1 Horizontal and 1 Vertical)
:::
To add a constraint click the item and control drag it.
<img src="https://i.imgur.com/i1GtRbs.gif" />
### Step 5: Making Minor Changes :mag:
Play with the stack views and constraints until you have the desired look. Try clicking on the StackView and changing the Axis, Alignment, Distribution, and spacing until you have the look you want.
### Resources :books:
This topic can be intimidating and can be frustrating, we recommend that you create a view with a few buttons and labels to get the hang of it. Here are some additional items that can help you.
:::warning
- [Apple AutoLayout Documentation](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html#//apple_ref/doc/uid/TP40010853-CH7-SW1)
- [Apple StackView Documentation](https://developer.apple.com/documentation/uikit/uistackview)
- [Apple Constraints Documentation](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithConstraintsinInterfaceBuidler.html)
- [CodePath Guide](https://guides.codepath.com/ios/Auto-Layout-Basics)
:::
### BONUS
Try the app on a horizontal view. Figure put what happened to our AutoLayout.
:::info
It may have something to do with your constraints 🧐
:::
:::warning
☝️ **NO submission is required for labs**
:::
# Lab 4 Animations
### Core Reading
:::warning
- [Animation](https://guides.codepath.org/ios/Animation)
- [Using Gesture Recognizers](https://guides.codepath.org/ios/Using-Gesture-Recognizers)
- [Moving and Transforming Views with Gestures](https://guides.codepath.org/ios/Moving-and-Transforming-Views-with-Gestures)
- [Gesture Recognizer Overview](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html)
:::
### Why Animation?
Open up an app and you are likely to see an animation either in the launch screen or when you swipe to refresh. Using animations in key areas like fetching data tells the user that things are happening therefore encouraging the user to wait. It also generally makes your app look professional. In the past, it took graphic designer skills to create animations and while that is still true for custom animations we now have a tool called [Lottie](https://airbnb.io/lottie/#/) that allows us to use professional animations in our apps.
:::warning
Here is a [**link**](https://medium.com/flawless-app-stories/animations-in-ios-30-beautiful-examples-80cb2663c559) with some examples of loading animations
:::
### Project Setup
- The checkpoints below should be implemented using the Pair Programming method
- Use your Week 3 Yelpy project as a starting point.
- You'll be using the same GitHub repository you've used for each iteration of your Yelpy App.
Here is a project starter if you don't have the lab from last week
:::success
**@[[assets/yelpy_starter_4.zip]]**
:::
### Lottie
:::warning
- Lottie iOS [Documentation ](http://airbnb.io/lottie/#/ios)
- Lottie [Github](https://github.com/airbnb/lottie-ios)
- Lottie[ tutorial](https://lottiefiles.com/blog/working-with-lottie/how-to-add-lottie-animation-ios-app-swift)
:::
<img src="https://imgur.com/LQnFAG1.gif" />
### 1. Install Lottie
```swift
pod 'lottie-ios'
pod install
```
For every file you use Lottie
```swift
import Lottie
```
### 2. Find the animation you want
- Once you have selected the animation that you want download it and drag it into your project.
:::info
***Note*** Download the animation as **JSON** file and drag them and drop them into your project.
*I chose a [**food carousel**](https://lottiefiles.com/4762-food-carousel) animation that went well with the theme of yelp*
:::
<img src="https://i.imgur.com/tS1JYOQ.png" />
### 3. Calling the Animation
:::warning
Reference the documentation [Documentation](https://airbnb.io/lottie/#/ios)
:::
To call the animation in General
```swift
// create a AnimationView
var animationView: AnimationView?
override func viewDidLoad() {
super.viewDidLoad()
animationView = .init(name: "animationName")
animationView?.frame = view.bounds
animationView?.play()
}
```
For this lab we are going to create a global variable so we can call the animation
```swift
var animationView: AnimationView?
```
Because we will want to call animations multiple times during the program lets write a function called Start animation
**What this function needs to do:**
1. Set the size of the frame
2. Set the animation loop
3. Set the animation speed
4. Start the animation
<img src="https://imgur.com/UhxBkvm.png" />
<br><br>
**We also need a stop function that needs:**
1. Stop the animation
2. Remove the animation overlay
<img src="https://imgur.com/6aZ5sn6.png" />
<br><br>
:::warning
🧐 Not working? :bulb: Did you call your start and stop functions?
:anguished: Still not working? :bulb: Think about adding a time delay to your stop function
:::
<img src="https://imgur.com/z8lKxMu.gif" width=250> <img src="https://imgur.com/oAHqfin.gif" width=250>
:::info
You can resize you animation any way you want take some time to play around with it
:::
:::success
:smile: You have completed this lab if you want to add extra style try adding in a Skeleton View view and a pull to refresh :smiley:
:::
---
## Skeleton View
Want to take your app to the next Level?
Skeleton View is a way to show the users that your page is loading and are fetching the data. We will implement one to display while the animation is playing.
:::warning
* Here is the [Documentation](https://github.com/Juanpe/SkeletonView)
* Using a Skeleton View in a UITableView: [Link](https://medium.com/flawless-app-stories/skeletonview-animation-ade973655d03)
* Another Guide on how to use a Skeleton View: [Link](https://medium.com/the-aesthetic-programmer/facebook-loading-labels-animation-simple-approach-for-skeleton-view-in-swift-4-4fcdfeffd121)
:::
<img src="https://github.com/Juanpe/SkeletonView/blob/develop/Assets/tableview_scheme.png?raw=true" />
### 1. Instal and import Skeleton View
```swift
pod 'SkeletonView'
pod install
//For files that you want to use SkelitonView:
import SkeletonView
```
### 2. Call SkeletonView
In general you will use:
```swift
// This shows a Solid
SkeletonViewview.showSkeleton()
//Hide
view.hideSkeleton()
```
Since we are working with table views we need to call the SkeletonTableViewDataSource instead of UITableViewDelegate, UITableViewDataSource and add in a function called collectionSkelitionView
<img src="https://imgur.com/6WFYhUt.png" />
<br><br>
:::info
Note: because we removed UITableViewDelegate from our extension RestaurantsViewController we need to add UITableViewDelegate to our class UITableViewDelegate
:::
Now that we have our TableView set we need to call the Skeleton View in our startAnimations function and hide it in the stop Animations function
### 3. Set the view to Skeletonable
The last step is to go into each view and set the elements to skeletonable = on
<img src='https://imgur.com/uhP6oVM.png' />
### 4. Making sure the SkeletonView is called in the tableView Cell
Now that we have turned everything on let's make sure the tableView is set to show the SkeletonView. Currently, it is only being applied outside the tableView ie. the search-bar.
to do this create a global var refresh. This will be used to determine when the tableView is being refreshed.
```swift
var refresh = true
```
Next inside the tableView function we will add an if statement that that will
<img src='https://imgur.com/R4zmoRm.png'/>
<br><br>
Finally we need to set refresh to false at the end of the stopAnimations function.
```swift
refresh = false
```
:::success
:+1: :smile: You have completed THE SkeletonView :smiley: :+1:
:::
<img src='https://imgur.com/EJGYjhl.gif'/>
### 4. Clean Up
Now we have finished our code is a little sloppy and if we want to include a pull to refresh or call the animations in different parts of the program its best to refactor out code to separate the SkeletonView from the tableView to do this we will recreate a RestaurantsViewController class with a UITableViewDelegate, and a UITableViewDataSource. once we do that we will copy all of the table view functions into it.
<img src='https://imgur.com/QpYhFub.png'/>
<img src='https://imgur.com/WyUZpXe.png'/>
Finally don't forget to remove the UITableViewDataSource from our RestaurantsViewController class
<img src='https://imgur.com/DTJI583.png'/>
### Resources :book:
:::warning
Here are some helpful resources
- [Documentation](https://github.com/Juanpe/SkeletonView)
- [Hacking with Swift](https://www.hackingwithswift.com/articles/40/skeletonview-makes-loading-content-beautiful)
- [Medium Article 1](https://medium.com/@osamakhan_92979/skeletonview-animation-in-swift-uitableview-d5508f2a736c)
- [Medium Article 2](https://medium.com/the-aesthetic-programmer/facebook-loading-labels-animation-simple-approach-for-skeleton-view-in-swift-4-4fcdfeffd121)
- [Cocoapods Article](https://cocoapods.org/pods/SkeletonView)
:::
---
## Bonus: Pull To Refresh:
Now that we have done some animations our users may want to get updates data. Previously we only have the data refreshing on the startup.
:::warning
[UIRefreshControl Guide](https://guides.codepath.org/ios/Using-UIRefreshControl)
:::
:::info
Hint :bulb: Twitter part 2 goes over pull to refresh
:::
## Bonus: Launch Screen?
In iOS we cant add lottie to the launch screen storyboard but there is a tool called Flow that allows us to edit and insert animations into the launch screen
:::warning
[Flow](https://createwithflow.com/)
[Flow tutorial](https://createwithflow.com/tutorials/launchAnimationStepByStep/)
:::
# 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:

### 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`

### 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

## 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

<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 💬
<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>
<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>
<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
```
<br>
:::warning
☝️ **NO submission is required for labs**
:::
# Lab 6 - Yelpy PhotoMap
In this lab you will be building a photo map. It will allow the user to take a photo, tag it with a location, and then see a map with all the previously tagged photos.

### Getting Started
- The checkpoints below should be implemented using the Pair Programming method
## Milestone 1: Project Setup and Initialize MapKit
1. Use the Yelp Project Starter.
- If you want to build off your old project you will need to implement a new UIViewController with a custom class of `PostImageViewController` and create a segue from `RestaurantDetailViewController` to this new custom VC
:::success
**@[[assets/yelpy_starter_6.zip]]**
:::
:::info
The storyboard and files are already updated and ready for you to implement your code.
***Note:*** The files we will use are:
- main.storyboard
- Project Settings
- Restaurant Model
- PostImageViewController
:::
:::warning
**Guides**
- <a href="https://guides.codepath.com/ios/Maps" target=blank>Maps - CodePath </a>
- <a href="https://guides.codepath.com/ios/Location-Quickstart" target=blank>Retrieving Location -CodePath </a>
- <a href="https://guides.codepath.com/ios/Camera-Quickstart" target=blank> Working with the Camera </a>
- <a href="https://developer.apple.com/documentation/mapkit" target=blank> MapKit -Apple Documentation </a>
- <a href="https://developer.apple.com/documentation/corelocation" target=blank> Apple Core Location </a>
- <a href="https://developer.apple.com/documentation/corelocation/getting_the_user_s_location" target=blank>Retrieving Location - Apple Documentation </a>
<a href="https://medium.com/flawless-app-stories/unwind-segues-in-swift-5-e392134c65fd" target=blank>Unwinding Segues - Medium Article </a>
:::
## Milestone 1: Configure MapKit + Storyboard Items
**Files to be modified for M1: Project Settings, Restaurant Model, Storyboard**
### Step 1: Add MapKit to Project
Add MapKit to project on the project configuration settings
- Click on Project Yelpy -> General -> Frameworks, Libraries, and Embedded Content
- click on (+) sign to add MapKit framework
### Step 2: Configure Restaurant Model
- You will need to refactor the Restaurant model to include coordinate points since we will be displaying the locations of each resaurant on a map!
:::info
The Yelp API returns a **coordinates** property of type **[String:Double]**
:::
### Step 3: Add MapView to Storyboard
1. Add a MapView
1. Go to **RestaurantDetailVC** and change the height constraint of **HeaderImage** to `multiplier of 20/100`
1. Add **MapKitView** beneath the HeaderImage
1. Give constraints of 0 from top of MapKitView to bottom anchor of HeaderImageView, 0 to trailing and leading constraints, and 0 to **Safe Area**
1. Create an outlet for the MapView to set its initial visible region to the restaurant's location in `viewDidLoad:`
```swift
// 1) get longitude and latitude from coordinates property
let latitude = r.coordinates["latitude"]!
let longitude = r.coordinates["longitude"]!
// 2) initialize coordinate point for restaurant
let locationCoordinate = CLLocationCoordinate2DMake(CLLocationDegrees.init(latitude), CLLocationDegrees.init(longitude))
// 3) initialize region object using restaurant's coordinates
let restaurantRegion = MKCoordinateRegion(center: CLLocationCoordinate2DMake(latitude, longitude), span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
// 4) set region in mapView to be that of restaurants
mapView.setRegion(restaurantRegion, animated: true)
```
- At this point you should see a map centered in the restaurant's location!
:::info
💡 We saved you some time and completed the following steps:
1. Imported MapKit
2. Conformed to MKMapViewDelegate
3. Set the mapView outlet's delegate to RestaurantDetailViewController :wink:
:::
## Milestone 2: Show Restaurant in MapView
**File Editing: RestaurantDetailsViewController**
### Step 1: Drop Annotation (Pin) in Map
Drop an annotation on the map and segue to PostImageViewController
```swift
// 5) instantiate annotation object to show pin on map
let annotation = MKPointAnnotation()
// 6) set annotation's properties
annotation.coordinate = locationCoordinate
annotation.title = r.name
// 7) drop pin on map using restaurant's coordinates
mapView.addAnnotation(annotation)
```
At this point, your `configureOutlets()` function should look like this:

### Step 2: MapView Methods
In this section we will be configuring our mapView methods:

1. Configure mapView method for `viewFor annotation`:
- Implement `(mapView:_ viewFor annotation:_)` method
- create a reuse identifier constant for your annotationView
- set `annotationView` to `mapView.dequeueReusableAnnotationView(withIdentifier: reuseID)`
- If `annotationView` is nil you want to configure it with an annotation and the reuse identifier from earlier then add an **accessoryView** with the camera image in your assets
- Finally, return the **annotationView**
```swift
if (annotationView == nil){
// MARK: USE MKPinAnnotationView and NOT MKAnnotationView
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseID)
annotationView?.canShowCallout = true
// 9) Add info button to annotation view
let annotationViewButton = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
annotationViewButton.setImage(UIImage(named: "camera"), for: .normal)
annotationView?.leftCalloutAccessoryView = annotationViewButton
}
```
This should be the end result:

:::info
💡 Make sure to use MKPinAnnotationView and not MKAnnotationView!
:::
1. `performSegue` using the identifier from the segue we made going to PostImageViewController inside the `calloutAccessoryControlTapped` method

## Milestone 3: Implement Protocol to Pass Data
**File Editing: PostImageViewController**
### Step 1: Create your own Protocol
- Create protocol named `PostImageViewControllerDelegate` above the class declaration
- Add weak var for PostImageViewController delegate
- Pass Image using Protocol Stub when you unwind your segue to return to DetailVC

### Step 2: Pass image back to RestaurantDetailVC
**File Editing: RestaurantDetailViewController**
In your `RestaurantDetailViewController`, using the protocol you created, conform to it in your Class and add it's `imageSelected()` protocol method:

:::success
🎉 Congrats! 🥳 You have finished all the required user stories.
:::
## Optional Stories
- [ ] Add a new ViewController and embed it in a TabBarController where you will display a map of with pins of **all** the restaurant locations
<!-- ## Photo Map
Today we'll be building a photo map. It will allow the user to take a photo, tag it with a location, and then see a map with all the previously tagged photos.
<img src="http://i.imgur.com/dMQtYhZ.gif" width=250><br>
### Milestone 1: Setup
1. Download the [starter project](https://github.com/codepath/ios_photo_map/blob/master/StarterZips/MapsStarter(Swift5).zip). The starter project contains the following view controllers:
- `PhotoMapViewController` => This is where you'll add the map in **Milestone #2**.
- `LocationsViewController` => This is already implemented and allows you to search Foursquare for a location that you want to drop a photo.
- `FullImageViewController` => This is where you'll add a fullscreen image in **Bonus #2**.
1. Fill in the following constants in `LocationsViewController` to connect to the Foursquare API:
- `CLIENT_ID` = QA1L0Z0ZNA2QVEEDHFPQWK0I5F1DE3GPLSNW4BZEBGJXUCFL
- `CLIENT_SECRET` = W2AOE1TYC4MHK5SZYOUGX0J3LVRALMPB4CXT3ZH21ZCPUMCU
1. Add [Photo Map README template](/snippets/ios_university/readme_templates/lab_6_readme.md?raw=true)
### Milestone 2: Create the Map View
<img src="http://i.imgur.com/tro9qJv.gif" width=200><br>
Implement the PhotoMapViewController to display a map of San Francisco with a camera button overlay.
1. [Add the MapKit framework](http://guides.codepath.org/ios/Project-Frameworks#adding-frameworks-to-project) to the Build Phases.
1. Then `import MapKit` into `PhotoMapViewController`.
1. Add the MKMapView and the UIButton for displaying the camera (asset included in the starter project). In the Storyboard, find the Photo Map View Controller, and drag the map view from the Objects Library and resize it so it fills the screen. Then, drag a button over the map view, and toggle the image property to the "camera" image asset.
1. Create an outlet for the map view.
1. [Set initial visible region](http://guides.codepath.org/ios/Using-MapKit#centering-a-mkmapview-at-a-point-with-a-displayed-region) of the map view to San Francisco.
1. Run the app in the simulator and confirm that you see the map and button.
### Milestone 3: Take a Photo
1. Create a property in your PhotoMapViewController to store your picked image
```swift
var pickedImage: UIImage!
```
1. Pressing the camera button should modally [present the camera](http://guides.codepath.org/ios/Camera-Quickstart).
- Create an action for the camera button and follow the guide to launch the camera (or image picker for the simulator).
- NOTE: The simulator does not support using the actual camera. Check that the source type is indeed available by using `UIImagePickerController.isSourceTypeAvailable(.camera)` which will return true if the device supports the camera. [See the note here for an example](http://guides.codepath.org/ios/Camera-Quickstart#step-2-instantiate-a-uiimagepickercontroller).
1. When the user has chosen an image, in your [delegate method](http://guides.codepath.org/ios/Camera-Quickstart#step-3-implement-the-delegate-method) you'll want to:
1. Assign the picked image to the `pickedImage` property
1. Dismiss the modal camera view controller you previously presented.
1. [Launch the LocationsViewController](http://guides.codepath.org/ios/Using-Modal-Transitions#triggering-the-transition-manually) in the completion block of dismissing the camera modal using the segue identifier `tagSegue`.
1. Your code should look like this:
```swift
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let originalImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
let editedImage = info[UIImagePickerController.InfoKey.editedImage] as! UIImage
// Do something with the images (based on your use case)
pickedImage = editedImage
// Dismiss UIImagePickerController to go back to your original view controller
dismiss(animated: true) {
self.performSegue(withIdentifier: "tagSegue", sender: nil)
}
}
```
### Milestone 4: Drop a Pin on the map
After the user selects an image, if you completed Milestone 3, they'll be taken to a view controller where they can pick a location to tag the image with. In this milestone, we'll drop a pin at that location.
<img src="http://i.imgur.com/Ih8wIo9.gif" width=200><br>
1. Add a pin to the map (We won't be actually using our image yet)
- In the PhotoMapViewController, inside the **locationsPickedLocation(controller:latitude:longitude:)** method, [Add a pin to the map](http://guides.codepath.org/ios/Using-MapKit#drop-pins-at-locations). Note: this function is already in the starter project.
- You can set the title to the longitude using `annotation.title = String(describing: latitude)`
- Notice how we call the [addAnnotation](https://developer.apple.com/library/prerelease/ios/documentation/MapKit/Reference/MKMapView_Class/index.html#//apple_ref/occ/instm/MKMapView/addAnnotation:) method on our `mapView` instance.
### Milestone 5: Add the photo you chose in the annotation view
<img src="http://i.imgur.com/jsPJ3er.gif" width=200><br>
1. [Add a custom image to the annotation view](http://guides.codepath.org/ios/Using-MapKit#use-custom-images-for-map-annotations)
1. Add `MKMapViewDelegate` to your PhotoMapViewController's class declaration. The class declaration should look like:
```swift
class PhotoMapViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate,
LocationsViewControllerDelegate, MKMapViewDelegate
```
1. Set the PhotoMapViewController as MapView's delegate. In `viewDidLoad`, add the following line:
```swift
mapView.delegate = self
```
1. Implement the [mapView:viewForAnnotation](https://developer.apple.com/library/prerelease/ios/documentation/MapKit/Reference/MKMapView_Class/index.html#//apple_ref/occ/instm/MKMapView/viewForAnnotation:) delegate method to provide an annotation view. The code below will add your `pickedImage` to the annotation view.
```swift
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let reuseID = "myAnnotationView"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseID)
if (annotationView == nil) {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseID)
annotationView!.canShowCallout = true
annotationView!.leftCalloutAccessoryView = UIImageView(frame: CGRect(x:0, y:0, width: 50, height:50))
}
let imageView = annotationView?.leftCalloutAccessoryView as! UIImageView
// Add the image you stored from the image picker
imageView.image = pickedImage
return annotationView
}
```
1. Run the app in the simulator. After adding an image at a location, if you tap on the pin, you should see a popup with the photo.
### Bonus 1: See Fullscreen Picture
<img src="http://i.imgur.com/mbfp9PL.gif" width=200><br>
- Tapping on an annotation's callout should push a view controller showing the full-size image.
- Add a button to the `rightCalloutAccessoryView` of type `UIButtonType.DetailDisclosure`
- Implement the [delegate method](https://developer.apple.com/library/prerelease/ios/documentation/MapKit/Reference/MKMapViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/MKMapViewDelegate/mapView:annotationView:calloutAccessoryControlTapped:) that gets called when a user taps on the accessory view to [launch the FullImageViewController](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/#//apple_ref/occ/instm/UIViewController/performSegueWithIdentifier:sender:) using the segue identifier `fullImageSegue`.
### Bonus 2: Replace the Pin with an Image
<img src="http://i.imgur.com/WIwqNtn.gif" width=200><br>
- The annotation view should use a custom image to replace the default red pin.
- Set MKAnnotationView's image property to the appropriate image. -->