Tumgik
#how to get the indexpath.row when a button in a cell is tapped?
yogeshpatelios · 5 years
Video
youtube
Get button click inside UITableViewCell Using | Protocol | Closure | Add...
0 notes
iyarpage · 7 years
Text
UISplitViewController Tutorial: Getting Started
Update note: This tutorial has been updated to iOS 11 and Swift 4 by Michael Katz. The original tutorial was written by Brad Johnson.
On an app running on the iPad, it rarely makes sense to have a full-screen table view like you do so often on iPhone – there’s just too much space. To better use that space, UISplitViewController comes to the rescue.
The split view lets you carve up the screen into two sections and display a view controller on each side. It’s typically used to display navigation on the left hand side, and a detail view on the right hand side. Since iOS 8, the split view controller works on both iPad and iPhone.
In this UISplitViewController tutorial, you’ll make a universal app from scratch that makes use of a split view controller to display a list of monsters from Math Ninja, one of the games developed by the team here at Razeware. You’ll use a split view controller to handle the navigation and display, which will adapt to work on both iPhone and iPad.
This UISplitViewController tutorial focuses on split view controllers; you should already be familiar with the basics of Auto Layout and storyboards before continuing.
Getting Started
Create a new Project in Xcode, and choose the iOS\Application\Single View App template.
Name the project MathMonsters. Leave language as Swift. Uncheck all the checkboxes. Then click on Next to finish creating the project.
Although you could use the Master-Detail App template as a starting point, you are going to start from scratch with the Single View App template. This is so you can get a better understanding of exactly how the UISplitViewController works. This knowledge will be helpful as you continue to use UISplitViewController in future projects.
Open Main.storyboard.
Delete the initial View Controller that is placed there by default in the storyboard. Delete ViewController.swift.
Drag a Split View Controller into the empty storyboard:
This will add several elements to your storyboard:
A Split View Controller. This is the root view of your application – the split view that will contain the entire rest of the app.
A Navigation Controller. This represents the UINavigationController that will be the root view of your master view controller (ie, the left pane of the split view when on iPad or Landscape iPhone 8 Plus). If you look in the split view controller, you’ll see the navigation controller has a relationship segue of master view controller. This allows you to create an entire navigation hierarchy in the master view controller without needing to affect the detail view controller at all.
A View Controller. This will eventually display all the details of the monsters. If you look in the split view controller, you will see the view controller has a relationship segue of detail view controller:
A Table View Controller. This is the root view controller of the master UINavigationController. This will eventually display the list of monsters.
Note: You will notice that Xcode raises a warning about the table view’s prototype cell missing a reuse identifier. Don’t worry about it for now; you will fix it shortly.
Since you deleted the default initial view controller from the storyboard, you need to tell the storyboard that you want your split view controller to be the initial view controller.
Select the Split View Controller and open the Attributes inspector. Check the Is Initial View Controller option.
You will see an arrow to the left of the split view controller, which tells you it is the initial view controller of this storyboard.
Build and run the app on an iPad simulator, and rotate your simulator to landscape.
You should see an empty split view controller:
Now run it on an iPhone simulator (any of them except a plus-sized phone, which is large enough that it will act just like the iPad version) and you will see that it starts off showing the detail view in full screen. It will also allows you to tap the back button on the navigation bar to pop back to the master view controller:
On iPhones other than an iPhone Plus in landscape, a split view controller will act just like a traditional master-detail app with a navigation controller pushing and popping back and forth. This functionality is built-in and requires very little extra configuration from you, the developer. Hooray!
You’re going to want to have your own view controllers shown instead of these default ones, so let’s get started creating those.
Creating Custom View Controllers
The storyboard has the view controller hierarchy set up – split view controller with its master and detail view controllers. Now you’ll need to implement the code side of things to get some data to show up.
Go to File\New\File… and choose the iOS\Source\Cocoa Touch Class template. Name the class MasterViewController, make it a subclass of UITableViewController, make sure the Also create XIB file checkbox is unchecked, and Language is set to Swift. Click Next and then Create.
Open MasterViewController.swift.
Scroll down to numberOfSections(in:). Delete this method. This method is not needed when only ever one section is returned.
Next, find tableView(_:numberOfRowsInSection:) and replace the implementation with the following:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 10 }
Finally, uncomment tableView(_:cellForRowAt:) and replace its implementation with the following:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) return cell }
This way, you’ll just have 10 empty rows to look at when you test this thing out later.
Open Main.storyboard. Select the Root View Controller. Click on the Identity inspector. Change the class to MasterViewController.
In addition, you need to make sure the prototype cell in the table view is given a reuse identifier, or it will cause a crash when the storyboard tries to load.
Within the Master View Controller, select the Prototype Cell. Change the Identifier to Cell. Change the cell Style to Basic.
Now, you’ll create the view controller for the detail side.
Go to File\New\File… and choose the iOS\Source\Cocoa Touch Class template. Name the class DetailViewController, make it a subclass of UIViewController, and make sure the Also create XIB file checkbox is unchecked and the Language is set to Swift. Click Next and then Create.
Open Main.storyboard, and select the view controller in the View Controller Scene. Click on the Identity inspector. Change the Class to DetailViewController.
Then drag a label into the middle of the detail view controller. Pin the label to the horizontal and vertical centers of the container with Auto Layout.
Double-click the label to change its text to say Hello, World! so you will know it’s working when you test it out later.
Build and run. At this point you should see your custom view controllers.
On iPad:
On iPhone:
Making Your Model
The next thing you need to do is define a model for the data you want to display. You don’t want to complicate things while learning the basics of split view controllers, so you’re going with a simple model with no data persistence.
First, make a class representing the monsters you want to display. Go to File\New\File…, select the iOS\Source\Swift File template, and click Next. Name the file Monster and click Create.
You’re just going to create a simple class with some attribute properties about each monster you want to display, and a couple of methods for creating new monsters and accessing the image for the weapon each monster has.
Replace the contents of Monster.swift with the following:
import UIKit enum Weapon { case blowgun, ninjaStar, fire, sword, smoke } class Monster { let name: String let description: String let iconName: String let weapon: Weapon init(name: String, description: String, iconName: String, weapon: Weapon) { self.name = name self.description = description self.iconName = iconName self.weapon = weapon } var weaponImage: UIImage { switch weapon { case .blowgun: return UIImage(named: "blowgun.png")! case .fire: return UIImage(named: "fire.png")! case .ninjaStar: return UIImage(named: "ninjastar.png")! case .smoke: return UIImage(named: "smoke.png")! case .sword: return UIImage(named: "sword.png")! } } var icon: UIImage? { return UIImage(named: iconName) } }
This file defines an enumeration to track the different kinds of weapons, and then a class to hold the monster information. There’s a simple initializer to create Monster instances, and a convenience method to get an image corresponding to the monster’s weapon.
That’s it for defining the model – so next let’s hook it up to your master view!
Displaying the Monster List
Open up MasterViewController.swift and add a new property to the class:
let monsters = [ Monster(name: "Cat-Bot", description: "MEE-OW", iconName: "meetcatbot", weapon: .sword), Monster(name: "Dog-Bot", description: "BOW-WOW", iconName: "meetdogbot", weapon: .blowgun), Monster(name: "Explode-Bot", description: "BOOM!", iconName: "meetexplodebot", weapon: .smoke), Monster(name: "Fire-Bot", description: "Will Make You Steamed", iconName: "meetfirebot", weapon: .ninjaStar), Monster(name: "Ice-Bot", description: "Has A Chilling Effect", iconName: "meeticebot", weapon: .fire), Monster(name: "Mini-Tomato-Bot", description: "Extremely Handsome", iconName: "meetminitomatobot", weapon: .ninjaStar) ]
This holds the array of monsters to populate the table view.
Find tableView(_:numberOfRowsInSection:) and replace the return statement with the following:
return monsters.count
This will return the number of monsters based on the size of the array.
Next, find tableView(_:cellForRowAtIndexPath:) and add the following code before the final return statement:
let monster = monsters[indexPath.row] cell.textLabel?.text = monster.name
This will configure the cell based on the correct monster. That’s it for the table view, which will simply show each monster’s name.
Download and unzip this art pack. Drag the folder containing those images into Assets.xcassets in Xcode.
Build and run the app.
You should see the list of monster bots on the left hand side on landscape iPad:
On iPhone:
Remember that on a compact-width iPhone, you start one level deep already in the navigation stack on the detail screen. You can tap the back button to see the table view.
Displaying Bot Details
Now that the table view is showing the list of monsters, it’s time to get the detail view in order.
Open Main.storyboard, select Detail View Controller and delete the label you put down earlier.
Using the screenshot below as a guide, drag the following controls into the DetailViewController’s view:
A 95×95 image view for displaying the monster’s image in the upper left hand corner.
A label aligned with the top of the image view with font System Bold, size 30, and with the text “Monster Name”
Two labels underneath, with font System, size 24. One label should be bottom aligned with the image view; the other label should be below the first label. They should have their left edges aligned, and titles “Description” and “Preferred Way To Kill”
A 70×70 image view for displaying the weapon image, horizontally center aligned with the “Preferred way to Kill” label.
Need some more hints? Open the spoilers below for the set of constraints I used to make the layout.
Solution Inside SelectShow>
Constraints:
Top Left Corner Image View: Leading Space to Safe Area with 16 points, Top Space to Safe Area with 8 points, Width Equals 95, Height Equals 95
Monster Name Label: Top Aligned to Top Left Corner Image View’s top, Leading Space to Image View by 8 points
Description Label: Bottom Aligned to Top Left Corner Image View’s bottom, Leading Space to Image View by 8 points, Bottom Space to Preferred Way to Kill label by 8 points
Preferred Way To Kill Label: Align Leading Edge to Description Label, Top Space to Description Label by 8 points 
Smaller Image View: Align Center X to Preferred Way to Kill Label, Top Space to Preferred Way to Kill Label by 8 points, Width Equals 70, Height Equals 70
Getting Auto Layout to use the proper constraints is especially important since this app is universal, and Auto Layout is what ensures the layout adapts well to both iPad and iPhone.
Note: Auto Layout can be a slippery devil! I highly recommend you check out our Beginning Auto Layout tutorial series if you run into any trouble.
That’s it for Auto Layout for now. Next, you will need to hook these views up to some outlets.
Open DetailViewController.swift and add the following properties to the top of the class:
@IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var descriptionLabel: UILabel! @IBOutlet weak var iconImageView: UIImageView! @IBOutlet weak var weaponImageView: UIImageView! var monster: Monster? { didSet { refreshUI() } }
Here you added properties for the various UI elements you just added which need to dynamically change. You also added a property for the Monster object this view controller should display.
Next, add the following helper method to the class:
func refreshUI() { loadViewIfNeeded() nameLabel.text = monster?.name descriptionLabel.text = monster?.description iconImageView.image = monster?.icon weaponImageView.image = monster?.weaponImage }
Whenever you switch the monster, you’ll want the UI to refresh itself and update the details displayed in the outlets. It’s possible that you’ll change monster and trigger the method even before the view has loaded, so you call loadViewIfNeeded() to guarantee that the view is loaded and its outlets are connected.
Now, go open up Main.storyboard. Right-click the Detail View Controller object from the Document Outline to display the list of outlets. Drag from the circle at the right of each item to the view to hook up the outlets.
Remember, the icon image view is the big image view in the top left, while the weapon image view is the smaller one underneath the “Preferred Way To Kill” label.
Go to to AppDelegate.swift and replace the implementation of application(_:didFinishLaunchingWithOptions:) with the following:
guard let splitViewController = window?.rootViewController as? UISplitViewController, let leftNavController = splitViewController.viewControllers.first as? UINavigationController, let masterViewController = leftNavController.topViewController as? MasterViewController, let detailViewController = splitViewController.viewControllers.last as? DetailViewController else { fatalError() } let firstMonster = masterViewController.monsters.first detailViewController.monster = firstMonster return true
A split view controller has an array property viewControllers that has the master and detail view controllers inside. The master view controller in your case is actually a navigation controller, so you get the top view controller from that to get your MasterViewController instance. From there, you can set the current monster to the first one in the list.
Build and run the app, and if all goes well you should see some monster details on the right.
On iPad Landscape:
and iPhone:
Note that selecting a monster on the MasterViewController does nothing yet and you’re stuck with Cat-Bot forever. That’s what you’ll work on next!
Hooking Up The Master With the Detail
There are many different strategies for how to best communicate between these two view controllers. In the Master-Detail App template, the master view controller has a reference to the detail view controller. That means the master view controller can set a property on the detail view controller when a row gets selected.
That works fine for simple applications where you only ever have one view controller in the detail pane, but you’re going to follow the approach suggested in the UISplitViewController class reference for more complex apps and use a delegate.
Open MasterViewController.swift and add the following protocol definition above the MasterViewController class definition:
protocol MonsterSelectionDelegate: class { func monsterSelected(_ newMonster: Monster) }
This defines a protocol with a single method, monsterSelected(_:). The detail view controller will implement this method, and the master view controller will message it when a monster is selected.
Next, update MasterViewController to add a property for an object conforming to the delegate protocol:
weak var delegate: MonsterSelectionDelegate?
Basically, this means that the delegate property is required to be an object that has monsterSelected(_:) implemented. That object will be responsible for handling what needs to happen within its view after the monster was selected.
Note: You need to mark the delegate as weak to avoid a retain cycle. To learn more about retain cycles in Swift, check out the Memory Management video in our Intermediate Swift video tutorial series.
Since you want DetailViewController to update when the monster is selected, you need to implement the delegate.
Open up DetailViewController.swift and add a class extension to the very end of the file:
extension DetailViewController: MonsterSelectionDelegate { func monsterSelected(_ newMonster: Monster) { monster = newMonster } }
Class extensions are great for separating out delegate protocols and grouping the methods together. In this extension, you are saying DetailViewController conforms to MonsterSelectionDelegate and then you implement the one required method.
Now that the delegate method is ready, you need to call it from the master side.
Open MasterViewController.swift and add the following method:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let selectedMonster = monsters[indexPath.row] delegate?.monsterSelected(selectedMonster) }
Implementing tableView(_:didSelectRowAt:) means you’ll be notified whenever the user selects a row in the table view. All you need to do is notify the monster selection delegate of the new monster.
Finally, open AppDelegate.swift. In application(_:didFinishLaunchingWithOptions:), add the following code just before the final return statement:
masterViewController.delegate = detailViewController
That’s the final connection between the two view controllers.
Build and run the app on iPad, and you should now be able to select between the monsters like the following:
So far so good with split views! Except there’s one problem left – if you run it on iPhone, selecting monsters from the master table view does not show the detail view controller. You now need to add make a small modification to make sure that the split view works on iPhone, in addition to iPad.
Open up MasterViewController.swift. Find tableView(_:didSelectRowAt:) and add the following to the end of the method:
if let detailViewController = delegate as? DetailViewController { splitViewController?.showDetailViewController(detailViewController, sender: nil) }
First, you need to make sure the delegate is set and that it is a DetailViewController instance as you expect. You then call showDetailViewController(_:sender:) on the split view controller and pass in the detail view controller. Every subclass of UIViewController has an inherited property splitViewController, which will refer to it’s containing view controller, if one exists.
This new code only changes the behavior of the app on iPhone, causing the navigation controller to push the detail controller onto the stack when you select a new monster. It does not alter the behavior of the iPad implementation, since on iPad the detail view controller is always visible.
After making this change, run it on iPhone and it should now behave properly. Adding just a few lines of code got you a fully functioning split view controller on both iPad and iPhone. Not bad!
Split View Controller in iPad Portrait
Run the app in iPad in portrait mode. At first it appears there is no way to get to the left menu, but try swiping from the left side of the screen. Pretty cool huh? Tap anywhere outside of the menu to hide it.
That built in swipe functionality is pretty cool, but what if you want to have a navigation bar up top with a button that will display the menu, similar to how it behaves on the iPhone? To do that, you will need to make a few more small modifications to the app.
First, open Main.storyboard and embed the Detail View Controller into a Navigation Controller. You can do this by selecting the Detail View Controller and then selecting Editor/Embed In/Navigation Controller.
Your storyboard will now look like this:
Now open MasterViewController.swift and find tableView(_:didSelectRowAt:). Change the if block with the call to showDetailViewController(_:sender:) to the following:
if let detailViewController = delegate as? DetailViewController, let detailNavigationController = detailViewController.navigationController { splitViewController?.showDetailViewController(detailNavigationController, sender: nil) }
Instead of showing the detail view controller, you’re now showing the detail view controller’s navigation controller. The navigation controller’s root is the detail view controller anyway, so you’ll still see the same content as before, just wrapped in a navigation controller.
There are two final changes to make before you run the app.
First, in AppDelegate.swift update application(_:didFinishLaunchingWithOptions:) by replacing the single line initializing detailViewController with the following two lines:
let rightNavController = splitViewController.viewControllers.last as? UINavigationController, let detailViewController = rightNavController.topViewController as? DetailViewController
Since the detail view controller is wrapped in a navigation controller, there are now two steps to access it.
Finally, add the following lines just before the final return statement:
detailViewController.navigationItem.leftItemsSupplementBackButton = true detailViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
This tells the detail view controller to replace its left navigation item with a button that will toggle the display mode of the split view controller. It won’t change anything when running on iPhone, but on iPad you will get a button in the top left to toggle the table view display. Run the app on iPad portrait and check it out:
Where To Go From Here?
Here’s an archive of the final project with all of the code you’ve developed so far.
For new apps, you’re likely just to use the Master-Detail template to save time, which gives you a split view controller to start. But now you’ve seen how to use UISplitViewController from the ground up and have a much better idea of how it works. Since you’ve seen how easy it is to get the master-detail pattern into your universal apps, go forth and apply what you’ve learned!
Check out our short video tutorial series on split view controllers if you’re interested in some more details on split view controllers across devices.
If you have any questions or comments, please join the forum discussion below!
The post UISplitViewController Tutorial: Getting Started appeared first on Ray Wenderlich.
UISplitViewController Tutorial: Getting Started published first on http://ift.tt/2fA8nUr
0 notes
iyarpage · 7 years
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