Explore

Let's do some design tweaks before we jump into making our layout work for every device and start to get this app more polished.

Let's compare what we see on the iPhone 8 with the original design:

There are a few things we need to fix:

We will fix all of these, but we will focus on first four right now. We have rounded corners in our photo filter list. We can implement these here. Open the ExploreCell.swift file by hitting cmd + Shift + o, type ExploreCell, and hit Enter. Add the following extension:

private extension ExploreCell {
func roundedCorners() {
imgExplore.layer.cornerRadius = 9
imgExplore.layer.masksToBounds = true
}
}

Add a roundedCorners() call inside the –layoutSubviews method.

Now that we have fixed the first issue let's fix the second by removing the background color. Open Explore.storyboard and select the exploreCell in the Outline view. Under the Utility panel, in the Attributes inspector update the Background from LetsEat Dark Grey to White Color. The third issue, that the navigation bar is displayed, is pretty easy to fix as well.  Open the ExploreViewController.swift file by hitting cmd + Shift + o and type ExploreViewController and hit Enter. After viewDidLoad() add the following method:

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: false)
}

That is all we need to do; now, every time we go to this view, we will always hide the Navigation bar at the top. Finally, let's update our app so that our tab bar buttons are the correct color. We need to add a new color to our Color Set called LetsEat Red and set the Hex value to D0021B. Now, open up the AppDelegate.swift file at the bottom of the file after the last curly brace adds the following:

private extension AppDelegate {
func initialize() {
setupDefaultColors()
}

func setupDefaultColors() {
guard let red = UIColor(named: "Lets Eat Red") else { return }
UITabBar.appearance().tintColor = red
UITabBar.appearance().barTintColor = .white
UITabBarItem.appearance()
.setTitleTextAttributes(
[NSAttributedStringKey.foregroundColor: UIColor.black],
for: UIControlState.normal)
UITabBarItem.appearance()
.setTitleTextAttributes(
[NSAttributedStringKey.foregroundColor: red],
for: UIControlState.selected)
UINavigationBar.appearance().tintColor = red
UINavigationBar.appearance().barTintColor = .white
UITabBar.appearance().isTranslucent = false
UINavigationBar.appearance().isTranslucent = false
}
}

Now inside the -application:didFinishLaunchingWithOptions: add the initialize() method call. Build and run the project by hitting the Play button (or using ⌘ + R).

You should now see we have completed the first four items.  Let's address the spacing issue next. Before we do, let's first switch our device to any iPad (I typically use the iPad Air 2, but you can pick whatever one you like). Then build and run the project by hitting the Play button (or using ⌘ + R). You will see that it is not too bad currently, but the spacing is different on each device. Now, we have set up values that only work for one device. However, we need this to work on all devices.

Let's start with our Explore.storyboard. First, we need to update some Auto Layout for our explore cells. Right now, we have a width set up for our image that needs to be more dynamic:

  1. Open up Explore.storyboard.
  2. Select the image inside the exploreCell.
  3. Then, in the Utilities panel, select the Attributes inspector and change the Content Mode under the View section to Aspect Fill. This will keep the images from looking stretched, while still filling the entire area.

These are the only updates we need to make to our explore cell. Next, we are going to create a file that will let us know which device is used. We can then use this to set up different looks, depending on the device. Let's create this file:

  1. Right-click the Misc folder and select New File.
  2. Inside of the Choose a template for your new file screen, select iOS at the top and then Swift File. Then, hit Next.
  3. Name this file Device and then hit Create.

First, we need to update our import statement from import Foundation to import UIKit.

Next, add the following to the import statement:

struct Device {
static var currentDevice: UIDevice {
struct Singleton {
static let device = UIDevice.current
}
return Singleton.device
}

static var isPhone: Bool {
return currentDevice.userInterfaceIdiom == .phone
}

static var isPad: Bool {
return currentDevice.userInterfaceIdiom == .pad
}
}

Our new struct will now tell us whether we are on an iPad or an iPhone. Having a file like this is good because it allows you to avoid having to rewrite the same code. To implement this code, all we need to do is add a snippet of code like the following:

if Device.isPhone{ }

This statement will make our code more readable;  if we need to add any more checks for particular devices, we can do it all in the same file. One more great use of putting code like this into its file is that, when you build the next app, you can just add this file to your project and continue.

Next, let's open the ExploreViewController.swift file and make some more updates to our code. We need to create a variable that we will use for the spacing we want between items. Add the following before our viewDidLoad() method:

fileprivate let minItemSpacing: CGFloat = 7

Now, we need to create a function to set up some default Collection View values. We also need to create an initialize() method to call our setup function. Add the following method call inside of the initialize() method:

setupCollectionView()

Next, add the following inside of the private extension after the initialize() method:

func setupCollectionView() {
let flow = UICollectionViewFlowLayout()
flow.sectionInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
flow.minimumInteritemSpacing = 0
flow.minimumLineSpacing = 7
collectionView?.collectionViewLayout = flow
}

This method will make sure that we have seven pixels of spacing all the way around.  We finally need to create an extension that will let us handle all of the spacing programmatically. After the last curly brace add the following extension:

extension ExploreViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if Device.isPad {
let factor = traitCollection.horizontalSizeClass == .compact ? 2:3
let screenRect = collectionView.frame.size.width
let screenWidth = screenRect - (CGFloat(minItemSpacing) * CGFloat(factor + 1))
let cellWidth = screenWidth / CGFloat(factor)

return CGSize(width: cellWidth, height: 195)
}
else {
let screenRect = collectionView.frame.size.width
let screenWidth = screenRect - 21
let cellWidth = screenWidth / 2.0

return CGSize(width: cellWidth, height: 195)
}
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {

return CGSize(width: self.collectionView.frame.width, height: 100)
}
}

Adding the UICollectionViewDelegateFlowLayout allows us to update our cell item size in code. Let's discuss each part of this extension we just added.  The -collectionView:layout:sizeForItemAtIndexPath: method is used to set the size of the cell. Inside of this method, we are using the struct we created. We are checking to see if we are using an iPad or an iPhone.

In the if part of the if...else statement, we are checking whether the screen is compact or not. If the screen is compact, then we want a two-column grid; otherwise, we want a three-column grid. We are also distributing our items evenly across the width of the screen.

In the else part of the if...else statement, we are just setting up a two-column grid on all phones. We get the screen width then subtract 21, and then we divide the result by 2 to distribute the cells evenly.

If you run the project, everything will look good.

There is one more thing that's broken if you attempt to rotate the device by using ⌘ + right arrow or ⌘ + left arrow. Then, you will see that our layout spacing does not update.

In order to fix this, we need to make one more update. After -shouldPerformSegueWithIdentifier:sender:, add the following:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView.reloadData()
}

Now, build and run your project again by hitting the Play button (or using ⌘ + R) and rotate the device. You will see that our layout spacing now updates.

Explore is now complete; let's move to our locations list.