Update note: This storyboards tutorial was updated to iOS 8 and Swift by Caroline Begbie. Original post by Tutorial Team member Matthijs Hollemans.
Storyboards are an exciting feature first introduced with iOS 5 that save you a lot of time building user interfaces for your apps.
To show you what a storyboard is, I’ll let a picture do the talking. This is the storyboard that you will be building in this tutorial:
You may not know exactly yet what the app does but you can clearly see which screens it has and how they are related. That is the power of using storyboards.
If you have an app with many different screens then storyboards can help reduce the amount of glue code you have to write to go from one screen to the next. In the old days, developers used to create a separate interface file (also knows as “nib” or “xib” files) for the design of each view controller. But now your app can use a single storyboard that contains the designs of all of these view controllers and the relationships between them.
Storyboards have a number of advantages:
- With a storyboard you have a better conceptual overview of all the screens in your app and the connections between them. It’s easy to keep track of everything because the entire design can be contained in a single file rather than spread out over many separate nibs.
- Storyboards can describe the transitions between the various views. These transitions are called “segues” and you create them by connecting your view controllers right in the storyboard. Thanks to segues you need less code to take care of your UI.
- Storyboards make working with table views a lot easier with prototype cells and static cells features. You can design your table views almost completely in the storyboard editor, something else that cuts down on the amount of code you have to write.
- Storyboards make it easier to use Auto Layout, a feature that allows you to define mathematical relationships between elements that defined their position and sizing. This powerful feature makes it much easier to handle devices of varying screen sizes and dimensions. It is outside of scope for this tutorial, but you can read more about it in Beginning Auto Layout.
If you’re the type who hates Interface Builder and who really wants to create the entire UI programmatically, then storyboards are probably not for you. Personally, I prefer to write as little code as possible — especially UI code! — so this tool is a welcome addition to my arsenal.
And if you want to use nibs then go right ahead, but know that you can combine storyboards with nibs. It’s not an either-or situation.
In this storyboards tutorial you’ll take a look at what you can do with storyboards. You’re going to build a simple app that lets you create a list of players and games, and rate their skill levels. In the process, you’ll learn the most common tasks that you’ll be using storyboards for.
Getting Started
Fire up Xcode and create a new project. Use the Single View Application template as the starting point.
Fill in the template options as follows:
- Product Name: Ratings
- Organization Name: fill this in however you like
- Company Identifier: the identifier that you use for your apps, in reverse domain notation
- Language: Swift
- Devices: iPhone
- Use Core Data: not checked
After Xcode has created the project, the main Xcode window looks like this:
The new project consists of two classes, AppDelegate and ViewController, and the star of this tutorial: the Main.storyboard file.
This is a portrait-only app, so before you continue, uncheck the Landscape Left and Landscape Right options under Deployment Info > Device Orientation seen in the General project settings shown above .
Let’s take a look at that storyboard. Click Main.storyboard in the project navigator to open it in Interface Builder:
The official storyboard terminology for a view controller is “scene”, but you can use the terms interchangeably. The scene is what represents a view controller in the storyboard.
Here you see a single view controller containing an empty view. The arrow pointing at the view controller from the left indicates it is the initial view controller to be displayed for this storyboard.
Designing a layout in the storyboard editor is done by dragging controls from the Object Library (see bottom-right corner) into your view controller. You’ll see how easy that is just ahead.
Note: You’ll notice that the default scene size is a square. Xcode 6 enables Auto Layout and Size Classes by default for storyboard and nib files. Auto Layout and Size Classes are cool new technologies for making flexible user interfaces that can easily resize, which is useful for supporting the various sizes of iPhones and iPads.
Auto Layout was introduced in iOS 6, and Size Classes in iOS 8. They do have a bit of a learning curve, which is why you’re not using them in this tutorial, but you’ll definitely want to get your head around them so that you’ll be able to support the different device sizes. To learn more about size classes, see our book iOS 8 by Tutorials.
Before you get to exploring, disable Auto Layout and Size Classes from the File inspector for this storyboard as seen below.
When prompted, keep size class data for iPhone, and click Disable Size Classes:
The scene is now the size of a 4-inch iPhone.
To get some feel for how the storyboard editor works, drag some controls from the Object Library in the lower right into the blank view controller:
As you drag the controls in, they should show up on the Document Outline on the left:
If you don’t see a Document Outline, click this button at the bottom left of the storyboard canvas:
The storyboard shows the contents of all your view controllers. Currently there is only one view controller (or scene) in your storyboard, but over the course of this tutorial you’ll be adding several others.
There is a miniature version of this Document Outline above the scene called the Dock:
The Dock shows the top-level objects in the scene. Each scene has at least a View Controller object, a First Responder object, and an Exit item, but it can potentially have other top-level objects as well. The Dock is convenient for making connections to outlets and actions. If you need to connect something to the view controller, you can simply drag to its icon in the Dock.
Note: You probably won’t be using the First Responder very much. This is a proxy object that refers to whatever object has first responder status at any given time. As an example, you can hook up the Touch Up Inside event from a button to First Responder’s cut:
selector. If at some point a text field has input focus then you can press that button to make the text field, which is now the first responder, cut its text to the pasteboard.
Run the app and it should look exactly like what you designed in the editor (yours may look different than the screenshot below – this is just for demonstration and will not be used later in the tutorial):
The single View Controller you defined was set as the Initial View Controller – but how did the app load it? Take a peek at the application delegate to find the answer. Open up AppDelegate.swift and you’ll see the source starts with this:
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool { // Override point for customization after application launch. return true } |
The @UIApplicationMain
attribute at the top of the file designates the AppDelegate class as the entry point for the module. It is a requirement for using storyboards that your application delegate inherits from UIResponder
and that it has a UIWindow
property. All the methods are practically empty. Even application(_:didFinishLaunchingWithOptions:)
simply returns true.
The secret is in the Info.plist file. Click on Info.plist (you can find it in the Supporting Files group) and you’ll see this:
Storyboard apps use the key UIMainStoryboardFile
, also known as “Main storyboard file base name”, to specify the name of the storyboard that must be loaded when the app starts. When this setting is present, UIApplication
will load the named storyboard file, automatically instantiate the “Initial View Controller” from that storyboard, and then put that controller’s view into a new UIWindow
object.
You can also see this in the Project Settings under the General tab and Deployment Info section:
Now to create the real Ratings app with several view controllers.
Just Add It To My Tab
The Ratings app you’re about to build has a tabbed interface with two screens. With a storyboard it is really easy to create tabs.
You’ll want to start with a clean storyboard, so switch back to Main.storyboard and delete the view controller you worked with earlier. This can be done by clicking on View Controller
in the Document Outline and pressing the Delete key.
Drag a Tab Bar Controller from the Object Library into the canvas. You may want to maximize your Xcode window first, because the Tab Bar Controller comes with two view controllers attached and you’ll need some room to maneuver. You can zoom in and out by double-clicking the canvas, or you can set the zoom scale by ctrl-clicking the canvas and selecting the zoom level.
The new Tab Bar Controller comes pre-configured with two additional view controllers – one for each tab. UITabBarController is a so-called container view controller because it contains one or more other view controllers. Two other common containers are the Navigation Controller and the Split View Controller (you’ll use the Navigation Controller later).
The container Relationship is represented by the arrows between the Tab Bar Controller and the view controllers that it contains. An embed Relationship in particular is signified by the icon seen below in the middle of the arrow body.
Note: If you want to move the Tab Bar Controller and its attached view controllers as a group, zoom out, and then you can ⌘-click or click and drag to select multiple scenes. This makes it possible to move them around together. (Selected scenes have a thin blue outline.)
Drag a label into the first view controller (currently titled “Item 1″), double click it, and give it the text “First Tab”. Also drag a label into the second view controller (“Item 2″) and give it the text “Second Tab”. This allows you to actually see something happen when you switch between the tabs.
Note: You can’t drag stuff into the scenes when the editor is zoomed out. You’ll need to return to the normal zoom level first by double-clicking in the canvas.
Build & Run, and you’ll see something similar to this in the console:
Ratings[18955:1293100] Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?
Fortunately, the error is pretty clear here – you never set an entry point, meaning you didn’t set the Initial View Controller after you deleted the scene used earlier. To fix this, select the Tab Bar Controller and go to the Attributes Inspector. Check the box that says Is Initial View Controller.
In the canvas, the arrow that used to point to the deleted view controller now points at the Tab Bar Controller:
This means that when you run the app, UIApplication
will make the Tab Bar Controller the main screen. Run the app and try it out. The app now has a tab bar and you can switch between the two view controllers with the tabs:
Tip: To change the initial view controller, you can also drag the arrow between view controllers.
Xcode actually comes with a template for building a tabbed app (unsurprisingly called the Tabbed Application template) that you could have used, but it’s good to know how this works so you can also create a Tab Bar Controller by hand if you have to.
Note: If you connect more than five scenes to the Tab Bar Controller, it automatically gets a More… tab when you run the app. Pretty neat!
Adding a Table View Controller
The two scenes that are currently attached to the Tab Bar Controller are both regular UIViewController
instances. You are going to replace the scene from the first tab with a UITableViewController
instead.
Click on that first view controller in the Document Outline to select it, and then delete it. From the Object Library drag a new Table View Controller into the canvas in the place where that previous scene used to be:
Next, you want to place the Table View Controller inside a navigation controller. With the Table View Controller selected, choose Editor\Embed In\Navigation Controller from Xcode’s menubar. This adds yet another controller to the canvas:
You could also have dragged in a Navigation Controller from the Object Library and embedded the tableview, but this Embed In command is a nice time saver for a common action.
Because the Navigation Controller is also a container view controller (just like the Tab Bar Controller), it has a relationship arrow pointing at the Table View Controller. You can also see these relationships in the Document Outline:
Notice that embedding the Table View Controller gave it a navigation bar. Interface Builder automatically put it there because this scene will now be displayed inside the Navigation Controller’s frame. It’s not a real UINavigationBar
object, but a simulated one.
If you look at the Attributes inspector for the Table View Controller, you’ll see the Simulated Metrics section at the top:
“Inferred” is the default setting for storyboards and it means the scene will show a navigation bar when it’s inside of a Navigation Controller, a tab bar when it’s inside of a Tab Bar Controller, and so on. You could override these settings if you wanted to, but keep in mind they are here only to help you design your screens. The Simulated Metrics aren’t used during runtime; they’re just a visual design aid that shows what your screen will end up looking like.
Next you need to connect these two new scenes to the Tab Bar Controller. Ctrl-drag from the Tab Bar Controller to the Navigation Controller. When you let go, a small popup menu appears. Choose the Relationship Segue – view controllers option:
This creates a new relationship arrow between the two scenes. This is also an embed Relationship as you saw with the other controllers contained by the Tab Bar Controller:
The Tab Bar Controller has two such relationships, one for each tab. The Navigation Controller itself has an embed Relationship with the Table View Controller.
When you made this new connection, a new tab was added to the Tab Bar Controller, simply named “Item”. For this app, you want this new scene to be the first tab, so drag the tabs around to change their order:
Run the app and try it out. The first tab now contains a table view inside a navigation controller.
Before you put some actual functionality into this app, you need to clean up the storyboard a little. You will name the first tab “Players” and the second “Gestures”. You don’t change this on the Tab Bar Controller itself, but in the view controllers that are connected to these tabs.
As soon as you connect a view controller to a Tab Bar Controller, it is given a Tab Bar Item object which you can see in the Document Outline or the bottom of the scene. You use this Tab Bar Item to configure the tab’s title and image seen on the Tab Bar Controller.
Select the Tab Bar Item inside the Navigation Controller, and in the Attributes inspector set its Title to Players:
Rename the Tab Bar Item for the view controller from the second tab to Gestures in the same manner.
A well-designed app should also put some pictures on these tabs. The resources for this tutorial contains a subfolder named Images. Drag that folder into the project, select ‘Copy items if needed’, and click Finish:
In the Attributes inspector for the Players Tab Bar Item, choose the Players.png image.
You probably guessed it, but give the Gestures item the image Gestures.png.
A view controller that is embedded inside a Navigation Controller has a Navigation Item that is used to configure the navigation bar. Select the Navigation Item for the Table View Controller (you can find it in the Document Outline) and change its title in the Attributes inspector to Players.
Alternatively, you can double-click the navigation bar and change the title there. Note that you should double-click the simulated navigation bar in the Table View Controller, not the actual Navigation Bar object in the Navigation Controller.
Run the app and marvel at your pretty tab bar, created without writing a single line of code!
Prototype Cells
Prototype cells allow you to easily design a custom layout for your table view cells directly from within the storyboard editor.
The Table View Controller comes with a blank prototype cell. Click on that cell to select it and in the Attributes inspector set the Style option to Subtitle. This immediately changes the appearance of the cell to include two labels.
With so much stackable content on a storyboard, it can sometimes be difficult to click on exactly what you want. If you have trouble, there are several options. One is that you can select the item in the Document Outline to the left of the canvas. The second is a handy hotkey: hold control + option + shift and click on the area you’re interested in. A popup will appear allowing you to select any element directly under your cursor. That latest – starting in Xcode 6 – is that you can single click multiple times to cycle through the different layers.
If you’ve used table views before and created your own cells by hand, you may recognize this as the UITableViewCellStyle.Subtitle
style. With prototype cells you can either pick one of the built-in cell styles as you just did, or create your own custom design (which you’ll do shortly).
Set the Accessory attribute to Disclosure Indicator and in the Identifier field type PlayerCell. All prototype cells are still regular UITableViewCell
objects and therefore should have a reuse identifier.
Run the app, and… nothing has changed. That’s not so strange: you still have to make a data source for the table so it will know what rows to display. That’s exactly what you’re going to do next.
Add a new file to the project. Choose the Cocoa Touch Class template under iOS/Source. Name the class PlayersViewController and make it a subclass of UITableViewController. The Also create XIB file option should be unchecked because you already have the design of this view controller in the storyboard. No nibs today! Choose the Swift language and hit Next followed by Create.
Go back to the storyboard and select the Table View Controller (make sure you select the actual view controller and not one of the views inside it). In the Identity inspector, set its Class to PlayersViewController. That is the essential step for hooking up a scene from the storyboard with your custom view controller subclass. Don’t forget this or your class won’t be used!
From now on when you run the app that table view controller from the storyboard is an instance of the PlayersViewController
class.
The table view will display a list of players, so now you will create the main data model for the app – an array that contains Player objects. Add a new file to the project using the Swift File template under iOS/Source. Name the file Player.
Add the following to the end of Player.swift:
import UIKit class Player: NSObject { var name: String var game: String var rating: Int init(name: String, game: String, rating: Int) { self.name = name self.game = game self.rating = rating super.init() } } |
There’s nothing special going on here. Player
is simply a container object for these three properties: the name of the player, the game they’re playing, and a rating of 1 to 5 stars.
You’ll next make an array of test Player
objects and then assign it to an array in PlayersViewController
. Start by creating a new file using the Swift File template named SampleData. Add this to the end of SampleData.swift:
//Set up sample data let playersData = [ Player(name:"Bill Evans", game:"Tic-Tac-Toe", rating: 4), Player(name: "Oscar Peterson", game: "Spin the Bottle", rating: 5), Player(name: "Dave Brubeck", game: "Texas Hold 'em Poker", rating: 2) ] |
Here you’ve defined a constant called playersData and assigned an array of hard coded Player objects to it.
Now add a Player array property just below class PlayersTableViewController: UITableViewController
in PlayersViewController.swift to hold the list of players:
var players: [Player] = playersData |
You could simply have set up the sample data in PlayersViewController when defining the players variable. But because this data might later be provided from a plist or an SQL file, it’s wise to handle loading the data outside of the view controller.
Now that you have an array full of Player
objects, you can continue hooking up the data source in PlayersViewController
. Still in PlayersViewController.swift, replace the table view data source methods with the following:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return players.count } |
The real work happens in cellForRowAtIndexPath
. Replace this method, which is currently commented out, with:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("PlayerCell", forIndexPath: indexPath) as UITableViewCell let player = players[indexPath.row] as Player cell.textLabel?.text = player.name cell.detailTextLabel?.text = player.game return cell } |
The method dequeueReusableCellWithIdentifier(_:forIndexPath:)
will check to see if there is an existing cell that can be recycled. If not, it will automatically allocate a prototype cell and return it to you. All you need to do is supply the re-use identifier that you set on the prototype cell in the storyboard editor – in this case PlayerCell. Don’t forget to set that identifier, or this little scheme won’t work!
Run the app, and lo and behold, the table view has players in it!
It takes just a few lines of code to use these prototype cells. I think that’s just great!
Note: In this app you’re using only one prototype cell but if your table needs to display different kinds of cells then you can simply add additional prototype cells to the storyboard. You can either duplicate the existing cell to make a new one, or increment the value of the Table View’s Prototype Cells attribute. Be sure to give each cell its own re-use identifier, though.
Designing Your Own Prototype Cells
Using a standard cell style is OK for many apps, but for this app you want to add an image on the right-hand side of the cell that shows the player’s rating (one to five stars). Having an image view in that spot is not supported by the standard cell styles, so you’ll have to make a custom design.
Switch back to Main.storyboard, select the prototype cell in the table view, and on the Attributes inspector, set its Style attribute to Custom. The default labels now disappear.
First make the cell a little taller. Either drag its handle at the bottom or change the Row Height value in the Size inspector (after checking Custom). Make the cell 55 points high.
Drag two Label objects from the Objects Library into the cell and place them roughly where the standard labels were previously. Just play with the font and colors in the Attributes Inspector and pick something you like. Set the text of the top label to Name
and the bottom label to Game
.
Drag an Image View into the cell and place it on the right, next to the disclosure indicator. Make it 81 points wide; the height isn’t very important. Set its Mode to Center (under View in the Attributes inspector) so that whatever image you put into this view is not stretched.
Make the labels 190 points wide by setting their width under the View section in the Size Inspector. The labels shouldn’t overlap the image view. The final design for the prototype cell looks something like this:
Because this is a custom designed cell, you can no longer use UITableViewCell
’s textLabel
and detailTextLabel
properties to put text into the labels. These properties refer to labels that aren’t on this cell anymore; they are only valid for the standard cell types. Instead, you will use tags to find the labels.
You could alternatively create a custom class that inherits from UITableViewCell and contains properties corresponding to the labels on your cell view. Tags are used here for simplicity, and they’re a perfectly good solution for simple cases. However, later in this tutorial you’ll experiment with the custom class method.
In the Attributes inspector, set the tag value for the Name label to 100, Game label to 101, and the Image View label 102.
Then open PlayersViewController.swift and add a new method called imageForRating
at the end as follows:
func imageForRating(rating:Int) -> UIImage? { switch rating { case 1: return UIImage(named: "1StarSmall") case 2: return UIImage(named: "2StarsSmall") case 3: return UIImage(named: "3StarsSmall") case 4: return UIImage(named: "4StarsSmall") case 5: return UIImage(named: "5StarsSmall") default: return nil } } |
Pretty simple – this returns a different star image depending on the rating. Still in PlayersViewController, change tableView(_:cellForRowAtIndexPath:)
to the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("PlayerCell", forIndexPath: indexPath) as UITableViewCell //1 let player = players[indexPath.row] as Player //2 if let nameLabel = cell.viewWithTag(100) as? UILabel { //3 nameLabel.text = player.name } if let gameLabel = cell.viewWithTag(101) as? UILabel { gameLabel.text = player.game } if let ratingImageView = cell.viewWithTag(102) as? UIImageView { ratingImageView.image = self.imageForRating(player.rating) } return cell } |
Here’s the breakdown of what you’ve done:
dequeueReusableCellWithIdentifier
will dequeue an existing cell with the reuse identifierPlayerCell
if available or create a new one if not.- You look up the
Player
object corresponding to the row being populated and assign it toplayer
. - The labels and images are looked up by their tag on the cell and populated with data from the
player
object.
That should do it. Now run the app again, and it will probably look something like this:
Hmm, that doesn’t look quite right – the cells appear to overlap one another. You did change the height of the prototype cell, but the table view doesn’t take that into consideration. There are two ways to fix it: you can change the table view’s Row Height attribute, or implement the tableView(tableView:heightForRowAtIndexPath:)
method. The former is fine in this case because we only have one type of cell and we know the height in advance.
Note: You would use tableView(tableView:heightForRowAtIndexPath:)
if you did not know the height of your cells in advance, or if different rows can have different heights.
Back in Main.storyboard, in the Size inspector of the Table View, set Row Height to 55:
If you run the app now, it looks a lot better!
By the way, if you changed the height of the cell by dragging its handle rather than typing in the value, then the table view’s Row Height property was automatically changed too. So it may have worked correctly for you the first time around.
Using a Subclass for the Cell
The table view already works pretty well but I’m not a big fan of using tags to access the labels and other subviews of the prototype cell. It would be much more clean if you could connect these labels to outlets and then use the corresponding properties. As it turns out, you can.
Add a new file to the project, with the Cocoa Touch Class template. Name it PlayerCell and make it a subclass of UITableViewCell. Don’t check the option to create a XIB, as you already have the cell in your storyboard.
Add these properties in the PlayerCell class, just below the class definition:
@IBOutlet weak var gameLabel: UILabel! @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var ratingImageView: UIImageView! |
All these variables are IBOutlets
, which can be connected up to your scene in the storyboard.
Back in Main.storyboard, select the prototype cell PlayerCell and change its class to PlayerCell on the Identity inspector. Now whenever you ask the table view data source for a new cell with dequeueReusableCellWithIdentifier(_:forIndexPath:)
, it will return a PlayerCell
instance instead of a regular UITableViewCell
.
Note that you gave this class the same name as the reuse identifier – they’re both called PlayerCell – but that’s only because I like to keep things consistent. The class name and reuse identifier have nothing to do with each other, so you could name them differently if you wanted to.
Now connect the labels and the image view to these outlets. Navigate to the Connections Inspector in the storyboard and then select the Player Cell from either the canvas or Document Outline. Drag from the nameLabel Outlet in the Connections inspector to the Name label object in either the Document Outline, or the canvas. Repeat for gameLabel and ratingImageView.
Important: You should hook up the controls to the table view cell, not to the view controller! You see, whenever your data source asks the table view for a new cell with dequeueReusableCellWithIdentifier
, the table view doesn’t give you the actual prototype cell but a copy (or one of the previous cells is recycled if possible).
This means there will be more than one instance of PlayerCell
at any given time. If you were to connect a label from the cell to an outlet on the view controller, then several copies of the label will try to use the same outlet. That’s just asking for trouble. (On the other hand, connecting the prototype cell to actions on the view controller is perfectly fine. You would do that if you had custom buttons or other UIControls
on your cell.)
An alternative to using the Connections inspector is to ctrl-drag from the PlayerCell to the control, and choose the outlet name from the popup menu:
Now that you’ve hooked up the properties, you can simplify the data source code a bit. Change tableView(_:cellForRowAtIndexPath:)
to:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("PlayerCell", forIndexPath: indexPath) as PlayerCell let player = players[indexPath.row] as Player cell.nameLabel.text = player.name cell.gameLabel.text = player.game cell.ratingImageView.image = imageForRating(player.rating) return cell } |
That’s more like it. You now cast the object that you receive from dequeueReusableCellWithIdentifier
to a PlayerCell
, and then you can simply use the properties that are wired up to the labels and the image view. Isn’t it great how using prototype cells makes table views a whole lot less messy?
Run the app and try it out. It should still look the same as before, but behind the scenes it’s now using your own table view cell subclass!
Where To Go From Here?
Click here to download the full source code for the project up to this point.
Check out part two of this tutorial, where we’ll cover segues, static table view cells, the Add Player screen, a game picker screen, and the full downloadable example project for this tutorial!
If you felt lost at any point during this tutorial, you also might want to brush up on the basics with our iOS Apprentice series. In that series, you’ll learn the foundational knowledge you need as an iOS developer from the ground up — perfect for complete beginners, or those looking to fill in some gaps.
If you have any questions or comments on this tutorial or on storyboards, please join the forum discussion below!
Storyboards Tutorial in Swift: Part 1 is a post from: Ray Wenderlich
The post Storyboards Tutorial in Swift: Part 1 appeared first on Ray Wenderlich.