Storyboard and Code
After you learn Storyboard, you’ll eventually want to take your interactions further. In Storyboard, you can only set up the objects and constraints. What if you want to animate when a button is tapped? Or, when an animation completes? That’s when you need to script that in code. Then, you can use a lot more events.
Downloads
To follow this course, you can download the source file, which will help you compare your progress.
One Class Per View Controller
Typically, you have one .swift file per Storyboard View Controller. When you first created your Xcode project, you had one View Controller, which is connected to ViewController.swift, as set up in the Identity Inspector. In the Project Navigator, you can select the Class file and find the Swift code inside.
Assistant Editor
We learned to use the Assistant Editor for previewing your code in Playground. The Assistant Editor can also be used for showing the Class file next to the Storyboard. This way, it’s easier to connect the UI objects to the code.
Let’s open the Assistant Editor by clicking the Circles icon in the Toolbar. Click the Circles again, but this time inside the Assistant Editor and select Automatic.
Automatic will find the Class file attached to your View Controller in Storyboard if you connected to Identity Inspector.
The Class File Explained
In the Assistant Editor, we can find the Swift code. Let me explain a little about what’s going on.
import UIKit
This is the framework that we’re using. Think of it as React or Bootstrap (although way more complex). Swift itself isn’t a framework — it’s a language. UIKit is the underlying framework. It’s what allows us to code UI objects specific to iOS and use cool features like Storyboard or smaller things like Background Blur.
class ViewController: UIViewController { }
Our Class ViewController is a subclass of UIViewController, meaning that it can use the same properties and methods, like viewDidLoad(). These are pre-made by Apple, allowing you to focus on building your app interactions, rather than building the framework. If you don’t understand everything, that’s fine because you’ll get better with time.
override func viewDidLoad() { }
As the name suggests, viewDidLoad is when the screen loads. Whatever is coded inside the curly brackets will run.
We can delete didReceiveMemoryWarning() and the green comments below. This stuff is more advanced and not necessary for beginners to understand. So far, your Class file should look like this.
Create an IBOutlet
Right now, the Class file is connected to the View Controller in Storyboard, but none of the UI objects are referenced.
Let’s create an IBOutlet for the title. Control + Drag the title to the Class file. Important: it has to be dropped inside the ViewController’s curly brackets and outside the viewDidLoad function.
When you drop the connection, a dialog will appear. We need to name the object to titleLabel. title is the name we give it and Label is the type. Once done, click Connect.
Storyboard Connections to IBOutlets
Let’s create more IBOutlets for the device image and play button. This time, you can use the Document Outline to Control + Drag from, which is easier for the play Visual Effect View.
So far, our project should look like this. If you mouse over the circle on the left of that line of code, it’ll show you where it’s connected within Storyboard.
Important: don’t change the name of the IBOutlet or anything in your code. Once created, there is no going back unless you understand the steps it takes to refactor.
Fade In Animation (Optional)
Now that we have declared our objects, we can manipulate them in the viewDidLoad. For example, we can set the alpha opacity to 0.
titleLabel.alpha = 0
deviceImageView.alpha = 0
playVisualEffectView.alpha = 0
Using UIView.animate, we can do a simple fade in animation.
UIView.animate(withDuration: 1) {
self.titleLabel.alpha = 1
self.deviceImageView.alpha = 1
self.playVisualEffectView.alpha = 1
}
Creating an IBAction
As a designer learning code, we’re interested in custom events, like when a user presses a button. Let’s create one for the play button which would play the intro video.
Control + Drag the Play Button right below the IBOutlets. Once you see the popover window, you have to change the Connection from Outlet to Action. This is only available for interactive elements such as the UIButton.
We’re naming it play + type + action. So playButtonTapped. That’s the naming convention recommended by Apple. The IBAction creates a function. You can code inside the curly brackets.
Play Video When Button is Tapped
Now that we connected the Play button, we can write some code for playing the intro video. The following code is a little more advanced, so if you feel a little lost, I suggest heading back to the Swift Playground sections.
First, we must import the AVKit framework. Type this code right underneath import UIKit.
import AVKit
Inside your playButtonTapped’s curly brackets, let’s start with declaring the URL of the video file.
let urlString = "https://player.vimeo.com/external/235468301.hd.mp4?s=e852004d6a46ce569fcf6ef02a7d291ea581358e&profile_id=175"
Then, we must transform the String into a URL, so that the AVPlayer class can read it. The AVPlayer is a UI object used to play the video with default controls such as play, pause and forward.
let url = URL(string: urlString)
let player = AVPlayer(url: url!)
Let’s create a View Controller for showing our Video player, then attach the player to it.
let playerController = AVPlayerViewController()
playerController.player = player
Finally, as with any View Controller, we can use the present function to animate it like a modal. When the animation is complete, we’ll start the video right away.
present(playerController, animated: true) {
player.play()
}
Section View Controller
Let’s learn how to create a second View Controller in Storyboard for the Section screen. Disable the Assistant Editor to give full space to the Storyboard by clicking on the 5 lines icon.
Drag and drop a View Controller from the Object library.
Create a New Class File
A Class file is needed to attach to the Section View Controller we’ve just created. To create a new Class file, go to the Project Navigator (Command + 0), right-click the ViewController.swift and select New File. In the first window, select Cocoa Touch Class.
The Class name is Section. Since we’re using a View Controller, we have to type UIViewController in the Subclass of field. ViewController will automatically be added to the Class name. Apple suggests that for every Class file, we should specify the type to avoid conflicts and to make things as clear as possible.
Once we created the SectionViewController.swift file, it’ll appear right below ViewController.swift.
Connecting to Storyboard
The new Class file isn’t connected to any Storyboard View Controller yet. So, let’s head back to the new View Controller in Storyboard and open the Identity Inspector.
Type SectionViewController. Notice that the auto-completion kicks in, which makes it easier to avoid typing mistakes.
Both Ends of the Connections
Important: when you create an IBOutlet or IBAction, it creates one connection in code and another connection that exists in the Storyboard’s Connections Inspector. The two have to be in sync and have the same names. If you modify the name in the code, you also have to delete it in the Connections Inspector.
Select the View Controller in Storyboard and go to the Connections Inspector to find all the connections made.
Replicating a Connection Error
Optional: don’t follow this part if you don’t feel comfortable yet. I’m showing an example how the app can break if you’re not careful with the connections.
- I change titleLabel to title in ViewController.swift and immediately get errors. I then change the references to title to fix the errors.
- The circles in the left are no longer filled, hinting that there is no longer a connection.
- In the Connections Inspector, the old names are still there. That’s the danger because Xcode will still try to make the connection, which ultimately will crash your app.
Debug Area
For those who have developed Websites before, you’re probably used to inspecting elements in the browser and looking at console logs. The Debug area has similar features. Whenever you have errors, you can find them on the right side of the Debug area.
Development tools can be unforgiving when it comes to crashes. If you’re lucky and followed all the steps successfully, you’ll be fine. But should you run into a crash, you need to follow these instructions.
- Look at the bottom Debug area and scroll the error messages to the top. Look for the word reason. Look for keywords that stand out. In this case, we can see mainButton. Google the sentence to look for solutions.
- Go back to the Project Navigator.
- In your main editor, click the left arrow button to go back to where you were.
- You can close the Debug area by clicking on the down arrow at the top left.
Fixing your Connections
The solution is to either Undo (Command Z) or rename back to the original name. Otherwise, if you wish to go forward with the new name, make sure to delete the old connections in the Connections Inspector, and repeat the steps.
If you don’t wish to recreate the IBOutlet, you can click on the circle and connect back to the appropriate object. The lesson here is that both ends have to be in sync.
Conclusion
This is the hardest part for anyone who’s never used Storyboard before. It’s an entirely new concept, even for many developers. Once you understand it, you can do so much. If you made it here, you should pat yourself on the back, because you made a giant step into iOS development.
Parallax Animation using Scroll
A lot of interesting interactions happen as a result of scroll events. In the Design+Code app, we use the scroll information to capture the read progress, to show or hide the navigation bar, and to apply a parallax effect on the visual elements.
Downloads
To follow this course, you can download the source file, which will help you compare your progress.
Setting up the IBOutlets
First, we’ll need to set up the objects that we want to apply the parallax effect. In the template, we already have most of the work done, except for the background image. Let’s control drag the Home image to the ViewController.swift underneath playVisualEffectView. Create an IBOutlet and name it backgroundImageView.
Also, select the Scroll View from the Document Outline and create an IBOutlet. Name it scrollView.
Repeat the same steps for the Hero View and Book View. Name them heroView and bookView respectively.
Swift Extension UIScrollViewDelegate
Extensions can be used to provide extra functionalities to an existing class. In this case, we’re going to extend our ViewController with a UIScrollViewDelegate class. Additionally, I love using Extensions to separate sections of a Class file. Add this code at the end below the ViewController class.
extension ViewController: UIScrollViewDelegate {
// Put Scroll functionalities to ViewController
}
Connecting the UIScrollViewDelegate
Whenever you use a Delegate protocol, you need to attach it to your Object. In this case, we need to attach it to the scrollView. Inside viewDidLoad, below super.viewDidLoad, put this code.
scrollView.delegate = self
scrollViewDidScroll
We need to capture the scrolling events so that we can animate our title, device image, play button and background image based on the vertical scroll position. UIScrollViewDelegate provides the extra functions that we’ll need to gain that valuable info. Inside the Extension’s curly brackets, let’s use the scrollViewDidScroll function to get the scrollView info.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView)
}
scrollView Values
Receiving info from any object or event can be valuable to create an immersive experience. In this specific case, we’re receiving info when the user scrolls thanks to the scrollView which is connected to the UIScrollViewDelegate. If we run the app, we’ll find the info printed in the Debug Area.
Capturing the Content Offset Y
The main info that we’ll need from the scroll is just the Y position. Type this inside the new scrollViewDidScroll function.
let offsetY = scrollView.contentOffset.y
Parallax Above Y Position 0
We only want to start animating when the user scrolls above the starting position, which is at Y position of 0. Let’s add this condition.
if offsetY < 0 { }
Cancelling the Hero View
When the scroll happens, we want the Hero View to stop moving. So, we’re using a trick which move the Hero View’s Y Position to the same position as the offsetY. The transform property is the perfect way to translate, rotate or scale an object. In this case, we’re using translation.
if offsetY < 0 {
heroView.transform = CGAffineTransform(translationX: 0, y: offsetY)
}
Let’s see a before and after we apply the scrolling effect.
Moving at Different Speed
Parallax is all about moving multiple objects at different speed, creating a 3 dimensional effect as if you’re watching the scenery while inside a car. Objects in the foreground moves faster than objects in the background. That’s essentially what we’ll be replicating.
We’re dividing the Y position by a negative number, for speed. The bigger the number, the slower the object moves. Since the Y Position is negative, the two negatives becomes a positive.
For example, if Y position is -10, and we divide that number by -5(speed), the object moves down by 2 pt. If we divide -10 by -2, the object moves down by 5 pt. Again, the bigger the number for speed, the slower the object moves.
heroView.transform = CGAffineTransform(translationX: 0, y: offsetY)
playVisualEffectView.transform = CGAffineTransform(translationX: 0, y: -offsetY/3)
titleLabel.transform = CGAffineTransform(translationX: 0, y: -offsetY/3)
deviceImageView.transform = CGAffineTransform(translationX: 0, y: -offsetY/4)
backgroundImageView.transform = CGAffineTransform(translationX: 0, y: -offsetY/5)
Masking with Clip to Bounds
The parallax seems to be working, but our background image is moving outside the Hero View. To fix that, let’s select the Hero View and go to the Attributes Inspector to enable Clip to Bounds.
Expanding the Background Image Height
Another issue is that when we scroll, the background image moves too far, giving way to the white space. We can fix that by expanding the height. Select the Background Image and double-click the top constraint. Change the value to -50.
Conclusion
Finally, our parallax is working as intended. Using the scroll events, you can code a lot of interesting effects and animations such as parallax, pull to refresh or unlocking hidden content. It’s a simple technique but can expand into much more as you figure out how to use it in new ways.
Collection View and Data Source
The Collection View allows you to create a grid type layout. It is also the most flexible type of layout. Virtually any layout can be created with it. Most commonly, you can see it in action in the Photos app, Music app or App Store. Additionally, it is perfect to set content that can scroll horizontally.
Downloads
To follow this course, you can download the source file, which will help you compare your progress.
Setting up a Collection View
We’ll need a Collection View to create the horizontal scrolling cards on the Design+Code iOS app.
In the Scroll View, Chapter View, we must delete the existing View right below the CHAPTER label, since we’ll be replacing it with the Collection View.
Then, we’ll drag and drop a Collection View from the Object library. Add New Constraints: right 0, bottom 50, left 0, height 248.
The Collection View Cell
The cell is for reusable content. The Card will be repeated multiple times for each section of content that we have. First, let’s set the size of the cells by clicking on the Collection View Cell and setting the Size to Custom, and configure to width 304, height 248.
Card Container
It’s a good practice to wrap your content inside a container so that you can easily move the whole thing later. Also, we’d like to be able to apply rounded corners and drop shadows to that container.
Drag and drop a UIView from the Object library and set the constraints to 0 for top, right, bottom, left. Make sure to disable Constrain to Margins.
From the Identity Inspector, add Runtime Attributes for the drop shadows and corner radius.
- layer.cornerRadius, Number, 14
- layer.shadowOpacity, Number 0.25
- layer.shadowOffset, Size, {0, 10}
- layer.shadowRadius, Number, 20
Drop shadows cannot use Clip to Bounds
When you use drop shadows, you cannot use Clip to Bounds since that would simply not show them. Why use Clip to Bounds, you may ask? Well, that’s because the card has rounded corners and the content needs to be masked within those bounds.
Card Second Container
The solution to our problem is to not use Clip to Bounds on the first container, and create another container that will use Clip to Bounds. Drag and drop a second UIView and set the constraints to 0 for top, right, bottom and left. Afterwards, in Identity Inspector, add a new Runtime attribute: layer.cornerRadius, Number, 14. This time, check Clip to Bounds in the Attribute Inspector.
Card Background
Drag and drop a UIImage and set the constraints to 0 for top, right, bottom and left. Set Image to ios11 and Content Mode to Aspect Fill.
If you’re missing the new cover images, make sure to re-download the assets again and import to your Asset Catalog.
Card Title
For the title, let’s drag and drop a Label and set it to “Learn iOS 11 Design”, 32 pt Semibold, white. The constraints are 20 for top, left and right. Make sure to set the Number of Lines to 3. Don’t worry about the text not showing in multiple lines. Storyboard doesn’t always preview objects accurately, but you’ll be able to see it when you Run the app later.
Card Description
Drag and drop another Label and set it to “A complete guide to colors, typography and layout for iOS”, 17 pt Regular, white. The constraints are 20 for right, bottom and left. Set the Number of Lines to 3.
Collection View Cell Identifier
If you Run the app, you’ll notice that the Collection View is empty. That’s because we need to fill it with actual data and set how many instances we want of it. The first thing we need to do is to set the Identifier to sectionCell. This ID is super important for declaring in the code.
Collection View Outlet
In our Class file, we’ll need to create an IBOutlet for the Collection View. Open the Assistant Editor so that we have both the Storyboard next to the Class file. Then, select the Collection View **and **Control Drag the object below your other IBOutlets. Name it chapterCollectionView.
Setting the Delegate and Data Source
At this point, we can just go back to the Standard Editor since we’ll be focusing on the code. Let’s write a new extension where we will set up the Collection View’s necessary data source.
As soon as we subclass these, there will be an error that says that “ViewController does not conform to the protocol”. Click Fix. Xcode will automatically add the missing functions for setting up the Collection View.
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
}
Number of Items in Section
One of the two necessary functions require us to set up the number of items. Let’s start with an arbitrary number: 5. This means that 5 cards will be showing.
The -> Int means that we have to return an integer that will determine the number of items.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
Collection View Cell Data
Xcode provides us with the cell information and the indexPath. The indexPath is the index for both items and sections. In this case, there is one section and 5 items.
First, we’re defining the cell to be reusable and we’re calling the identifier sectionCell that we set up previously in Storyboard. Finally, as the function requires it, we‘re returning that cell.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sectionCell", for: indexPath)
return cell
}
Connecting the Collection View Outlet
While the data source is configured, Xcode still doesn’t know how the Collection View is connected to the object we created in Storyboard. Let’s connect the Collection View’s delegate and dataSource to self. In viewDidLoad, add this code.
chapterCollectionView.delegate = self
chapterCollectionView.dataSource = self
Collection View Cell Size
At the beginning, we set the Cell size to 304 x 248, but we didn’t set how the cells will be dynamically sized when reused. To do that, select the Collection View and go to the Size Inspector to change the Cell Size to width 304, height 248.
To create a margin between cells, set the Min Spacing, For Lines to 20. Additionally, we’ll update the Section Insets, Left to 20 to add an overall margin on the left.
Collection View Horizontal Scrolling
In order to make the cards scroll horizontally, we need to go to the Attribute Inspector and set the Scroll Direction to Horizontal. While we’re at it, let’s also set the Background to Clear, which is clear. Finally, let’s make sure to hide the scroll indicators by unchecking Show Horizontal Indicator and Show Vertical Indicator.
Drop Shadows Being Clipped
As we’re testing the app, we’re noticing that the drop shadows are being clipped. To fix this, we need to uncheck Clip to Bounds from both the Collection View Cell and the Collection View.
Conclusion
The Collection View is insanely useful for creating interesting layouts that reuse its cells multiple times. It’s also perfect for populating dynamic data. With this lesson, you’ll be able to take your layouts to the next level.
Static Data and Reusable Cells
With a few well-structured lines of code, you can create static data that can be dynamically used for your layout. It’s especially useful for setting quick prototypes that are simple to set up for beginners and designers.
Downloads
To follow this course, you can download the source file, which will help you compare your progress.
Creating a Data Class
The first thing we need to do is to create Data.swift where we’ll put our static data. Go to your Project navigator and right-click SectionViewController.swift and New File…. Select Cocoa Touch Class and name the file Data. Whenever you’re not sure what Subclass to use, you can also select NSObject.
Data Array and Dictionary
To give a quick refresher, let’s create a simple array.
let sections = ["Title 1", "Title 2", "Title 3"]
The same array with indentation can be written like this:
let sections = [
"Title",
"Title 2",
"Title 3"
]
But that only stored a single value, so we’ll need to take it up a notch and turn it into a Dictionary by adding more values: title, caption, body, image for each card. We’re wrapping those 4 values between more brackets. Here’s the data that we need. You can copy and paste this outside the class since it would take a very long time to type everything.
let sections = [
[
"title": "Learn iOS 11 Design",
"caption": "A complete guide to designing for iOS 11",
"body": "While the flat design has become universal over the past 5 years, it wasn’t so common before iOS 7. It was the shift that shaped the design landscape. But to say that it hasn’t evolved would be inaccurate. iOS design has adapted to the bigger screens. What started as the ultimate opposite of hyper-realistic designs that preceded it, flat design is now much more nuanced, giving way to gradients, drop shadows and cards.",
"image": "ios11"
],
[
"title": "Designing for iPhone X",
"caption": "Guidelines to designing for iOS 11",
"body": "iOS 11 marks the introduction of the iPhone X, a much taller iPhone that has virtually no bezel. The 5.8-inch OLED screen is larger than the iPhone 8 Plus’s 5.5-inch, yet the body size is about the same as the iPhone 8. For designers, this means more freedom in our canvas.",
"image": "ios11-iphone-x"
],
[
"title": "Design for iPad",
"caption": "How bigger screens affect your design",
"body": "Designing for the iPad isn’t as simple as flipping a switch and just making everything bigger. A larger screen provides a real opportunity to present more content while respecting some basic rules in navigation, typography and visual hierarchy. Don’t treat the iPad as just a big iPhone. Instead, treat it more like a desktop computer with touch capabilities. In other words, your users can be more productive, see more content and perform tasks faster like typing, and drag-and-drop and multi-tasking.",
"image": "ios11-ipad"
],
[
"title": "Design for Apple Watch",
"caption": "Designing for people on the go",
"body": "Apple Watch was introduced on April 24, 2015, and it was highly anticipated by developers, designers, and the media. It was truly the first wearable that broke every expectation, placing Apple as the number one watch manufacturer in the world.",
"image": "ios11-watch"
],
[
"title": "Learn Colors",
"caption": "How to work with colors",
"body": "Colors are difficult to master because it’s really easy to go overboard. When we design, we have a tendency to over-design. For colors, we tend to use competing colors that distract and just feels completely unnatural. What I can recommend is to simply stick to the basics and temper your use of colors by focusing on its utility and pleasantness. When in doubt, use colors only to draw attention to a button or element of importance.",
"image": "ios11-colors"
]
]
Collection View Cell Class
In order to fill our data, we’ll need to connect the Card’s Storyboard objects to a Class file. Right-click SectionViewController.swift and select New File…. Select Cocoa Touch Class. Type Section as class name, then type UICollectionViewCell as the subclass of.
Connecting the Collection View Cell
Go back to Storyboard and select the Collection View Cell named sectionCell. In the Identity Inspector, set the Class to SectionCollectionViewCell. The autocompletion should help you fill the correct Class.
Manual Assistant Editor
Let’s enable the Assistant Editor by clicking on the circles icon in the top right. Since Automatic doesn’t give us the Cell Class specifically, we’ll do it manually by clicking the 4 squares icon and then, go to Recent Files, SectionCollectionViewCell.swift.
IBOutlets in Cell Class
Now, we can create the IBOutlets. Control + Drag the title, caption and cover image to create the IBOutlets in the class file.
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var captionLabel: UILabel!
@IBOutlet weak var coverImageView: UIImageView!
Populating the Data
With the data and outlets set up, we can finally populate the data in the cards. Go back to the Standard Editor and select ViewController.swift from the Project Navigator.
Array or Dictionary Count
In the Collection View’s numberOfItemsInSection function, let’s return the number of values that we have in the sections dictionary. We’re using count to calculate the number of values.
return sections.count
Populating the Cell
In cellForItemAt, we’ll configure the cell. In the cell declaration, we’ll need to specify exactly which Class we’re using so that we can have access to its outlets.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sectionCell", for: indexPath) as! SectionCollectionViewCell
Now, let’s declare a single section using the indexPath.row given to us.
let section = sections[indexPath.row]
Using the section, we can get access to the values for the title, caption and image. For the image, we needed to force unwrap, as suggested by Xcode.
cell.titleLabel.text = section["title"]
cell.captionLabel.text = section["caption"]
cell.coverImageView.image = UIImage(named: section["image"]!)
Conclusion
Boom! Our data is working and it’s filling the cards in the Chapter section. This technique will take your prototype and iOS app very far since you can update parts of the app by just modifying the data file.
3D Animation using Scroll
Even for the horizontal scrolling that happens in the Collection View, we can capture the position. In the lesson, we’ll learn how to apply a nice 3D Transform when the user scrolls the cards, giving this smooth and beautiful parallax 3D effect.
Downloads
To follow this course, you can download the source file, which will help you compare your progress.
Setting the Scroll Delegate
Let’s write an extension with that is a subclass of UIScrollViewDelegate where we’ll capture the scroll position. Looks like we’ve already done that previously in the Parallax Animation using Scroll section. To refresh your memory. Here’s the basic code:
extension ViewController: UIScrollViewDelegate { }
Also, make sure to connect that to the Scroll View object in viewDidLoad.
scrollView.delegate = self
Getting the Collection View Cell
The information that we’re looking for is the position and size of the card. To get that, we need to somehow select the Collection View Cell itself during scrolling. We know that scrollView gives us the information on scroll position, it also gives us that info when we’re scrolling inside the Collection View. So it’s safe to assume that scrollView can also become a collectionView, but only when scrolling horizontally.
Let’s declare a collectionView only when scrollView happens to be a Collection View. Type the following code inside the scrollViewDidScroll function.
if let collectionView = scrollView as? UICollectionView { }
Thanks to the collectionView, we can capture all the cells that are visible on the screen. While we’re at it, let’s make it specific to SectionCollectionViewCell so that we can use its IBOutlets. Make sure to set it as an Array since visibleCells has multiple cells and not just one. Our full code is as follow.
if let collectionView = scrollView as? UICollectionView {
for cell in collectionView.visibleCells as! [SectionCollectionViewCell] {
// Do something with the cell
}
}
Getting the Cell Frame
The road to getting the cell’s frame is a little complex, but as far as I know, this is the only way I found. First, we need to get the indexPath from the cell using the collectionView.
let indexPath = collectionView.indexPath(for: cell)!
With the indexPath, we can get the cell attributes.
let attributes = collectionView.layoutAttributesForItem(at: indexPath)!
The cell attributes doesn’t give us an accurate frame value of the cells when the scrolling happens. We’ll need to convert those values to the view.
let cellFrame = collectionView.convert(attributes.frame, to: view)
Printing the Frame Values
Let’s run the app and see what kind of values we get based on the scroll position. As you can see, we’re getting the values we’re looking for when we scroll the cards horizontally.
print(cellFrame)
Cover Image Parallax
With the frame information, we can do so much. One of the simple things we can do is moving the X position of the Cover image found inside the cell using transform. Since we don’t want it too fast, we’ll divide origin.x by 5.
let translationX = cellFrame.origin.x / 5 cell.coverImageView.transform = CGAffineTransform(translationX: translationX, y: 0)
CATransform3D Rotate
Let’s spice up the animation a little by using CATransform3D, which lets you add perspective to your transform, giving a 3 dimensional effect. This time, the transform will be applied to the layer of the object.
First, we’re using the X position to convert that into the perspective degrees. The lower the number, the sharper the angle.
let angleFromX = Double((-cellFrame.origin.x) / 10) let angle = CGFloat((angleFromX * Double.pi) / 180.0)
Let’s customize the transform matrix values. It’s pretty complex and not something I can explain, but if you wish to learn more, here’s a pretty good post.
var transform = CATransform3DIdentity transform.m34 = -1.0/1000
Finally, we’re applying the angle and transform to CATransform3DRotate, which is basically like the normal rotate, but with perspective.
let rotation = CATransform3DRotate(transform, angle, 0, 1, 0)
cell.layer.transform = rotation
This is the result so far. It looks almost identical to the official app.
Adding CATransform3D Scale
We could stop here, but let’s experiment a little with the 3D scale. You can create some really interesting effects.
Again, using the X position, we’re creating a formula that translates well into scale. The goal is to get a value between 0 and 1.
var scaleFromX = (1000 - (cellFrame.origin.x - 200)) / 1000
Then, we’re making sure that there is a maximum and minimum scale by setting these conditions.
let scaleMax: CGFloat = 1.0
let scaleMin: CGFloat = 0.6
if scaleFromX > scaleMax {
scaleFromX = scaleMax
}
if scaleFromX < scaleMin {
scaleFromX = scaleMin
}
Finally, we apply the 3D scale to our cell.
let scale = CATransform3DScale(CATransform3DIdentity, scaleFromX, scaleFromX, 1)
cell.layer.transform = scale
The result is much more pronounced in Landscape mode, so I recommend testing it in that mode. As with all these animations, you can definitely play with the numbers that match your specific need.
Applying Both 3D Rotate and Scale
Since we want to apply both rotate and scale at the same time, we just need to use CATransform3DConcat.
cell.layer.transform = CATransform3DConcat(rotation, scale)
We can remove this line because we’ve replaced it.
cell.layer.transform = rotation // remove or comment this line
Fixing First Load
As you might have noticed, the rotation and scale don’t apply until we start scrolling horizontally. That causes an issue where the animation jumps suddenly at the start.
To fix this, we need to create a function for the animation by only feeding the frame of the cell. Put this function after scrollViewDidScroll and name it animateCell. We’re going to return the CATransform3D itself so that we can use that to apply to the cell. Move the animation code that starts with let angleFromX….
func animateCell(cellFrame: CGRect) -> CATransform3D {
// Animation code
return CATransform3DConcat(rotation, scale)
}
We’re going to call the animateCell function from where we moved the code.
cell.layer.transform = animateCell(cellFrame: cellFrame)
Calling animateCell at Start
Thanks to the new animateCell function we made, we can call it as many times as we want to apply the 3D transform. It doesn’t have to be solely attached to the scroll behavior. Again, to fix our issue at the start, we’re going to call it from cellForItemAt.
cell.layer.transform = animateCell(cellFrame: cell.frame)
Collection View Cell Spacing
Because of the 3D transforms, the spacing between each card is a little too wide, so we can fix it by changing the Collection View’s Min Spacing For Lines to 0. While we’re at it, let’s change the Section Insets to 20 for the Right.
Conclusion
CATransform3D is a fantastic way to take your animation to the next level. There is so much more to explore in term of using the matrix values and playing with rotate, scale and translate. Our final result is something that will leave quite an impression on our users.
Container View
Your View Controllers will get bloated as you keep adding new things over time. One of the ways to make separate the load and the logic is to create a Container View which contains a separate View Controller. Like this, it’s easier to manage existing objects and apply new changes.
Downloads
To follow this course, you can download the source file, which will help you compare your progress.
Create an Container View
In Storyboard, drag and drop a Container View from the Object Library. Set the constraints to 0 for top, right and left. Set the height to 524.
At the bottom of the View Controller, you’ll see another one floating below. Drag that to the right so that it becomes visible.
New View Controller
The Container View creates a child View Controller that works exactly like any View Controller. The only difference is that it’s now managed outside of the parent one.
Background View
Drag and drop a UIView to the new View Controller and set the constraints to 0 for top, right, bottom and left. Set the Background color to #323843.
Collection View
For the Testimonials, we’ll use the Collection View, which will allow us to scroll a bunch of cards horizontally. If you’d like to learn more in detail about how to implement a Collection View, please head over to the Collection View section.
Let’s drag and drop a Collection View. Set the constraints to top 50, right 0, left 0 and height 235. Set the Background to Clear, Scroll Direction Horizontal and uncheck Show Horizontal Indictor.
In the Size Inspector, set the Cell Size to width 304, height 235, min spacing 20, line spacing 20, left 20, right 20.
Collection View Cell
Each reusable cell represents a testimonial. Let’s select the Collection View Cell and go to the Size Inspector tom set the Size to Custom. Set the width to 304 and the height to 235.
In the Attribute Inspector, set the Identifier to testimonialCell.
Container with Drop Shadows
Drag and drop the first UIView and set the constraints to 0 to top, right, bottom and left.
Set the Runtime Attributes in the Identity Inspector to:
- layer.cornerRadius, Number, 14
- layer.shadowOpacity, Number 0.25
- layer.shadowOffset, Size, {0, 10}
- layer.shadowRadius, Number, 20
Collection View and Cell Clip to Bounds
Drop shadows can be clipped by the Collection View and Cell, you’ll need to uncheck Clip to Bounds on both of them.
Testimonial Text
Drag and drop a UILabel and set the constraints to top 20, right 20, left 54. Set the font to 20 pt, Dark Gray and the number of lines to 0, so that it can expand to multiple lines.
Quotes
For the beginning and end quote images, we’ll go to Media Library and filter by typing quote.
Drag and drop Quote-Begin and set the constraint to top 20, left 16, width 23, height 18.
Then, drag and drop Quote-End and set the constraints to right 7, width 12, height 9. Because the quote has to follow the bottom end of the text, we’ll have to use relative constraint by Control Dragging to the testimonial text label. Select Bottom so that it always anchors against the bottom of the label.
Person Labels
For the full name, we’ll drag a UILabel and set to 15 pt Medium, black. For the job description, drag another label at 15 pt, Dark Gray. Select both labels and click on Embed in Stack. Then select the Stack View and set the constraints to right 20, bottom 14, left 54.
Labels Content
Change the label texts to “Design+Code is a wake-up call. Why should I learn a web based technology or a deprecated tool when the obvious choice is to learn Xcode?”, “Full name”, “Job”.
Notice that when you set the testimonial text to multiple lines, the end quote follows the bottom of the text.
Avatar
Drag and drop a UIImage. At first, it’s a little big, so set the size to width 24, height 24 first. Drag it to the left of the person labels. Set the constraints to right 10, width 24, height 24. Finally, Control Drag to the Stack View in the Document Outline and select Center Vertically.
AVATAR ROUNDED CORNERS
Go to the Identity Inspector and set the Runtime Attributes to layer.cornerRadius, Number, 12, which is the width of the avatar divided by 2. Whenever you want to have rounded corners, make sure to enable Clip to Bounds in the Attributes Inspector.
Stats Stack View
These are similar to to the stats found at the top of the Home screen. We’ll want to use Stack View and center it horizontally within the screen.
First, drag and drop a UILabel and set to 30 pt Semibold, white. Second, drop a UILabel at 15 pt, white with 50% opacity. Set their content to “26,000 people”, “are learning from Design+Code” respectively. Third, drag and drop a UIView and set the background color to #AF47B9 and the Runtime Attributes to layer.cornerRadius, Number, 1.5.
Finally, select all 3 elements and click on Embed in Stack. The Distribution should be Equal Centering. For the constraints, set the top to 50 and click Align to enable Horizontally in Container.
Logos Stack View
From the Media Library, we’re going to drag and drop Logo-Google, Logo-Apple and Logo-Stripe. Try to put them next to each other in a horizontal line. Then, select all 3 logos and click on Embed in Stack with the distribution set to Equal Centering and spacing set to 50. The constraints should be bottom 50, align Horizontally in Container.
Testimonial Class File
We’ll need to create a Class file for the child View Controller. Go to the Project Navigator and right-click SectionViewController.swift and select New File…. Select Cocoa Touch Class, type Testimonial in Class and make it a subclass of UIViewController.
Testimonial Collection View Cell Class
A Collection View Cell Class is also needed for each card used. Right-click TestimonialViewController.swift and select New File…. Select Cocoa Touch, type Testimonial, subclass of UICollectionViewCell.
Connect the Testimonial Class
Let’s go back to the Storyboard and select the View Controller to set its Class to TestimonialViewController in the Identity Inspector.
Connect the Cell Class
Likewise, select the Collection View Cell and set its Class to TestimonialCollectionViewCell.
Collection View IBOutlet
We’ll need to create IBOutlets for the items we want to modify, such as the testimonial text, name, job, avatar and the Collection View Cell itself.
Enable the Assistant Editor to show the Class file next to the Storyboard. Make sure to have Automatic on, so that it shows TestimonialViewController.swift. Control + drag the Collection View from the Document Outline and set the name to testimonial.
Collection View Cell IBOutlets
With the Project Navigator and Storyboard open, Option-click TestimonialCollectionViewCell.swift. This will automatically open the Class file in the Assistant Editor. This technique is especially useful when working with Cell Classes.
Let’s drag and drop the text, name, job and avatar.
Testimonial Data
Let’s set up some a Dictionary for the Testimonial data. Paste this in Data.swift.
let testimonials = [
[
"text": "Design+Code is a wake-up call. Why should I learn a web based technology or a deprecated tool when the obvious choice is to learn Xcode?",
"name": "Jean-Marc Denis",
"job": "Product Designer at Facebook",
"avatar": "avatar-jean-marc"
],
[
"text": "If you're comfortable with Framer, you can easily transfer your knowledge to Swift. You can animate pretty easily while building an app.",
"name": "Min-Sang Choi",
"job": "Interaction Designer at Google",
"avatar": "avatar-min-sang"
],
[
"text": "First of all I am 12 years old, live in Britain and I just realised that all I want to do for the rest of my life is design amazing things. (Tom Fox won a scolarship for WWDC 2015)",
"name": "Tom Fox",
"job": "Student",
"avatar": "avatar-tom-fox"
],
[
"text": "As a developer, I really appreciated the chapters on Color Theory and Typography. It was just the right balance of theory combined with practical examples.",
"name": "Chris Ching",
"job": "Teaches iOS Programming",
"avatar": "avatar-chris-ching"
],
[
"text": "I began a Swift learning plan 5 days ago, this is part of what I did in five days. I learned a lot from Meng To's Design+Code.",
"name": "MartinRGB",
"job": "Designer and Coder",
"avatar": "avatar-martin-rgb"
],
[
"text": "Thanks to Design + Code, I just released my first app on the store: Sky Graph. It's been a great learning experience as a designer learning to code.",
"name": "Wayne Sang",
"job": "Senior Product Manager at TWG",
"avatar": "avatar-wayne-sang"
],
[
"text": "I found and bought Design+Code by Meng To which takes you through each step of the process from design to code to app store submission.",
"name": "Kenny Chen",
"job": "User Experience Designer at Bankrate",
"avatar": "avatar-kenny-chen"
],
[
"text": "Thanks to @MengTo, I was able to get a rough prototype of my first app working this weekend.",
"name": "Andrew McCarthy",
"job": "Product Designer",
"avatar": "avatar-andrew-mccarthy"
]
]
Setting Up Collection View
Once the Class files and IBOutlets are created, we’re ready to configure the data source. In TestimonialViewController.swift, we’re going to subclass UICollectionViewDelegate and UICollectionViewDataSource and set the necessary Collection View setup.
extension TestimonialViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return testimonials.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "testimonialCell", for: indexPath)
return cell
}
}
Don’t forget to set the Collection View’s delegate and dataSource to self in viewDidLoad.
testimonialCollectionView.delegate = self
testimonialCollectionView.dataSource = self
Cell Configuration
In order to populate the data to our cells, we’ll need to specifically target TestimonialCollectionViewCell. Replace this line.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "testimonialCell", for: indexPath) as! TestimonialCollectionViewCell
Then, we’ll retrieve a single testimonial for each row.
let testimonial = testimonials[indexPath.row]
Finally, we can fill the correct IBOutlet with the appropriate data.
cell.textLabel.text = testimonial["text"]
cell.nameLabel.text = testimonial["name"]
cell.jobLabel.text = testimonial["job"]
cell.avatarImageView.image = UIImage(named: testimonial["avatar"]!)
Conclusion
That was a long lesson that really put to test your ability to apply knowledge from past sections such as Collection View, Static Data and Storyboard. The more hours you spend repeating these steps, the more comfortable you’ll get at implementing on your own. One of the things that really helped me get better is to execute already known lessons all by myself, using only my memory as the weapon.
The more we’ll advance, the more you’ll find that some of the steps are familiar. You should take it as a sign that you’re improving.
Passing Data
As you work with multiple View Controllers, you’ll eventually want to pass data between them. This will in turn make your app more interactive and dynamic. For example, each time you press on a card, it’ll show a screen filled with data specific to that article.
Downloads
To follow this course, you can download the source file, which will help you compare your progress.
Designing the Section Screen
We begin by applying the design in the Section View Controller in Storyboard. In summary, we need to create the title, caption, body text and cover image. In term of interactive items, we have a progress indicator in the top right and a close button on the top right.
Starting with Scroll View
Since the content will far exceed the screen height, we’ll start with the Scroll View. Drag and drop a UIScrollView from the Object library. Set the constraints to top 0, right 0, bottom 0, left 0. Make sure that they’re against the Superview.
There are 2 ways to do this. When you add new constraints, you can click on the down arrow to choose View instead. Or, You click on the parent View and uncheck Safe Area Layout Guide. Also, make sure that Constrain to Margin is disabled.
Hero Container View
The Hero View isn’t unlike the one in the Home screen. Let’s drag and drop a UIView and set the constraints to top 0, right 0, bottom 500, left 0 and height 420. It’s also important to set the Align to Horizontally in Container, otherwise the Scroll View doesn’t have a solid X position.
Background Cover Image
Drag and drop a UIImage and set its constraints to 0 on all sides: top, right, bottom and left. It’s important to ensure that the object is inside the Hero View before applying the constraints.
The image should be configured to ios11 with the Content Mode Aspect Fill.
Section Labels
Drag and drop a UILabel with the text “Learn iOS 11 Design” and set it to 32 pt Semibold white. The constraints should be top 66, left 20, width 264. Set the number of lines to 0.
Drag and drop a second UILabel with the text “Learn colors, typography and layout for iOS”, 17 pt, 80% white with the constraints bottom 30, left 20, width 264. Set the number of lines to 0 as well.
Drag and drop a third UILabel, but this time outside of the Hero View, right below it. Make the text to “Three years ago Apple…”, 19 pt Dark Gray. The constraints should be set to top 30, right 20, left 20. Again, the number of lines set to 0.
Progress Text Visual Effect View
The challenge with the progress text is that it’s wrapped inside a Visual Effect View, which needs to grow if there is more text.
First, drag and drop a UIVisualEffectView with the Blur Style set to Dark. Set the constraints to top 20, left 20, height 36.
Then, drop a UILabel inside, set to “1 / 12”, 15 pt Semibold, 70% white. Set the Align to Horizontally in Container and Vertically in Container.
Here’s the tricky part. Deselect everything by clicking outside and select the Visual Effect View and control drag to the label and click on Equal Widths. In the Size Inspector, double-click the Equal Width constraint and update the constant to 20. This will add a 10 pt padding on the left and right.
As you can see from the video below, you the View changes its width depending on the text length. Pretty cool!
Progress Rounded Corners
Let’s not forget to set the rounded corners. Select the Visual Effect View and add a new Runtime Attribute in the Identity Inspector: layer.cornerRadius, Number, 12. In the Attribute Inspector, make sure to check Clip to Bounds.
Close Visual Effect View
What’s particular with the Close button is that it won’t move as you scroll. This simply means that we have to place it outside the UIScrollView.
In the Document Outside, collapse the Scroll View to make it easier to place an object outside of it. Then, drag and drop a Visual Effect View right below it. Be careful not to place it inside. The little circle should be on the left of the icon and not on the right.
Make the Blur Style to Dark. Set the constraints to top 20, right 20, width 36, height 36 with Constrain to margins unchecked. To make the corners rounded, go to the Identity Inspector and add a Runtime Attribute: layer.cornerRadius, Number, 12. Also, check the Clip to Bounds in the Attribute Inspector.
Close Button
Drag and drop a UIButton inside the Visual Effect View and set its constraints to 0 for top, right, bottom and left. Set the text to nothing and the Image to Action-Close.
Connecting the Section’s IBOutlets
Select the Section View Controller and enable the Assistant Editor with Automatic on, so that it detects SectionViewController.swift. Control drag the objects to the Class file to set the following IBOutlets and IBAction for the close button.
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var captionLabel: UILabel!
@IBOutlet weak var coverImageView: UIImageView!
@IBOutlet weak var progressLabel: UILabel!
@IBOutlet weak var bodyLabel: UILabel!
@IBAction func closeButtonTapped(_ sender: Any) {
}
Section Data
In addition to the outlets, we need to declare the section data that we’ll receive. Notice that we’re using var because we’re anticipating that the value will be changed when passed from Home. Type this code right below the IBOutlets.
var section: [String: String]!
Using that data, we can populate our objects in viewDidLoad.
titleLabel.text = section["title"]
captionLabel.text = section["caption"]
bodyLabel.text = section["body"]
coverImageView.image = UIImage(named: section["image"]!)
Section Progress
In order to get the progress count, we’ll need two things: 1) what is the current index and 2) how many sections there are.
var sections: [[String: String]]!
var indexPath: IndexPath!
Let’s use those two values to set the text for progressLabel. Since the indexPath.row starts with 0, we’ll have to add 1 to it.
progressLabel.text = "\(indexPath.row+1) / \(sections.count)"
Segue from Home to Section
In order to navigate and pass data from one screen to another, we need to create a segue between the two View Controllers. Go to the first View Controller and control drag the View Controller icon to the Section View Controller. Select Present Modally.
Then, select the Segue connection between them and change the name to HomeToSection.
Calling the Segue from the Collection View Cell
Open your Project Navigator and go to ViewController.swift. Inside UICollectionViewDelegate, at the bottom, type didSelectItemAt to get its appropriate function from the autocompletion.
Then, do a performSegue. We’re going to use the HomeToSection identifier that we set earlier. For the sender, we’re going to send the indexPath.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
performSegue(withIdentifier: "HomeToSection", sender: indexPath)
}
Prepare Segue
While the segue is performed, you can pass the sender and other types of data using prepare. Put this code after the viewDidLoad function.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
Since you can have many segues, it’s good practice to set a condition which identifies a specific segue first.
if segue.identifier == "HomeToSection" {
}
In addition to identifier, the segue also holds the destination value, which gives us which View Controller it’s connected to. We just need to specify SectionViewController in order to gain access to its objects.
let toViewController = segue.destination as! SectionViewController
When we called performSegue, we sent indexPath as the sender. With indexPath.row, we can get a specific section.
let indexPath = sender as! IndexPath
let section = sections[indexPath.row]
Finally, we can send the 3 types of data that exist in SectionViewController.
toViewController.section = section
toViewController.sections = sections
toViewController.indexPath = indexPath
Your code should look like this. If you run the app, you’ll notice that the data is passed applied to the Section View Controller.
Dismiss Section View Controller
The close button in the Section screen doesn’t do anything for now. To make go back to Home, use the dismiss function in closeButtonTapped.
dismiss(animated: true, completion: nil)
Scroll View Content Insets
In the Section screen, you can fix extra spacing at the top of the Scroll View by going to the Storyboard, select Scroll View and set Content Insets to Never in the Size Inspector.
Safe Area Fixes
In the Section screen, we set every constraints against the Superview or parent view. That won’t look great on the iPhone X’s landscape mode. To fix this, we must enable Safe Area Layout Guide on the Scroll View and Hero View in the Size Inspector.
Then, you must select the constraints and replace Superview by Safe Area.
Conclusion
Knowing how to transfer data from one screen to another can make your app a whole lot more interesting. You can build so many types of apps with this technique: blogs, photos, playlists, etc.
Status Bar
The Status bar is ubiquitous on almost every screen. Having complete control over this piece of the puzzle is essential to giving a great user experience. It’s important that the status bar is not in the way of the content. On a light background, it must be black and on a dark background, it must be white. Sometimes, when you’re taking a photo or consuming medias, it’s okay to hide it. You also have the option to animate it when a screen is loaded or dismissed.
Downloads
To follow this course, you can download the source file, which will help you compare your progress.
Optional: Use Safe Area
The first technique is to create a top constraint margin for the Scroll View using the Safe Area. Why the Safe Area? Because the status bar is 40 on the iPhone X and 20 on all the other iPhones. Safe Area will automatically make the distinction for you. Since the background is typically white, use the default bar style instead.
override var preferredStatusBarStyle: UIStatusBarStyle {
return .default
}
In Storyboard, select the Scroll View and go to the Size Inspector to double-click on the Top Space constraint. Set the Second Item to Safe Area instead and change the constant to 0.
Optional: Use a Navigation Bar
One of the most common methods in managing the status bar is to use the Navigation Bar. This bar always sits on top of the content. Paired with a status bar, it will ensure that the text in the status bar has a background, making it legible at all times.
To create a Navigation Bar, click on the View Controller and go to Editor, Embed In, Navigation Controller. If you wish to learn more about the Navigation Bar, there is a full section on this.
Optional: Status Bar Blurred Background
Having a flat colored status bar background may be a bit boring, especially since Apple likes to use the blurred background for their status bar.
The issue with this technique is that it needs to be managed carefully since we’re essentially adding a subview. For example, when switching to landscape mode the Status Bar needs to be updated. Also, make sure to run this only once, like from viewDidLoad. Otherwise, it will keep adding new subviews on top of each other.
func addBlurStatusBar() {
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let blur = UIBlurEffect(style: .dark)
let blurStatusBar = UIVisualEffectView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: statusBarHeight))
blurStatusBar.effect = blur
view.addSubview(blurStatusBar)
}
Call the addBlurStatusBar function from viewDidLoad.
addBlurStatusBar()
Light Status Bar
By default, the status bar is set to black, which is perfect for a white background. But since our Hero View in the Home screen has a background image, the light status bar will work better. To achieve this, use the following code in ViewController.swift right below viewDidLoad.
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
The problem with this technique is that the status bar text won’t be legible on a white background, so you’ll have to rely on some scroll hacking.
Adding a Status Bar Background
You can also add a status bar background color by adding the following function below viewDidLoad.
func setStatusBarBackgroundColor(color: UIColor) {
guard let statusBar = UIApplication.shared.value(forKeyPath: "statusBarWindow.statusBar") as? UIView else { return }
statusBar.backgroundColor = color
}
Then, you can call it from viewDidLoad
setStatusBarBackgroundColor(color: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.5))
Hiding the Status Bar
When viewing a video or photo in full-screen mode, or when reading a book, it is sometimes appropriate to hide the status bar to let the user focus on the content without distraction.
In the SectionViewController.swift, write this code below viewDidLoad.
override var prefersStatusBarHidden: Bool {
return true
}
Animating the Status Bar
The Status Bar can be animated in two ways: slide and fade. To make it happen, you must first declare a Bool so that we can set it in multiple places.
var isStatusBarHidden = false
Type this function below viewDidLoad in order to update the status bar. We’re also setting the type of animation to slide.
override var prefersStatusBarHidden: Bool {
return isStatusBarHidden
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .slide
}
Then, we’ll animate it in the prepare function, at the bottom.
isStatusBarHidden = true
UIView.animate(withDuration: 0.5, animations: {
self.setNeedsStatusBarAppearanceUpdate()
})
Finally, when we’re coming back from the Section screen, we need to show the status bar again. Use viewWillAppear, since it runs before viewDidAppear and viewDidLoad.
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(true)
isStatusBarHidden = false
UIView.animate(withDuration: 0.5) {
self.setNeedsStatusBarAppearanceUpdate()
}
}
Conclusion
We’ve looked at a bunch of different ways to modify the status bar depending on the layout and navigation. This should cover 90% of the use cases. Knowing how to master the status bar seems like a basic skill, but you’d be surprised as to how many apps don’t have that figured out.
Storyboard in Playground
Storyboards are the keystone of manageable user flows. Getting to know how to layout, prototype and animate in them is fundamental to your development.
In this section, we are going to use a storyboard file inside a playground to explore how to set basic constraints, make it work with code and animate it using all of the above.
For that, we are going to leverage Interface Builder’s Outlets, Actions and Collections on the layout Constraints, Views and Gesture Recognizers. The final product is going to be an entirely functional animation built using the minimal amount of code and the maximum amount of Storyboard.
This section will also extend some concepts of the Intro to Storyboard section, and it may be advisable to review it.
Downloads
To follow this course, you can download the source file, which will help you compare your progress.
Disclaimer
This section uses an experimental technique. The errors you might encounter while performing this tutorial may be tied to the version of Xcode you are running.
Card Controller
The initial playground file has been customized to have a Storyboard file linked to a custom UI View Controller children. By triggering Command+1, you will have access to all the inner files of this project.
Below the Sources folder, there is a Card Controller swift class file. Let’s have a look:
import UIKit
open class CardController : UIViewController {
@IBOutlet public var card : UIView!
@IBOutlet public var closeButton : UIButton!
@IBOutlet public var cardConstraints : Array<NSLayoutConstraint>!
@IBAction open func tapCard (_ sender : AnyObject) {}
@IBAction open func tapClose (_ sender : AnyObject) {}
}
This class is the one from which we are going to build our View Controller used in the interaction. It has outlets for the elements we are going to animate. It also has an outlet collection for some constraints we are going to use later. And it also has actions to enable user interaction.
XIB file
Below the Sources folder, there is a resources folder that contains the resources for the close button, the chapters screen, and the section cover image. This group also contains a file called View.xib in which we are going to place our views and set some constraints and actions. When opened it displays a view customized with a blue background.
You will also see that on the left side of it there is a drawer for three items: a file owner, a first responder, and a view. You are already familiar with views, but you might be confused about what is the file owner. Select the File Owner yellow cube and, using the Identity Inspector (Option + Command + 3) check out the value of Class under Custom Class.
Some outline items represent objects that otherwise would have no visual presence, being on the View Controller. Select the Connections Inspector (Option + Command + 6) and see that this object already treats the View as its own.
View Controller
Let’s go back to the actual playground and write some code to leverage what has already been set. Begin by creating a custom View Controller that draws from the functionality we have built on the Card View Controller with the following statements:
class ViewController : CardController {}
Then, set the storyboard to instantiate the view controller into the Storyboard Live View:
PlaygroundPage.current.liveView = ViewController()
And trigger the live view by selecting the Assistant Editor and selecting Live View. It will show a colored canvas with the View Controller we have just created. Nothing else to see for now.
Loading the View
Now we are going to learn a little about what makes a storyboard file tick. Inside the View Controller class, set the following function:
override func loadView() {
Bundle.main.loadNibNamed("View", owner: self, options: nil)
}
This line is an explicit explanation of what an iOS app does under the hood when it launches. It creates a view controller and loads the view inside while also setting its properties and outlets.
Placing the elements in the view hierarchy
Views are like layers and folders at the same time as they have properties and may have children. If you use Sketch, they correlate to a Symbol or an Artboard. If you use Framer, they act like Layers. Let’s go back to our View.xib file and place some elements.
Firstly, we might want to set a White Color for our background. Then, select the View and, under the Size Inspector, deactivate Safe Area Layout Guide. Also, lay the Chapter Screen.png centered inside.
Cover Image
Continue by also placing an Image View and using the Size Inspector, change the x 20, y 256, width 300 and height 250. Under the Attribute Inspector, set the image field to be Cover.jpg, change the content mode to Aspect Fill so the image stays in ratio while also covering its dimensions and activate Clip to Bounds, so the image does not show outside of its bounds.
Using the Identity Inspector, set its label to Cover Image under the Document section.
As we are using an experimental technique, you should save the file using Command + S and close the playground. Then, reopen it and go back to the playground to see it show.
Building up the hierarchy
Let’s examine the transition we are trying to replicate. Although it only has two states, we have to read and translate the layout precisely to constraints.
OBJECT LIBRARY
The objective is to use the Object library to place some labels, buttons and views corresponding to the card, close button, title and subtitle.
LAYOUT
In this part, we are going to translate the current layout into a constraint system layout that will help us build a multi-state interface that works on many screen sizes.
In this section, we encourage you to use the Add New Constraints dialog. It’s a helper that makes it easy to create constraints simultaneously and helps avoid unwanted behavior in Xcode’s automatic layout resolution.
CARD
Let’s start by placing a View on top of the Cover Image. Select the Cover Image and trigger Editor > Embed In > View from the menu bar. Set its background color to White color and activate Clip To Bounds. Also, constrain it by left 20, top 256, width 300 and height 250.
That is going to be our card and contain all of its subviews. Using the Identity Inspector set the label field to Card View and create a Runtime Attribute of layer.cornerRadius valued to the Number 14
Finish by moving the Cover Image to its center and constraining it to all sides.
TITLE
Then we place the title using a Label. Filter for Label and place one under Card and edit its content to “Storyboard in Playground”. Change it to be a White Color 32 point System Font Bold. Also, set its number of lines to 0 so that it can resize itself.
Center it on the card and constrain it 16 points from the top, left and right. Let’s name it Title in the Identity inspector.
SUBTITLE
Do the same for the subtitle. Filter for Label and place one under Card and edit its content to “A deeper look at Storyboards and the view hierarchy”. Change it to be a White Color 17 point System Font. Also, set its number of lines to 0 so that it can resize itself.
Center it on the card and constrain it 16 points from the bottom, left and right. Select its bottom constraint and, using the Size Inspector, set its relationship to the Cover Image.
Let’s label it Subtitle on the Identity inspector.
CLOSE BUTTON
Now, we place the button filtering Button on the Object library. Remove the text from it and constrain it at 28 points in width and height and ** 20 ** points from the right and top. Also, set its image to be [email protected]. Don’t forget to set its initial state to hidden under the ** Attributes inspector**.
To make it more visible, let’s set a background color of 50% Black and, using the Identity Inspector, set a Runtime Attribute of layer.cornerRadius to the Number 14. Also, set its label to Close Button.
Labeling Constraints
We have created a lot of constraints for out card view. When doing a complex layout, the Document Outline may get a little out of hand when we reach some 20 constraints. To help manage that we are going to label them for easy identification.
CARD LAYOUT CONSTRAINTS
Let’s begin by selecting the constraints for the top, leading, width and height of the ** Card View** and changing their names to Card Layout Top, Card Layout Leading, Card Layout Width, and Card Layout Height.
COVER IMAGE CONSTRAINTS
Do that same process for the Cover Image constraints. Rename them to Cover Image Top, Cover Image Leading, Cover Image Trailing, and Cover Image Bottom.
Opening the card
Similarly to the last part, we are going to lay a couple of constraints that describe layout. Only this time, the full-screen layout. These new constraints are going live harmoniously amongst all the previous ones.
To make this possible, we are going to uninstall some of the constraints we already have, create new ones, decrease their priority and reinstall the uninstalled constraints. This process will help us build a conclusive layout using the minimum required information with no conflict.
FULLSCREEN CARD
Select all the Card Layout and Cover Image constraints and uninstall them simultaneously using the Attributes Inspector.
RESIZE THE CARD
Resize the Card so it fills all the canvas size and set its constraints on top, bottom, leading and trailing to Superview. Prioritize them at 999 so they don’t conflict with the constraints we just turned off.
COVER IMAGE
Select the Cover Image and constrain it to 420 height. Then edit this constraint to be Less Than or Equal to 420.
The Cover image now grows vertically to as much as 420 when the card unfolds.
FITTING
Then, simultaneously reinstall the leading, trailing and top constraints of the Cover image. Also, reinstall the bottom constraint, but first set its priority to 998.
The Cover image now has the same size as the folded card without competing with other constraints such as its maximum height or its superview’s height.
THE SWITCHING STATES
To test how the layout works, we could try a neat trick. Installing and uninstalling the Interface Builder.
Select all the Card Layout constraints, and install and uninstall simultaneously using the Attributes Inspector. We can see how the layout is going to look at the two states of the animation.
Nice, right?
Controller
We’ve already established all we need for the animation to work. Now, we need to wire it to our controller.
Classes declared inside a playground file are not yet available to a .xib file inside a Playground. To work around this, we have already declared a subclass of UIViewController on a source file under the playground sources. It’s the Card Controller we have already reviewed in the previous part.
@IBOutlet public var card : UIView!
@IBOutlet public var closeButton : UIButton!
@IBOutlet public var cardConstraints : Array<NSLayoutConstraint>!
@IBAction open func tapCard (_ sender : AnyObject) {}
@IBAction open func tapClose (_ sender : AnyObject) {}
This class is the open class from which we already derived our own. It’s solely a bridge between the .xib file and the playground’s main file. Apart from that, you might already know how to link elements to corresponding @IBOutlet and @IBAction statements. These statements are the hints Xcode uses to determine what variables and functions Interface Builder may set up.
Let’s go back to the View.xib file and wire things up. In the Outline view, we already have the File’s Owner set to be a Card Controller. Let’s complete the required connections.
CONNECTIONS INSPECTOR
Select the Connections Inspector with the File Owner selected. There should be a lot of connections, many of those placed by us.
Connect the card, closeButton and view to its corresponding counterparts on the Outline.
OUTLET COLLECTIONS
Under Outlet Collections, you will see there is a cardConstraints. This item is a special outlet for it can connect to many items. Connect it to all Card Layout constraints.
CLOSE BUTTON
Let’s also connect our tapClose action to the Close Button on the Outline using the event Touch Up Inside.
TAP GESTURE
The last step is to go to the Object Library and filter on Tap Gesture Recognizer. Move it to the outline on top of the Card view. It should automatically be connected as a gesture recognizer to the Card. Also, connect its selector to the tapCard: action on File’s Owner.
To have the playground acknowledge all the changes we have made to the View.xib file, save it and close the playground. Reopen it and go back to the playground’s main file and trigger the Live View.
Activating and Deactivating Constraints
We have everything from Card Controller file wired up to View.xib.
Bundle.main.loadNibNamed("View", owner: self, options: nil)
As you may remember, upon loading the view from the View.xib file, we have it set as the File Owner. This will automatically give us access to the attributes such as card, closeButton, cardConstraints, and receive tapCard: and tapClose: actions.
The connections, view creation, and layout are what essentially happens under the hood when using Storyboards.
OVERRIDING FUNCTIONS
We’ve already tested and proved that by installing and uninstalling certain constraints, we can switch between the closed and open state of the card. Now, we need to find a way to do just that on our view controller. A way to do it is to override the tapCard: and tapClose: function. Begin by setting their overrides inside the View Controller class:
override public func tapCard ( sender : AnyObject) {
}
override public func tapClose ( sender : AnyObject) {
}
OPENING
For it to unfold, the Card Layout Constraints have to be deactivated when the card is tapped. Symmetrically, we reactivate them to fold it back again. Luckily, we have already set a card constraints variable.
Deactivate them inside tapCard: like this:
NSLayoutConstraint.deactivate(cardConstraints)
closeButton.isHidden = false
Activate them back again in tapClose like this:
NSLayoutConstraint.activate(cardConstraints)
closeButton.isHidden = true
In this part, you can see that the View Controller will activate and deactivate the constraints that make the layout look like a card according to the action that was called while also showing and hiding the closeButton.
FINER DETAILS
Our interaction already opens and closes the card. Still, we have some layout inconsistencies while also doing the interaction with no animation. Let’s fix that.
In the tapCard function we should animate like this:
let animator = UIViewPropertyAnimator(duration: 0.7, dampingRatio: 0.7) {
self.closeButton.layer.opacity = 1
self.card.layer.cornerRadius = 0
self.card.layoutIfNeeded()
}
animator.startAnimation()
Symmetrically, let’s do the opposite for tapClose:
let animator = UIViewPropertyAnimator(duration: 0.7, dampingRatio: 0.7) {
self.closeButton.layer.opacity = 0
self.card.layer.cornerRadius = 14
self.card.layoutIfNeeded()
}
animator.startAnimation()
This View Property Animator will interpolate all the values changes inside it. Additionally, when UI Kit tries to layout the card on layout if needed, it also animates the constraint changes.
Conclusion
iOS and Xcode have a lot of untapped possibilities and functionalities, among those is the use of Storyboards in Playgrounds as a method of prototyping and learning about how the technology works.
In this section, we’ve learned not only how to coordinate those two features together but also demystifies some of the allure behind how Interface Builder’s Outlets and Actions work. We also saw how solving layouts and layout animations is part of Auto Layout functionality.
Don’t forget to review the final playground and see if you can improve. There are countless ways to do the same layout, all with its potential and drawbacks.