#how to embed stack view in xcode
Explore tagged Tumblr posts
Video
youtube
Introduction to Auto Layout with Xcode (Part-3) - Stack View
#Auto Layout stack view#stack view in ios#stack view in xcode#UIStackView#how to embed stack view in ios#how to embed stack view in xcode
0 notes
Text
Desktop Application Development – Graphical User Interface
.embed-container { position: relative; padding-bottom: 56.25%; height: auto; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
(adsbygoogle = window.adsbygoogle || []).push({});
Hidden discounts at DbSchema. *********Click here for Recommended Books************* | | | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ujbandara@ youpul@
Skype – ujbandara . . desktop application development tools , desktop application development using php , desktop application development services , desktop application development using python , windows desktop application development , desktop application development software , desktop application development in c%23 tutorial , desktop application development company
Desktop App Development – Windows app development This documentation provides info about developing applications and drivers for the Windows operating system. The Win32 and COM application
HTML5 App Development for Desktop and Mobile. JavaScript … Creates development frameworks and tools that help design, develop and deploy applications for desktop and mobile devices.
Python Desktop Application Development � Pluralsight Training 4 Sep 2014 … This course demonstrates how you can easily create desktop applications using the simplicity of Python and the power of Qt.
TideSDK | Create multi-platform desktop apps with HTML5, CSS3 … Develop Once, Deploy Everywhere – Mobile, Web and Desktop. Have a … Wunderlist MacOS desktop app by 6Wunderkinder created width TideSDK.
Desktop Application Development � MacOSX, Windows and Linux … Jacob is an experienced Desktop Developer. Read his story about the history of Desktop Development and his experience with TideKit. Read it yourself!
Desktop application development with Javascript and HTML – Stack … I am looking for Titanium Appcelerator alternatives for Desktop … I started looking into Titanium for desktop dev. I liked the concept but not
AppJS Build Desktop Applications. for Linux … What is better than this stack for application development? … So relax and focus on the task your application should do.
Desktop Application Development | Outsourced … – SourceEdge The Desktop applications at SourceEdge bring out the best of both, as we offer the power and performance of desktop applications with the modern
Desktop Application Development | Desktop Software Developer Dot Com Infoway is a desktop application development company that offers rich, user-friendly and effective desktop applications that can work offline. Hire
Application Development | Appcelerator Inc. Create native mobile apps across different mobile devices, as well as hybrid and HTML5, on Appcelerator’s open source Titanium development environment.
Learn Desktop Apps with Video Courses and Tutorials from Tutorials include how to make an app for Mac and Windows operating systems, and lessons on Xcode, C , C#, and other development tools and languages.
Python desktop application development – Therry van Neerven … 11 Nov 2014 … Python desktop application development – Therry van Neerven� … He’s the author of the �sendcloud client�, a desktop app of the company
Cappuccino Web Framework – Build Desktop Class Applications in … Modern App Development for the Web … amazing table views, drag and drop and every modern UI appearance and behaviour you might expect on the desktop.
Desktop Application Development | Enterprise Technology Solutions Our desktop application development services provide you with an efficient, user-friendly, and customized desktop application that can run offline
Category:Desktop database application development tools – Wikipedia Pages in category ‘Desktop database application development tools’. The following 16 pages are in this category, out of 16 total. This list may not reflect
Hire Desktop Application Developers & Programmers – oDesk Find & hire certified desktop application developers & programmers. Free job … I have developed in Delphi (versions 5,6,7 to XE6), C , PHP, SQL � more.
HTML5 Apps on the Desktop in 2013 | Clint Berry 11 May 2013 … … latest options for running HTML5 apps as native desktop applications. … I know I sure don’t want to develop apps on my 4′ phone screen.
Enyo JavaScript Application Framework Test Enyo is a JavaScript app framework enabling developers build native-quality … Use Enyo to develop apps for all major platforms, from phones and tablets
How Do I – Create A Desktop Application With Codename One … Please notice that this is a pro only feature and that it will produce applications that look like a tablet application rather than a typical desktop application.
(adsbygoogle = window.adsbygoogle || []).push({});
Android App Development for Beginners – 12 – More on User Interface
Android App Development for Beginners – 11 – Designing the User Interface
Electron And Ionic: Better Tools For Mobile + Desktop App Development
Cross Platform (web, Mobile, Desktop) Development With Spring
What Is Cross Platform Development? – Mobile And Desktop Explained
Developing Desktop And Mobile Apps With PySide And QML
from MobileAppBros http://mobileappbros.xyz/mobile-app-development/desktop-application-development-graphical-user-interface/%20
0 notes
Text
Getting Started with Core Data Tutorial
This is an abridged chapter from our book Core Data by Tutorials, which has been completely updated for Swift 4 and iOS 11. This tutorial is presented as part of our iOS 11 Launch Party — enjoy!
Welcome to Core Data! In this tutorial, you’ll write your very first Core Data app. You’ll see how easy it is to get started with all the resources provided in Xcode, from starter code templates to the Data Model editor.
You’re going to hit the ground running right from the start. By the end of the tutorial you’ll know how to:
Model data using Xcode’s model editor
Add new records to Core Data
Fetch a set of records from Core Data
Display the fetched records using a table view.
You’ll also get a sense of what Core Data is doing behind the scenes, and how you can interact with the various moving pieces.
Getting Started
Open Xcode and create a new iOS project based on the Single View App template. Name the app HitList and make sure Use Core Data is checked.
Checking the Use Core Data box will cause Xcode to generate boilerplate code for what’s known as an NSPersistentContainer in AppDelegate.swift.
The NSPersistentContainer consists of a set of objects that facilitate saving and retrieving information from Core Data. Inside this container is an object to manage the Core Data state as a whole, an object representing the Data Model, and so on.
The standard stack works well for most apps, but depending on your your app and its data requirements, you can customize the stack to be more efficient.
Note: Not all Xcode templates under iOS/Application have the option to start with Core Data. In Xcode 9, only the Master-Detail App and Single View App templates have the Use Core Data checkbox.
The idea for this sample app is simple: There will be a table view with a list of names for your very own “hit list”. You’ll be able to add names to this list and eventually, you’ll use Core Data to make sure the data is stored between sessions. We don’t condone violence in the book, so you can think of this app as a favorites list to keep track of your friends too, of course!
Click on Main.storyboard to open it in Interface Builder. Select the view controller on the canvas and embed it inside a navigation controller. From Xcode’s Editor menu, select Embed In…\ Navigation Controller.
Click on the navigation controller’s navigation bar to select it, then click on Prefers Large Titles in the Attributes Inspector. This will give the sample app a fresh iOS 11 style.
Next, drag a Table View from the object library into the view controller, then resize it so it covers the entire view.
If not already open, use the icon located in the lower left corner of your canvas to open Interface Builder’s document outline.
Ctrl-drag from the Table View in the document outline to its parent view and select the Leading Space to Safe Area constraint:
Do this three more times, selecting the constraints Trailing Space to Safe Area, Top Space to Safe Area and finally, Bottom Space to Safe Area. Adding those four constraints makes the table view fill its parent view.
Next, drag a Bar Button Item and place it on the view controller’s navigation bar. Finally, select the bar button item and change its system item to Add. Your canvas should look similar to the following screenshot:
Every time you tap the Add button, an alert controller containing a text field will appear. From there you’ll be able to type someone’s name into the text field. Tapping Save will save the name, dismiss the alert controller and refresh the table view, displaying all the names you’ve entered.
But first, you need to make the view controller the table view’s data source. In the canvas, Ctrl-drag from the table view to the yellow view controller icon above the navigation bar, as shown below, and click on dataSource:
In case you’re wondering, you don’t need to set up the table view’s delegate since tapping on the cells won’t trigger any action. It doesn’t get simpler than this!
Open the assistant editor by pressing Command-Option-Enter or by selecting the middle button on the Editor toolset on the Xcode bar. Delete the didReceiveMemoryWarning() method. Next, Ctrl-drag from the table view onto ViewController.swift, inside the class definition to create an IBOutlet.
Next, name the new IBOutlet property tableView, resulting in the following line:
@IBOutlet weak var tableView: UITableView!
Next, Ctrl-drag from the Add button into ViewController.swift just below your viewDidLoad() definition. This time, create an action instead of an outlet, naming the method addName, with a type UIBarButtonItem:
@IBAction func addName(_ sender: UIBarButtonItem) { }
You can now refer to the table view and the bar button item’s action in code.
Next, you’ll set up the model for the table view. Add the following property to ViewController.swift below the tableView IBOutlet:
var names: [String] = []
names is a mutable array holding string values displayed by the table view. Next, replace the implementation of viewDidLoad() with the following:
override func viewDidLoad() { super.viewDidLoad() title = "The List" tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") }
This will set a title on the navigation bar and register the UITableViewCell class with the table view.
Note: register(_:forCellReuseIdentifier:) guarantees your table view will return a cell of the correct type when the Cell reuseIdentifier is provided to the dequeue method.
Next, still in ViewController.swift, add the following UITableViewDataSource extension below your class definition for ViewController:
// MARK: - UITableViewDataSource extension ViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return names.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = names[indexPath.row] return cell } }
If you’ve ever worked with UITableView, this code should look very familiar. First you return the number of rows in the table as the number of items in your names array.
Next, tableView(_:cellForRowAt:) dequeues table view cells and populates them with the corresponding string from the names array.
Next, you need a way to add new names so the table view can display them. Implement the addName IBAction method you Ctrl-dragged into your code earlier:
// Implement the addName IBAction @IBAction func addName(_ sender: UIBarButtonItem) { let alert = UIAlertController(title: "New Name", message: "Add a new name", preferredStyle: .alert) let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in guard let textField = alert.textFields?.first, let nameToSave = textField.text else { return } self.names.append(nameToSave) self.tableView.reloadData() } let cancelAction = UIAlertAction(title: "Cancel", style: .default) alert.addTextField() alert.addAction(saveAction) alert.addAction(cancelAction) present(alert, animated: true) }
Every time you tap the Add button, this method presents a UIAlertController with a text field and two buttons, Save and Cancel.
Save inserts the text fields current text into the names array then reloads the table view. Since the names array is the model backing the table view, whatever you type into the text field will appear in the table view.
Finally, build and run your app for the first time. Next, tap the Add button. The alert controller will look like this:
Add four or five names to the list. You should see something similar to below:
Your table view will display the data and your array will store the names, but the big thing missing here is persistence. The array is in memory but if you force quit the app or reboot your device, your hit list will be wiped out.
Core Data provides persistence, meaning it can store data in a more durable state so it can outlive an app re-launch or a device reboot.
You haven’t added any Core Data yet, so nothing should persist after you navigate away from the app. Let’s test this out. Press the Home button if you’re using a physical device or the equivalent (Shift+⌘+H) if you’re using the Simulator. This will take you back to the familiar app grid on the home screen:
From the home screen, tap the HitList icon to bring the app back to the foreground. The names are still on the screen. What happened?
When you tap the Home button, the app currently in the foreground goes to the background. When this happens, the operating system flash-freezes everything currently in memory, including the strings in the names array. Similarly, when it’s time to wake up and return to the foreground, the operating system restores what used to be in memory as if you’d never left.
Apple introduced these advances in multitasking back in iOS 4. They create a seamless experience for iOS users but add a wrinkle to the definition of persistence for iOS developers. Are the names really persisted?
No, not really. If you had completely killed the app in the fast app switcher or turned off your phone, those names would be gone. You can verify this, as well. With the app in the foreground, double tap the Home button to enter the fast app switcher, like so:
From here, flick the HitList app snapshot upwards to terminate the app. There should be no trace of HitList in living memory (no pun intended). Verify the names are gone by returning to the home screen and tapping on the HitList icon to trigger a fresh launch.
The difference between flash-freezing and persistence may be obvious if you’ve worked with iOS for some time and are familiar with the way multitasking works. In a user’s mind, however, there is no difference. The user doesn’t care why the names are still there, whether the app went into the background and came back, or because the app saved and reloaded them.
All that matters is the names are still there when the app comes back!
So the real test of persistence, is whether your data is still there after a fresh app launch.
Modeling your Data
Now that you know how to check for persistence, you can dive into Core Data. Your goal for the HitList app is simple: persist the names you enter so they’re available for viewing after a fresh app launch.
Up to this point, you’ve been using plain old Swift strings to store the names in memory. In this section, you’ll replace these strings with Core Data objects.
The first step is to create a managed object model, which describes the way Core Data represents data on disk.
By default, Core Data uses a SQLite database as the persistent store, so you can think of the Data Model as the database schema.
Note: You’ll come across the word managed quite a bit in the book. If you see “managed” in the name of a class, such as in NSManagedObjectContext, chances are you are dealing with a Core Data class. “Managed” refers to Core Data’s management of the life cycle of Core Data objects.
However, don’t assume all Core Data classes contain the word “managed”. Actually, most don’t. For a comprehensive list of Core Data classes, check out the Core Data framework reference in the documentation browser.
Since you’ve elected to use Core Data, Xcode automatically created a Data Model file for you and named it HitList.xcdatamodeld.
Open HitList.xcdatamodeld. As you can see, Xcode has a powerful Data Model editor:
The Data Model editor has a lot of features, but for now, let’s focus on creating a single Core Data entity.
Click on Add Entity on the lower-left to create a new entity. Double-click the new entity and change its name to Person, like so:
You may be wondering why the model editor uses the term Entity. Weren’t you simply defining a new class? As you’ll see shortly, Core Data comes with its own vocabulary. Here’s a quick rundown of some terms you’ll commonly encounter:
An entity is a class definition in Core Data. The classic example is an Employee or a Company. In a relational database, an entity corresponds to a table.
An attribute is a piece of information attached to a particular entity. For example, an Employee entity could have attributes for the employee’s name, position and salary. In a database, an attribute corresponds to a particular field in a table.
A relationship is a link between multiple entities. In Core Data, relationships between two entities are called to-one relationships, while those between one and many entities are called to-many relationships. For example, a Manager can have a to-many relationship with a set of employees, whereas an individual Employee will usually have a to-one relationship with his manager.
Note: You’ve probably noticed that entities sound a lot like classes. Likewise, attributes and relationships sound a lot like properties. What’s the difference? You can think of a Core Data entity as a class definition and the managed object as an instance of that class.
Now that you know what an attribute is, you can add an attribute to Person object created earlier. Open HitList.xcdatamodeld. Next, select Person on the left-hand side and click the plus sign (+) under Attributes.
Set the new attribute’s name to, er, name and change its type to String:
Saving to Core Data
Open ViewController.swift, add the following Core Data module import below the UIKit import:
import CoreData
This import is all you need to start using the Core Data API in your code.
Next, replace the names property definition with the following:
var people: [NSManagedObject] = []
You’ll store Person entities rather than string names, so you rename the array serving as the table view’s data model to people. It now holds instances of NSManagedObject rather than simple strings.
NSManagedObject represents a single object stored in Core Data; you must use it to create, edit, save and delete from your Core Data persistent store. As you’ll see shortly, NSManagedObject is a shape-shifter. It can take the form of any entity in your Data Model, appropriating whatever attributes and relationships you defined.
Since you’re changing the table view’s model, you must also replace both data source methods implemented earlier. Replace your UITableViewDataSource extension with the following:
// MARK: - UITableViewDataSource extension ViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return people.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let person = people[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = person.value(forKeyPath: "name") as? String return cell } }
The most significant change to these methods occurs in tableView(_:cellForRowAt:). Instead of matching cells with the corresponding string in the model array, you now match cells with the corresponding NSManagedObject.
Note how you grab the name attribute from the NSManagedObject. It happens here:
cell.textLabel?.text = person.value(forKeyPath: "name") as? String
Why do you have to do this? As it turns out, NSManagedObject doesn’t know about the name attribute you defined in your Data Model, so there’s no way of accessing it directly with a property. The only way Core Data provides to read the value is key-value coding, commonly referred to as KVC.
Note: KVC is a mechanism in Foundation for accessing an object’s properties indirectly using strings. In this case, KVC makes NSMangedObject behave more or less like a dictionary at runtime.
Key-value coding is available to all classes inheriting from NSObject, including NSManagedObject. You can’t access properties using KVC on a Swift object that doesn’t descend from NSObject.
Next, find addName(_:) and replace the save UIAlertAction with the following:
let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in guard let textField = alert.textFields?.first, let nameToSave = textField.text else { return } self.save(name: nameToSave) self.tableView.reloadData() }
This takes the text in the text field and passes it over to a new method named save(name:). Xcode complains because save(name:) doesn’t exist yet. Add it below addName(_:):
func save(name: String) { guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } // 1 let managedContext = appDelegate.persistentContainer.viewContext // 2 let entity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)! let person = NSManagedObject(entity: entity, insertInto: managedContext) // 3 person.setValue(name, forKeyPath: "name") // 4 do { try managedContext.save() people.append(person) } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") } }
This is where Core Data kicks in! Here’s what the code does:
Before you can save or retrieve anything from your Core Data store, you first need to get your hands on an NSManagedObjectContext. You can consider a managed object context as an in-memory “scratchpad” for working with managed objects.
Think of saving a new managed object to Core Data as a two-step process: first, you insert a new managed object into a managed object context; then, after you’re happy with your shiny new managed object, you “commit” the changes in your managed object context to save it to disk.
Xcode has already generated a managed object context as part of the new project’s template. Remember, this only happens if you check the Use Core Data checkbox at the beginning. This default managed object context lives as a property of the NSPersistentContainer in the application delegate. To access it, you first get a reference to the app delegate.
You create a new managed object and insert it into the managed object context. You can do this in one step with NSManagedObject’s static method: entity(forEntityName:in:).
You may be wondering what an NSEntityDescription is all about. Recall earlier, NSManagedObject was called a shape-shifter class because it can represent any entity. An entity description is the piece linking the entity definition from your Data Model with an instance of NSManagedObject at runtime.
With an NSManagedObject in hand, you set the name attribute using key-value coding. You must spell the KVC key (name in this case) exactly as it appears in your Data Model, otherwise your app will crash at runtime.
You commit your changes to person and save to disk by calling save on the managed object context. Note save can throw an error, which is why you call it using the try keyword within a do-catch block. Finally, insert the new managed object into the people array so it shows up when the table view reloads.
That’s a little more complicated than an array of strings, but not too bad. Some of the code here, such as getting the managed object context and entity, could be done just once in your own init() or viewDidLoad() then reused later. For simplicity, you’re doing it all in the same method.
Build and run the app, and add a few names to the table view:
If the names are actually stored in Core Data, the HitList app should pass the persistence test. Double-tap the Home button to bring up the fast app switcher. Terminate the HitList app by flicking it upwards.
From Springboard, tap the HitList app to trigger a fresh launch. Wait, what happened? The table view is empty:
You saved to Core Data, but after a fresh app launch, the people array is empty! That’s because the data is sitting on disk waiting for you, but you’re not showing it yet.
Fetching from Core Data
To get data from your persistent store into the managed object context, you have to fetch it. Open ViewController.swift and add this code below viewDidLoad():
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) //1 guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } let managedContext = appDelegate.persistentContainer.viewContext //2 let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person") //3 do { people = try managedContext.fetch(fetchRequest) } catch let error as NSError { print("Could not fetch. \(error), \(error.userInfo)") } }
Step by step, this is what the code does:
Before you can do anything with Core Data, you need a managed object context. Fetching is no different! Like before, you pull up the application delegate and grab a reference to its persistent container to get your hands on its NSManagedObjectContext.
As the name suggests, NSFetchRequest is the class responsible for fetching from Core Data. Fetch requests are both powerful and flexible. You can use fetch requests to fetch a set of objects meeting the provided criteria (i.e. give me all employees living in Wisconsin and have been with the company at least three years), individual values (i.e. give me the longest name in the database) and more.
Fetch requests have several qualifiers used to refine the set of results returned. You’ll learn more about these qualifiers in Chapter 4, “Intermediate Fetching”; for now, you should know NSEntityDescription is a required one of these qualifiers.
Setting a fetch request’s entity property, or alternatively initializing it with init(entityName:), fetches all objects of a particular entity. This is what you do here to fetch all Person entities. Also note NSFetchRequest is a generic type. This use of generics specifies a fetch request’s expected return type, in this case NSManagedObject.
You hand the fetch request over to the managed object context to do the heavy lifting. fetch(_:) returns an array of managed objects meeting the criteria specified by the fetch request.
Note: Like save(), fetch(_:) can also throw an error so you have to use it within a do block. If an error occurred during the fetch, you can inspect the error inside the catch block and respond appropriately.
Build and run the application. Immediately, you should see the list of names you added earlier:
Great! They’re back from the dead (pun intended). Add a few more names to the list and restart the app to verify saving and fetching are working. Short of deleting the app, resetting the Simulator or throwing your phone off a tall building, the names will appear in the table view no matter what.
Where to Go From Here?
You can download the completed project for this tutorial here.
In just a few pages, you’ve already experienced several fundamental Core Data concepts: Data Models, entities, attributes, managed objects, managed object contexts and fetch requests.
If you enjoyed what you learned in this tutorial, why not check out the complete Core Data by Tutorials book, available in our store?
Here’s a taste of what’s in the book:
1. Chapter 1, Your First Core Data App: You’ll click File\New Project and write a Core Data app from scratch! This chapter covers the basics of setting up your data model and then adding and fetching records.
2. Chapter 2, NSManagedObject Subclasses: NSManagedObject is the base data storage class of your Core Data object graphs. This chapter will teach you how you customize your own managed object subclasses to store and validate data.
3. Chapter 3, The Core Data Stack: Under the hood, Core Data is made up of many parts working together. In this chapter, you’ll learn about how these parts fit together, and move away from the starter Xcode template to build your own customizable system.
4. Chapter 4, Intermediate Fetching: Your apps will fetch data all the time, and Core Data offers many options for getting the data to you efficiently. This chapter covers more advanced fetch requests, predicates, sorting and asynchronous fetching.
5. Chapter 5, NSFetchedResultsController: Table views are at the core of many iOS apps, and Apple wants to make Core Data play nicely with them! In this chapter, you’ll learn how NSFetchedResultsController can save you time and code when your table views are backed by data from Core Data.
6. Chapter 6, Versioning and Migration: As you update and enhance your app, its data model will almost certainly need to change. In this chapter, you’ll learn how to create multiple versions of your data model and then migrate your users forward so they can keep their existing data as they upgrade.
7. Chapter 7, Unit Tests: Testing is an important part of the development process, and you shouldn’t leave Core Data out of that! In this chapter, you’ll learn how to set up a separate test environment for Core Data and see examples of how to test your models.
8. Chapter 8, Measuring and Boosting Performance: No one ever complained that an app was too fast, so it’s important to be vigilant about tracking performance. In this chapter, you’ll learn how to measure your app’s performance with various Xcode tools and then pick up some tips for dealing with slow spots in your code.
9. Chapter 9, Multiple Managed Object Contexts: In this final chapter, you’ll expand the usual Core Data stack to include multiple managed object contexts. You’ll learn how this can improve perceived performance and help make your app architecture less monolithic and more compartmentalized.
And to help sweeten the deal, the digital edition of the book is on sale for $49.99! But don’t wait — this sale price is only available for a limited time.
Speaking of sweet deals, be sure to check out the great prizes we’re giving away this year with the iOS 11 Launch Party, including over $9,000 in giveaways!
To enter, simply retweet this post using the #ios11launchparty hashtag by using the button below:
Tweet !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');
We hope you enjoy this update, and stay tuned for more book releases and updates!
The post Getting Started with Core Data Tutorial appeared first on Ray Wenderlich.
Getting Started with Core Data Tutorial published first on http://ift.tt/2fA8nUr
0 notes
Text
Storyboards Tutorial for iOS: Part 2
Storyboards, Segues and Static Cells
Update note: This tutorial has been updated for Xcode 9, iOS 11, and Swift 4 by Nicholas Sakaimbo. The original tutorial was written by Matthijs Hollemans.
If you want to learn about storyboards, you’ve come to the right place!
In the first part of this series, you covered the basics of using Interface Builder to create and connect various view controllers, along with how to make custom table view cells directly from the storyboard editor.
In this second and final part of this storyboards tutorial series, we’ll cover segues, static table view cells, the Add Player scene, and game picker scene!
We’ll start where we left off last tutorial, so open your project from last time, or download the example code from the previous tutorial here.
OK, now you’ll dive into some of the other cool features in storyboards!
Introducing Segues
It’s time to add more view controllers to the storyboard. You’re going to create a scene to add new players to the app.
Open Main.storyboard and drag a Bar Button Item into the right slot of the navigation bar on the Players scene. In the Attributes inspector change System Item to Add, and set its Style to Bordered.
When the user taps this button, you want the app to pop up a new modal scene for entering details of a new player.
Drag a new Table View Controller into the canvas to the right of the Players scene. Remember you can double-click the canvas to zoom out so you have more room to work. With the new Table View Controller selected, choose Editor\Embed in\Navigation Controller.
Here’s the trick: Select the + button you just added on the Players scene and ctrl-drag to the new Navigation Controller. Release the mouse button and select Present Modally from the popup menu:
This places a new arrow between the Players scene and the Navigation Controller:
This type of connection is known as a segue (pronounce: seg-way) and represents a transition from one scene to another. The storyboard connections you’ve seen so far were relationships and they described one view controller containing another. A segue, on the other hand, changes what’s on the scene. Segues are triggered by taps on buttons, table view cells, gestures, and so on.
The cool thing about using segues is you don’t have to write any code to present the new scene, or hook up your buttons to IBAction methods. What you just did, dragging from the Bar Button Item to the next scene, is enough to create the transition. (Note: If your control already has an IBAction connection, the segue overrides it.)
Build and run the app and tap the + button. A new table view will slide up from the bottom.
This is called a modal segue. The new scene completely obscures the previous one. The user cannot interact with the underlying scene until they close the modal scene first. Later you’ll see “show” segues that push a new scene onto a Navigation Controller’s navigation stack.
The new scene isn’t very useful yet – you can’t even close it to go back to the main scene. That’s because segues only go one way – so while it can go from the Players scene to this new one, it can’t go back.
Storyboards provide the ability to ‘go back’ with something called an unwind segue, which you’ll implement next. There’s three main steps:
Create an object for the user to select, usually a button.
Create an unwind method in the controller you want to return to.
Hook up the method and the object in the storyboard.
First, open Main.storyboard and select the new Table View Controller scene. Change the title of the scene to Add Player (by double-clicking in the navigation bar). Next, add two Bar Button Items, one to each side of the navigation bar. In the Attributes inspector, set the System Item property of the left button to Cancel, and the right button to Done.
Next, add a new file to the project using the Cocoa Touch Class template – name it PlayerDetailsViewController and make it a subclass of UITableViewController. Next, open Main.storyboard and select the Add Player scene. In the Identity inspector set its Class to PlayerDetailsViewController. I always forget this very important step, so to make sure you don’t; I’ll keep pointing it out.
Now you can finally create the unwind segue. Open PlayersViewController.swift, add the following extension above your UITableViewDataSource extension:
// MARK: - IBActions extension PlayersViewController { @IBAction func cancelToPlayersViewController(_ segue: UIStoryboardSegue) { } @IBAction func savePlayerDetail(_ segue: UIStoryboardSegue) { } }
cancelToPlayersViewController(_:) is simply a marker for the unwind segue. Later you’ll add code to savePlayerDetail(_:) to allow it to live up to it’s name!
Finally, open Main.storyboard and hook up the Cancel and Done buttons to their respective action methods. Ctrl-drag from the bar button to the exit object above the view controller then pick the correct action name from the popup menu:
Note the name you gave the cancel method. When you create an unwind segue, the list will show all unwind methods (i.e. ones with the signature @IBAction func methodname(_ segue:)) in the entire app, so create a name you can recognize.
Build and run the app, tap the + button, and test the Cancel and Done buttons. A lot of functionality for very little code!
Storyboards Static Cells
When you’re finished with this section, the Add Player scene will look like this:
That’s a grouped table view, but you don’t have to create a data source for this table. You can design it directly in the storyboard — no need to write tableView(_:cellForRowAt:) for this one! Static cells is the feature that makes this possible.
Open Main.storyboard and select the table view in the Add Player scene. In the Attributes inspector change Content to Static Cells. Change Style from Plain to Grouped and give the table view 2 sections.
Note: When you change the value of the Sections attribute, the editor will clone the existing section. (You can also select a specific section in the Document Outline on the left and duplicate it.)
The finished scene will have only one row in each section, so select two cells in each of the sections and delete them using the Document Outline.
Next, select the top table view section (from the Document Outline) and set the header value to Player Name.
Drag a new Text Field into the cell for this section. Stretch out its width and remove its border so you can’t see where the text field begins or ends. Set the Font to System 17.0 and uncheck Adjust to Fit.
You’re going to make an outlet for this text field on the PlayerDetailsViewController using the Assistant Editor feature of Xcode. While still in the storyboard, open the Assistant Editor with the button from the toolbar (the one at the top right with two intertwining rings). It should automatically open on PlayerDetailsViewController.swift (if it doesn’t, use the jumpbar in the right hand split window to select PlayerDetailsViewController.swift).
Select the new text field and ctrl-drag to the top of PlayersDetailViewController, just below the class definition. When the popup appears, name the new outlet nameTextField, and click Connect. Xcode will add the property to the PlayersDetailViewController class and connect it in the storyboard:
Creating outlets for views on your table cells is exactly the kind of thing I said you shouldn’t try with prototype cells, but for static cells it’s OK. There will be only one instance of each static cell so it’s perfectly acceptable to connect their subviews to outlets on the view controller.
Select the second section of the table view in the Document Outline and delete the placeholder “Section-2” text in the Header field in the Attributes Inspector. Set the Style of the static cell in the second section to Right Detail. This gives you a standard cell style to work with. Change the label on the left to read Game by double clicking it and give the cell a Disclosure Indicator accessory.
Just as you did for nameTextField, make an outlet for the label that says Detail and name it detailLabel. The labels on this cell are just regular UILabel objects. You might need to click a few times on the text “Detail” to select the label (and not the whole cell) before ctrl-clicking and dragging to PlayerDetailsViewController.swift. Once done, it will look similar to the following:
The final design of the Add Player scene looks like this:
Note: The scenes you’ve designed so far in this storyboard all have the width and height of the 4.7-inch screen of the iPhone 7, which is 667 points tall. Obviously, your app should work properly with different screen sizes, and you can preview these sizes within your Storyboard.
Open the Assistant Editor from the toolbar, and use the jump bar to select Preview. At the bottom left of the assistant editor, click the + symbol to add new screen sizes to preview. To remove a screen size, select it and hit the Delete key.
For the Ratings app, you don’t have to do anything fancy. It only uses table view controllers and they automatically resize to fit the screen space. When you do need to support different layouts for different sized devices, you’ll use Auto Layout and Size Classes.
Build and run the app. You’ll notice the Add Player scene is still blank!
When you use static cells, your table view controller doesn’t need a data source. Since you used an Xcode template to create the PlayerDetailsViewController class, it still has some placeholder code for the data source and this prevents the static cells from working properly. Time to fix it!
Open PlayerDetailsViewController.swift and delete everything from the following line down (except for the class closing bracket):
override func viewDidLoad() { super.viewDidLoad() }
Build and run the app. Now the new scene displays the static cells, and all without writing a line of code.
One more thing about static cells: they only work in UITableViewController. Even though Interface Builder will let you add them to a table view inside a regular UIViewController, this won’t work during runtime. The reason is UITableViewController provides some extra magic to take care of the data source for the static cells. Xcode prevents you from compiling such a project with the error message: “Illegal Configuration: Static table views are only valid when embedded in UITableViewController instances”. Prototype cells, on the other hand, work just fine in table view’s placed inside regular view controllers.
Note: If you’re building a scene with a lot of static cells — more than can fit in the visible frame — you can scroll through them in Interface Builder with the scroll gesture on the mouse or trackpad (2 finger swipe).
You can’t always avoid writing code altogether though, even for a table view of static cells. When you dragged the text field into the first cell, you probably noticed it didn’t fit completely. There’s a small margin of space around the text field. The user can’t see where the text field begins or ends, so if they tap in the margin and the keyboard doesn’t appear, they’ll be confused.
To avoid this, let a tap anywhere inside the row bring up the keyboard. Open PlayerDetailsViewController.swift and add the following extension to the end of the file:
// MARK: - UITableViewDelegate extension PlayerDetailsViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.section == 0 { nameTextField.becomeFirstResponder() } } }
If the user taps the first cell, the app should activate the text field. There’s only one cell in the section so you only need to test for the section index. Making the text field the first responder will automatically bring up the keyboard.
Note: when adding a delegate method, or overriding a view controller method, just start typing the method name (without preceding it with “func”), and you’ll be able to select the correct method from the available list.
You should also set the Selection for the cell to None in the storyboard Attributes Inspector, otherwise the row appears highlighted when the user taps in the margin around the text field.
All right, that’s the design of the Add Player scene. Now to actually make it work!
The Add Player Scene at Work
For now you’ll ignore the Game row and just let users enter the name of the player.
When the user taps the Cancel button the scene should close and the data entered should be lost. This already works with the unwind segue.
When the user taps Done, you should create a new Player object, fill its properties and update the list of players.
prepare(for:sender:) is invoked whenever a segue is about to take place. You’ll override this method to store the data entered into a new Player object before dismissing the view.
Note: Never call prepare(for:sender:) yourself. It’s a message from UIKit to let you know a segue has just been triggered.
Open PlayerDetailsViewController.swift, and add the following property at the top of the class:
// MARK: - Properties var player: Player?
Next, add the following method below your IBOutlets definitions:
// MARK: - Navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "SavePlayerDetail", let playerName = nameTextField.text { player = Player(name: playerName, game: "Chess", rating: 1) } }
prepare(for:sender:) creates a new Player instance with default values for game and rating. It does this only for a segue with the identifier SavePlayerDetail.
Open Main.storyboard, find the Add Player scene in the Document Outline and select the unwind segue tied to savePlayerDetail:. Change Identifier to SavePlayerDetail:
Next, open PlayersViewController and replace the unwind segue method savePlayerDetail(_:) with the following:
@IBAction func savePlayerDetail(_ segue: UIStoryboardSegue) { guard let playerDetailsViewController = segue.source as? PlayerDetailsViewController, let player = playerDetailsViewController.player else { return } // add the new player to the players array players.append(player) // update the tableView let indexPath = IndexPath(row: players.count - 1, section: 0) tableView.insertRows(at: [indexPath], with: .automatic) }
This obtains a reference to the PlayerDetailsViewController via the segue reference and resolves the Player object. Next, append the new Player object to the players array. Finally, inform the table view a new row was inserted at the bottom, since the table view and its data source must always be in sync.
You could have invoked tableView.reloadData() but it looks nicer to insert the new row with an animation. UITableViewRowAnimation.automatic automatically picks the correct animation, depending on where you insert the new row.
Build and run the app, you should now be able to add new players to the list!
Performance
Since you have several view controllers in the storyboard, you might be wondering about performance. Loading a whole storyboard at once isn’t a big deal. The storyboard doesn’t instantiate all the view controllers right away – only the initial view controller is immediately loaded. Since your initial view controller is a Tab Bar Controller, the two view controllers it contains are also loaded (the Players scene from the first tab and the scene from the second tab).
The other view controllers are not instantiated until you segue to them. When you close these view controllers they’re immediately deallocated, so only the actively used view controllers are in memory.
To see this in practice, open PlayerDetailsViewController.swift and add the following below your IBOutlet definitions:
// MARK: - Initializers required init?(coder aDecoder: NSCoder) { print("init PlayerDetailsViewController") super.init(coder: aDecoder) } deinit { print("deinit PlayerDetailsViewController") }
You’re overriding init?(coder:) and deinit, and making them log a message to the Xcode Debug pane.
Build and run the app. Open the Add Player scene. You should see the print() log statement from init?(coder:).
When you close the Add Player scene, either by tapping Cancel or Done, you should see the print() log statement from deinit. If you open the scene again, you should also see the message from init?(coder:) again. This should reassure you that view controllers are loaded on-demand only.
The Game Picker Scene
Tapping the Game row in the Add Player scene should open a new scene to let the user pick a game from a list. This means you’ll add another table view controller, although this time you’re going to push it on the navigation stack rather than show it modally.
Open Main.storyboard and drag a new Table View Controller into the canvas. Next, select the Game table view cell in the Add Player scene (be sure to select the entire cell, not one of the labels) and ctrl-drag to the new table view controller to create a segue between them. Select Show under Selection Segue in the popup, not Accessory Action.
Select this new segue and give it the identifier PickGame in the Attributes Inspector.
Select the new table view controller in the Document Outline and in the Attributes Inspector, name this scene Choose Game.
Next, select the prototype table view cell and set the Style of the prototype cell to Basic, and give it the reuse identifier GameCell. That’s all you need to do for the design of this scene:
Add a new Swift file to the project, using the Cocoa Touch Class template and name it GamePickerViewController, subclass of UITableViewController.
Next, open Main.storyboard and select the Choose Game Scene. In the Identity Inspector, set its Custom Class to GamePickerViewController.
Now you’ll give this new scene some data to display. Open GamePickerViewController.swift, and add replace everything in the class definition with the following:
// MARK: - Properties var games = [ "Angry Birds", "Chess", "Russian Roulette", "Spin the Bottle", "Texas Hold'em Poker", "Tic-Tac-Toe" ]
Next, add the following extension to the end of the file:
// MARK: - UITableViewDataSource extension GamePickerViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return games.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell", for: indexPath) cell.textLabel?.text = games[indexPath.row] return cell } }
Here you’re setting up the data source to use the games array and placing the string values in the cell’s textLabel.
Build and run the app and tap the Game row. The new Choose Game scene will slide into view. Tapping the rows won’t do anything yet, but because this scene is presented on the navigation stack, you can always tap the back button to return to the Add Player scene.
This is pretty cool, huh? You didn’t have to write any code to invoke this new scene. You just ctrl-dragged from the static table view cell to the new scene and that’s it. The only code you wrote was to populate the contents of the table view, which is typically something more dynamic rather than a hardcoded list.
Currently, this new scene isn’t very useful since it doesn’t send any data back. You’ll have to add a new unwind segue.
In GamePickerViewController add the following properties below the games property:
var selectedGame: String? { didSet { if let selectedGame = selectedGame, let index = games.index(of: selectedGame) { selectedGameIndex = index } } } var selectedGameIndex: Int?
Whenever selectedGame is updated, didSet will locate the game string in games array and automatically update selectedGameIndex with the correct index from the table.
Next, replace tableView(_:cellForRowAt:) with the following:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell", for: indexPath) cell.textLabel?.text = games[indexPath.row] if indexPath.row == selectedGameIndex { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } return cell }
This sets a checkmark on the cell containing the name of the currently selected game. Small gestures such as these will be appreciated by users of the app.
Next, add the following extension below the UITableViewDataSource extension:
// MARK: - UITableViewDelegate extension GamePickerViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) // Other row is selected - need to deselect it if let index = selectedGameIndex { let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0)) cell?.accessoryType = .none } selectedGame = games[indexPath.row] // update the checkmark for the current row let cell = tableView.cellForRow(at: indexPath) cell?.accessoryType = .checkmark } }
This method is called whenever the user taps a row. First deselect the row after it was tapped (makes it fade from the gray highlight color back to white). Finally, remove the checkmark from the previously selected cell, and puts it on the just tapped cell.
Build and run the app. Tap the name of a game and its row will get a checkmark. Tap the name of another game and the checkmark moves to that row.
The scene should close when the user taps a row but that doesn’t happen yet because you haven’t hooked up an unwind segue. Sounds like a great next step!
Open PlayerDetailsViewController.swift, and add the following below the player property:
var game: String = "Chess" { didSet { detailLabel.text = game } }
This property will hold the selected game so it can be stored in the Player object later. didSet will display the name of the game in the static table cell whenever the name changes.
Still in PlayerDetailsViewController.swift, add the following extension above your UITableViewDelegate extension:
// MARK: - IBActions extension PlayerDetailsViewController { @IBAction func unwindWithSelectedGame(segue: UIStoryboardSegue) { if let gamePickerViewController = segue.source as? GamePickerViewController, let selectedGame = gamePickerViewController.selectedGame { game = selectedGame } } }
This method is executed once the user selects a game from the Choose Game Scene and updates both the label on screen and the game property based on the game selected. The unwind segue also pops GamePickerViewController off the navigation controller’s stack.
Open Main.storyboard, ctrl-drag from the tableview cell to the Exit as you did before, and choose unwindWithSelectedGame: from the popup list:
In the Attributes Inspector give the new unwind segue the Identifier SaveSelectedGame.
Build and run the app. Create a new player, select the player’s game row and choose a game.
The game is not updated on the Add Player scene!
Unfortunately, the unwind segue method is performed before tableView(_:didSelectRowAt:), so the selectedGameIndex is not updated in time. Fortunately, you can override prepare(for:sender:) and complete the operation before the unwind happens.
Open GamePickerViewController, and add the following method below your property definitions:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { guard segue.identifier == "SaveSelectedGame", let cell = sender as? UITableViewCell, let indexPath = tableView.indexPath(for: cell) else { return } let index = indexPath.row selectedGame = games[index] }
The sender parameter of prepare(for:sender:) is the object that initiated the segue, which in this case was the selected game cell. You can use the indexPath to locate the selected game in games array then set selectedGame so it’s available in the unwind segue.
Build and run the app and select the game, it’ll update the player’s game details!
Next, you need to change PlayerDetailsViewController‘s prepare(for:sender:) to return the selected game, rather than the hardcoded “Chess”. This way, when you complete adding a new player, their actual game will be displayed on the Players scene.
Open PlayerDetailsViewController.swift, replace prepareForSegue(_:sender:) with the following:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "SavePlayerDetail", let playerName = nameTextField.text { player = Player(name: playerName, game: game, rating: 1) } }
When you complete the Add Player scene and tap done, the list of players will now update with the correct game.
One more thing – when you choose a game, return to the Add Player scene, then try to choose a game again, the game you chose before should have a checkmark by it. The solution is to pass the selected game stored in PlayerDetailsViewController over to the GamePickerViewController when you segue.
Still in PlayerDetailsViewController.swift, add the following to the end of prepare(for:sender:):
if segue.identifier == "PickGame", let gamePickerViewController = segue.destination as? GamePickerViewController { gamePickerViewController.selectedGame = game }
Note you now have two if statements checking segue.identifier. SavePlayerDetail is the unwind segue going back to the Players list, and PickGame is the show segue going forwards to the Game Picker scene. The code you added will set the selectedGame on the GamePickerViewController just before the view is loaded. Setting selectedGame will automatically update selectedGameIndex which is the index the table view cell uses to set a checkmark.
Awesome. You now have a functioning Choose Game scene!
One More Thing: Storyboard References
Open Main.storyboard and zoom out. You’ll see the complete project has several scenes.
This bird’s-eye-view of your project is nice, but you can imagine a large number of scenes could become unwieldy to navigate. In addition, multiple people working in the same storyboard file can lead to nasty merge conflicts in version control.
To mitigate these issues, you can use Storyboard References to split up an existing storyboard into one or more smaller storyboard files, separated by logical areas of functionality. Let’s see how this works by refactoring all view controllers from the Players tab into their own storyboard.
To do this, click + drag to select all view controllers starting from PlayerViewController‘s containing navigation controller to the Choose Game scene (you may need to zoom out sufficiently to do this). Alternatively, you could also use Command + click to select view controllers in the Document Outline.
Next, select Editor\Refactor to Storyboard to consolidate the selected scenes into their own storyboard.
When prompted for a filename, type “Players” and hit “Save”. You’ll see a new Players.storyboard file in your project containing all the scenes you just built. In addition, back in Main.storyboard, you’ll see the tab bar controller now points to the Players Storyboard Reference and not directly to the Players scene.
Build and run the project to confirm everything works as before. Voila! Refactoring storyboards is easy and could save you down the road – it’s a great tool to have in your toolbelt.
Where To Go From Here?
Here is the final Ratings example project with all of the code from the above tutorial.
Congratulations, you now know the basics of using the Storyboard Editor, and can create apps with multiple view controllers transitioning between each other with segues! Editing multiple view controllers and their connections to each other in one place makes it easy to visualize your app as a whole.
One item you didn’t add as part of this tutorial is the ability to change the player rating, but you now know enough about storyboards to implement this yourself for practice. :]
You’ve also seen how easy it is to customize table views and table view cells. Static cells make it easy to set up an interface without implementing all the data source methods.
If you want to learn more about storyboards, check out our book the iOS Apprentice.
If you have any questions or comments on this tutorial or on storyboards in general, please join the forum discussion below!
The post Storyboards Tutorial for iOS: Part 2 appeared first on Ray Wenderlich.
Storyboards Tutorial for iOS: Part 2 syndicated from http://ift.tt/2uHrXAJ
0 notes
Text
Storyboards Tutorial for iOS: Part 2
Storyboards, Segues and Static Cells
Update note: This tutorial has been updated for Xcode 9, iOS 11, and Swift 4 by Nicholas Sakaimbo. The original tutorial was written by Matthijs Hollemans.
If you want to learn about storyboards, you’ve come to the right place!
In the first part of this series, you covered the basics of using Interface Builder to create and connect various view controllers, along with how to make custom table view cells directly from the storyboard editor.
In this second and final part of this storyboards tutorial series, we’ll cover segues, static table view cells, the Add Player scene, and game picker scene!
We’ll start where we left off last tutorial, so open your project from last time, or download the example code from the previous tutorial here.
OK, now you’ll dive into some of the other cool features in storyboards!
Introducing Segues
It’s time to add more view controllers to the storyboard. You’re going to create a scene to add new players to the app.
Open Main.storyboard and drag a Bar Button Item into the right slot of the navigation bar on the Players scene. In the Attributes inspector change System Item to Add, and set its Style to Bordered.
When the user taps this button, you want the app to pop up a new modal scene for entering details of a new player.
Drag a new Table View Controller into the canvas to the right of the Players scene. Remember you can double-click the canvas to zoom out so you have more room to work. With the new Table View Controller selected, choose Editor\Embed in\Navigation Controller.
Here’s the trick: Select the + button you just added on the Players scene and ctrl-drag to the new Navigation Controller. Release the mouse button and select Present Modally from the popup menu:
This places a new arrow between the Players scene and the Navigation Controller:
This type of connection is known as a segue (pronounce: seg-way) and represents a transition from one scene to another. The storyboard connections you’ve seen so far were relationships and they described one view controller containing another. A segue, on the other hand, changes what’s on the scene. Segues are triggered by taps on buttons, table view cells, gestures, and so on.
The cool thing about using segues is you don’t have to write any code to present the new scene, or hook up your buttons to IBAction methods. What you just did, dragging from the Bar Button Item to the next scene, is enough to create the transition. (Note: If your control already has an IBAction connection, the segue overrides it.)
Build and run the app and tap the + button. A new table view will slide up from the bottom.
This is called a modal segue. The new scene completely obscures the previous one. The user cannot interact with the underlying scene until they close the modal scene first. Later you’ll see “show” segues that push a new scene onto a Navigation Controller’s navigation stack.
The new scene isn’t very useful yet – you can’t even close it to go back to the main scene. That’s because segues only go one way – so while it can go from the Players scene to this new one, it can’t go back.
Storyboards provide the ability to ‘go back’ with something called an unwind segue, which you’ll implement next. There’s three main steps:
Create an object for the user to select, usually a button.
Create an unwind method in the controller you want to return to.
Hook up the method and the object in the storyboard.
First, open Main.storyboard and select the new Table View Controller scene. Change the title of the scene to Add Player (by double-clicking in the navigation bar). Next, add two Bar Button Items, one to each side of the navigation bar. In the Attributes inspector, set the System Item property of the left button to Cancel, and the right button to Done.
Next, add a new file to the project using the Cocoa Touch Class template – name it PlayerDetailsViewController and make it a subclass of UITableViewController. Next, open Main.storyboard and select the Add Player scene. In the Identity inspector set its Class to PlayerDetailsViewController. I always forget this very important step, so to make sure you don’t; I’ll keep pointing it out.
Now you can finally create the unwind segue. Open PlayersViewController.swift, add the following extension above your UITableViewDataSource extension:
// MARK: - IBActions extension PlayersViewController { @IBAction func cancelToPlayersViewController(_ segue: UIStoryboardSegue) { } @IBAction func savePlayerDetail(_ segue: UIStoryboardSegue) { } }
cancelToPlayersViewController(_:) is simply a marker for the unwind segue. Later you’ll add code to savePlayerDetail(_:) to allow it to live up to it’s name!
Finally, open Main.storyboard and hook up the Cancel and Done buttons to their respective action methods. Ctrl-drag from the bar button to the exit object above the view controller then pick the correct action name from the popup menu:
Note the name you gave the cancel method. When you create an unwind segue, the list will show all unwind methods (i.e. ones with the signature @IBAction func methodname(_ segue:)) in the entire app, so create a name you can recognize.
Build and run the app, tap the + button, and test the Cancel and Done buttons. A lot of functionality for very little code!
Storyboards Static Cells
When you’re finished with this section, the Add Player scene will look like this:
That’s a grouped table view, but you don’t have to create a data source for this table. You can design it directly in the storyboard — no need to write tableView(_:cellForRowAt:) for this one! Static cells is the feature that makes this possible.
Open Main.storyboard and select the table view in the Add Player scene. In the Attributes inspector change Content to Static Cells. Change Style from Plain to Grouped and give the table view 2 sections.
Note: When you change the value of the Sections attribute, the editor will clone the existing section. (You can also select a specific section in the Document Outline on the left and duplicate it.)
The finished scene will have only one row in each section, so select two cells in each of the sections and delete them using the Document Outline.
Next, select the top table view section (from the Document Outline) and set the header value to Player Name.
Drag a new Text Field into the cell for this section. Stretch out its width and remove its border so you can’t see where the text field begins or ends. Set the Font to System 17.0 and uncheck Adjust to Fit.
You’re going to make an outlet for this text field on the PlayerDetailsViewController using the Assistant Editor feature of Xcode. While still in the storyboard, open the Assistant Editor with the button from the toolbar (the one at the top right with two intertwining rings). It should automatically open on PlayerDetailsViewController.swift (if it doesn’t, use the jumpbar in the right hand split window to select PlayerDetailsViewController.swift).
Select the new text field and ctrl-drag to the top of PlayersDetailViewController, just below the class definition. When the popup appears, name the new outlet nameTextField, and click Connect. Xcode will add the property to the PlayersDetailViewController class and connect it in the storyboard:
Creating outlets for views on your table cells is exactly the kind of thing I said you shouldn’t try with prototype cells, but for static cells it’s OK. There will be only one instance of each static cell so it’s perfectly acceptable to connect their subviews to outlets on the view controller.
Select the second section of the table view in the Document Outline and delete the placeholder “Section-2” text in the Header field in the Attributes Inspector. Set the Style of the static cell in the second section to Right Detail. This gives you a standard cell style to work with. Change the label on the left to read Game by double clicking it and give the cell a Disclosure Indicator accessory.
Just as you did for nameTextField, make an outlet for the label that says Detail and name it detailLabel. The labels on this cell are just regular UILabel objects. You might need to click a few times on the text “Detail” to select the label (and not the whole cell) before ctrl-clicking and dragging to PlayerDetailsViewController.swift. Once done, it will look similar to the following:
The final design of the Add Player scene looks like this:
Note: The scenes you’ve designed so far in this storyboard all have the width and height of the 4.7-inch screen of the iPhone 7, which is 667 points tall. Obviously, your app should work properly with different screen sizes, and you can preview these sizes within your Storyboard.
Open the Assistant Editor from the toolbar, and use the jump bar to select Preview. At the bottom left of the assistant editor, click the + symbol to add new screen sizes to preview. To remove a screen size, select it and hit the Delete key.
For the Ratings app, you don’t have to do anything fancy. It only uses table view controllers and they automatically resize to fit the screen space. When you do need to support different layouts for different sized devices, you’ll use Auto Layout and Size Classes.
Build and run the app. You’ll notice the Add Player scene is still blank!
When you use static cells, your table view controller doesn’t need a data source. Since you used an Xcode template to create the PlayerDetailsViewController class, it still has some placeholder code for the data source and this prevents the static cells from working properly. Time to fix it!
Open PlayerDetailsViewController.swift and delete everything from the following line down (except for the class closing bracket):
override func viewDidLoad() { super.viewDidLoad() }
Build and run the app. Now the new scene displays the static cells, and all without writing a line of code.
One more thing about static cells: they only work in UITableViewController. Even though Interface Builder will let you add them to a table view inside a regular UIViewController, this won’t work during runtime. The reason is UITableViewController provides some extra magic to take care of the data source for the static cells. Xcode prevents you from compiling such a project with the error message: “Illegal Configuration: Static table views are only valid when embedded in UITableViewController instances”. Prototype cells, on the other hand, work just fine in table view’s placed inside regular view controllers.
Note: If you’re building a scene with a lot of static cells — more than can fit in the visible frame — you can scroll through them in Interface Builder with the scroll gesture on the mouse or trackpad (2 finger swipe).
You can’t always avoid writing code altogether though, even for a table view of static cells. When you dragged the text field into the first cell, you probably noticed it didn’t fit completely. There’s a small margin of space around the text field. The user can’t see where the text field begins or ends, so if they tap in the margin and the keyboard doesn’t appear, they’ll be confused.
To avoid this, let a tap anywhere inside the row bring up the keyboard. Open PlayerDetailsViewController.swift and add the following extension to the end of the file:
// MARK: - UITableViewDelegate extension PlayerDetailsViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.section == 0 { nameTextField.becomeFirstResponder() } } }
If the user taps the first cell, the app should activate the text field. There’s only one cell in the section so you only need to test for the section index. Making the text field the first responder will automatically bring up the keyboard.
Note: when adding a delegate method, or overriding a view controller method, just start typing the method name (without preceding it with “func”), and you’ll be able to select the correct method from the available list.
You should also set the Selection for the cell to None in the storyboard Attributes Inspector, otherwise the row appears highlighted when the user taps in the margin around the text field.
All right, that’s the design of the Add Player scene. Now to actually make it work!
The Add Player Scene at Work
For now you’ll ignore the Game row and just let users enter the name of the player.
When the user taps the Cancel button the scene should close and the data entered should be lost. This already works with the unwind segue.
When the user taps Done, you should create a new Player object, fill its properties and update the list of players.
prepare(for:sender:) is invoked whenever a segue is about to take place. You’ll override this method to store the data entered into a new Player object before dismissing the view.
Note: Never call prepare(for:sender:) yourself. It’s a message from UIKit to let you know a segue has just been triggered.
Open PlayerDetailsViewController.swift, and add the following property at the top of the class:
// MARK: - Properties var player: Player?
Next, add the following method below your IBOutlets definitions:
// MARK: - Navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "SavePlayerDetail", let playerName = nameTextField.text { player = Player(name: playerName, game: "Chess", rating: 1) } }
prepare(for:sender:) creates a new Player instance with default values for game and rating. It does this only for a segue with the identifier SavePlayerDetail.
Open Main.storyboard, find the Add Player scene in the Document Outline and select the unwind segue tied to savePlayerDetail:. Change Identifier to SavePlayerDetail:
Next, open PlayersViewController and replace the unwind segue method savePlayerDetail(_:) with the following:
@IBAction func savePlayerDetail(_ segue: UIStoryboardSegue) { guard let playerDetailsViewController = segue.source as? PlayerDetailsViewController, let player = playerDetailsViewController.player else { return } // add the new player to the players array players.append(player) // update the tableView let indexPath = IndexPath(row: players.count - 1, section: 0) tableView.insertRows(at: [indexPath], with: .automatic) }
This obtains a reference to the PlayerDetailsViewController via the segue reference and resolves the Player object. Next, append the new Player object to the players array. Finally, inform the table view a new row was inserted at the bottom, since the table view and its data source must always be in sync.
You could have invoked tableView.reloadData() but it looks nicer to insert the new row with an animation. UITableViewRowAnimation.automatic automatically picks the correct animation, depending on where you insert the new row.
Build and run the app, you should now be able to add new players to the list!
Performance
Since you have several view controllers in the storyboard, you might be wondering about performance. Loading a whole storyboard at once isn’t a big deal. The storyboard doesn’t instantiate all the view controllers right away – only the initial view controller is immediately loaded. Since your initial view controller is a Tab Bar Controller, the two view controllers it contains are also loaded (the Players scene from the first tab and the scene from the second tab).
The other view controllers are not instantiated until you segue to them. When you close these view controllers they’re immediately deallocated, so only the actively used view controllers are in memory.
To see this in practice, open PlayerDetailsViewController.swift and add the following below your IBOutlet definitions:
// MARK: - Initializers required init?(coder aDecoder: NSCoder) { print("init PlayerDetailsViewController") super.init(coder: aDecoder) } deinit { print("deinit PlayerDetailsViewController") }
You’re overriding init?(coder:) and deinit, and making them log a message to the Xcode Debug pane.
Build and run the app. Open the Add Player scene. You should see the print() log statement from init?(coder:).
When you close the Add Player scene, either by tapping Cancel or Done, you should see the print() log statement from deinit. If you open the scene again, you should also see the message from init?(coder:) again. This should reassure you that view controllers are loaded on-demand only.
The Game Picker Scene
Tapping the Game row in the Add Player scene should open a new scene to let the user pick a game from a list. This means you’ll add another table view controller, although this time you’re going to push it on the navigation stack rather than show it modally.
Open Main.storyboard and drag a new Table View Controller into the canvas. Next, select the Game table view cell in the Add Player scene (be sure to select the entire cell, not one of the labels) and ctrl-drag to the new table view controller to create a segue between them. Select Show under Selection Segue in the popup, not Accessory Action.
Select this new segue and give it the identifier PickGame in the Attributes Inspector.
Select the new table view controller in the Document Outline and in the Attributes Inspector, name this scene Choose Game.
Next, select the prototype table view cell and set the Style of the prototype cell to Basic, and give it the reuse identifier GameCell. That’s all you need to do for the design of this scene:
Add a new Swift file to the project, using the Cocoa Touch Class template and name it GamePickerViewController, subclass of UITableViewController.
Next, open Main.storyboard and select the Choose Game Scene. In the Identity Inspector, set its Custom Class to GamePickerViewController.
Now you’ll give this new scene some data to display. Open GamePickerViewController.swift, and add replace everything in the class definition with the following:
// MARK: - Properties var games = [ "Angry Birds", "Chess", "Russian Roulette", "Spin the Bottle", "Texas Hold'em Poker", "Tic-Tac-Toe" ]
Next, add the following extension to the end of the file:
// MARK: - UITableViewDataSource extension GamePickerViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return games.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell", for: indexPath) cell.textLabel?.text = games[indexPath.row] return cell } }
Here you’re setting up the data source to use the games array and placing the string values in the cell’s textLabel.
Build and run the app and tap the Game row. The new Choose Game scene will slide into view. Tapping the rows won’t do anything yet, but because this scene is presented on the navigation stack, you can always tap the back button to return to the Add Player scene.
This is pretty cool, huh? You didn’t have to write any code to invoke this new scene. You just ctrl-dragged from the static table view cell to the new scene and that’s it. The only code you wrote was to populate the contents of the table view, which is typically something more dynamic rather than a hardcoded list.
Currently, this new scene isn’t very useful since it doesn’t send any data back. You’ll have to add a new unwind segue.
In GamePickerViewController add the following properties below the games property:
var selectedGame: String? { didSet { if let selectedGame = selectedGame, let index = games.index(of: selectedGame) { selectedGameIndex = index } } } var selectedGameIndex: Int?
Whenever selectedGame is updated, didSet will locate the game string in games array and automatically update selectedGameIndex with the correct index from the table.
Next, replace tableView(_:cellForRowAt:) with the following:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "GameCell", for: indexPath) cell.textLabel?.text = games[indexPath.row] if indexPath.row == selectedGameIndex { cell.accessoryType = .checkmark } else { cell.accessoryType = .none } return cell }
This sets a checkmark on the cell containing the name of the currently selected game. Small gestures such as these will be appreciated by users of the app.
Next, add the following extension below the UITableViewDataSource extension:
// MARK: - UITableViewDelegate extension GamePickerViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) // Other row is selected - need to deselect it if let index = selectedGameIndex { let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0)) cell?.accessoryType = .none } selectedGame = games[indexPath.row] // update the checkmark for the current row let cell = tableView.cellForRow(at: indexPath) cell?.accessoryType = .checkmark } }
This method is called whenever the user taps a row. First deselect the row after it was tapped (makes it fade from the gray highlight color back to white). Finally, remove the checkmark from the previously selected cell, and puts it on the just tapped cell.
Build and run the app. Tap the name of a game and its row will get a checkmark. Tap the name of another game and the checkmark moves to that row.
The scene should close when the user taps a row but that doesn’t happen yet because you haven’t hooked up an unwind segue. Sounds like a great next step!
Open PlayerDetailsViewController.swift, and add the following below the player property:
var game: String = "Chess" { didSet { detailLabel.text = game } }
This property will hold the selected game so it can be stored in the Player object later. didSet will display the name of the game in the static table cell whenever the name changes.
Still in PlayerDetailsViewController.swift, add the following extension above your UITableViewDelegate extension:
// MARK: - IBActions extension PlayerDetailsViewController { @IBAction func unwindWithSelectedGame(segue: UIStoryboardSegue) { if let gamePickerViewController = segue.source as? GamePickerViewController, let selectedGame = gamePickerViewController.selectedGame { game = selectedGame } } }
This method is executed once the user selects a game from the Choose Game Scene and updates both the label on screen and the game property based on the game selected. The unwind segue also pops GamePickerViewController off the navigation controller’s stack.
Open Main.storyboard, ctrl-drag from the tableview cell to the Exit as you did before, and choose unwindWithSelectedGame: from the popup list:
In the Attributes Inspector give the new unwind segue the Identifier SaveSelectedGame.
Build and run the app. Create a new player, select the player’s game row and choose a game.
The game is not updated on the Add Player scene!
Unfortunately, the unwind segue method is performed before tableView(_:didSelectRowAt:), so the selectedGameIndex is not updated in time. Fortunately, you can override prepare(for:sender:) and complete the operation before the unwind happens.
Open GamePickerViewController, and add the following method below your property definitions:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { guard segue.identifier == "SaveSelectedGame", let cell = sender as? UITableViewCell, let indexPath = tableView.indexPath(for: cell) else { return } let index = indexPath.row selectedGame = games[index] }
The sender parameter of prepare(for:sender:) is the object that initiated the segue, which in this case was the selected game cell. You can use the indexPath to locate the selected game in games array then set selectedGame so it’s available in the unwind segue.
Build and run the app and select the game, it’ll update the player’s game details!
Next, you need to change PlayerDetailsViewController‘s prepare(for:sender:) to return the selected game, rather than the hardcoded “Chess”. This way, when you complete adding a new player, their actual game will be displayed on the Players scene.
Open PlayerDetailsViewController.swift, replace prepareForSegue(_:sender:) with the following:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "SavePlayerDetail", let playerName = nameTextField.text { player = Player(name: playerName, game: game, rating: 1) } }
When you complete the Add Player scene and tap done, the list of players will now update with the correct game.
One more thing – when you choose a game, return to the Add Player scene, then try to choose a game again, the game you chose before should have a checkmark by it. The solution is to pass the selected game stored in PlayerDetailsViewController over to the GamePickerViewController when you segue.
Still in PlayerDetailsViewController.swift, add the following to the end of prepare(for:sender:):
if segue.identifier == "PickGame", let gamePickerViewController = segue.destination as? GamePickerViewController { gamePickerViewController.selectedGame = game }
Note you now have two if statements checking segue.identifier. SavePlayerDetail is the unwind segue going back to the Players list, and PickGame is the show segue going forwards to the Game Picker scene. The code you added will set the selectedGame on the GamePickerViewController just before the view is loaded. Setting selectedGame will automatically update selectedGameIndex which is the index the table view cell uses to set a checkmark.
Awesome. You now have a functioning Choose Game scene!
One More Thing: Storyboard References
Open Main.storyboard and zoom out. You’ll see the complete project has several scenes.
This bird’s-eye-view of your project is nice, but you can imagine a large number of scenes could become unwieldy to navigate. In addition, multiple people working in the same storyboard file can lead to nasty merge conflicts in version control.
To mitigate these issues, you can use Storyboard References to split up an existing storyboard into one or more smaller storyboard files, separated by logical areas of functionality. Let’s see how this works by refactoring all view controllers from the Players tab into their own storyboard.
To do this, click + drag to select all view controllers starting from PlayerViewController‘s containing navigation controller to the Choose Game scene (you may need to zoom out sufficiently to do this). Alternatively, you could also use Command + click to select view controllers in the Document Outline.
Next, select Editor\Refactor to Storyboard to consolidate the selected scenes into their own storyboard.
When prompted for a filename, type “Players” and hit “Save”. You’ll see a new Players.storyboard file in your project containing all the scenes you just built. In addition, back in Main.storyboard, you’ll see the tab bar controller now points to the Players Storyboard Reference and not directly to the Players scene.
Build and run the project to confirm everything works as before. Voila! Refactoring storyboards is easy and could save you down the road – it’s a great tool to have in your toolbelt.
Where To Go From Here?
Here is the final Ratings example project with all of the code from the above tutorial.
Congratulations, you now know the basics of using the Storyboard Editor, and can create apps with multiple view controllers transitioning between each other with segues! Editing multiple view controllers and their connections to each other in one place makes it easy to visualize your app as a whole.
One item you didn’t add as part of this tutorial is the ability to change the player rating, but you now know enough about storyboards to implement this yourself for practice. :]
You’ve also seen how easy it is to customize table views and table view cells. Static cells make it easy to set up an interface without implementing all the data source methods.
If you want to learn more about storyboards, check out our book the iOS Apprentice.
If you have any questions or comments on this tutorial or on storyboards in general, please join the forum discussion below!
The post Storyboards Tutorial for iOS: Part 2 appeared first on Ray Wenderlich.
Storyboards Tutorial for iOS: Part 2 published first on http://ift.tt/2fA8nUr
0 notes