Silver and Gold solution
This is how I solved the two challenges in this chapter.
-
Create a new Cocoa Touch class file, language set to Swift, name your class ExistingContactViewController, subclassing from UIViewController.
-
In Main.storyboard, create a new UINavigationControler and UIView, following the same directions you followed to create the New Contact Controller / View, except label these Existing Contact Navigation Controller and Existing Contact View Controller. Labeling is not a requirement but it makes it easier to know what’s going on, now that we have multiple nav controllers.
-
For Existing Contact View Controller, set its Custom Class to ExistingContactViewController in the inspector. Don’t forget to control+click drag from Existing Contact Navigation Controller to Existing Contact View Controller to set “root view controller”.
-
Lay out labels & text fields the same way you did for New Contact View Controller. Add Cancel and Save buttons just the same, and ctrl+click drag from Cancel to the exit icon just like before, specifying the same cancelToContactsController function. Don’t bother with a segue for Save yet, we haven’t made the function yet.
-
In Main.storyboard, click on your Table View and look at the inspector. Click the 4th icon in the inspector (Attributes). Change Prototype Cells to 1 from 0. This is necessary because we need a fake “cell” to appear in Interface Builder so we can create a segue from the cell.
-
Once you do the previous step, a new cell should appear in IB. Click it and in the inspector, set the Identifier to ContactCell.
-
Control-click drag from your Prototype Cell to your new Existing Contact Navigation Controller, and choose “Show Segue, Present Modally”.
-
Once this is done, on the left side of IB where all your items are listed in a table, click the segue you just created, and go to the Attribute Inspector. Set its identifier to editContactSegue. This is needed later to differentiate between which segue is taking place (cancel segue? new contact segue? edit segue?)
-
In ExistingContactViewController.swift, create 2 @IBOutlet entries for first name / last name and connect them up in Interface Builder just like before.
-
Also add the following 3 vars. We need indexPath to store the table row number that the user tapped to bring up the dialog, so we can pass that information back to ContactsViewController once the users taps Save. And we need the firstname / lastname properties so ExistingContactViewController can copy the name the user is editing into its UITextField properties. We can’t set those properties directly from ContactsViewController because the UITextField objects won’t be ready until after the view controller initializes.
var firstNameString: String = ""
var lastNameString: String = ""
var indexPath: IndexPath?
- Change your viewDidLoad function to look like this. This will copy the name you’re editing to the text fields.
override func viewDidLoad() {
super.viewDidLoad()
firstNameTextField.text = firstNameString
lastNameTextField.text = lastNameString
}
- Next, I thought it would be handy to have an easy way to get a firstName and a lastName from a Contact object, which normally stores its full name in a single variable. I did it like this, add these properties to your Contact class:
var firstName: String {
get {
var arr = name.characters.split(separator: " ").map(String.init)
return arr[0]
}
}
var lastName: String {
get {
var arr = name.characters.split(separator: " ").map(String.init)
return arr[1]
}
}
- Next, in ContactsViewController.m, comment out the second line in viewDidLoad, which is no longer needed since we’re using a prototype cell in IB.
//[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"ContactCellz"];
-
Also in ContactsViewController.m - change the value you specify for dequeueReusableCellWithIdentifier from whatever it is from before to @“ContactCell”. dequeueReusableCellWithIdentifier is used in the line you just commented out, but it’s also used in another function a few lines down, be sure to change it there too.
-
Also in ContactsViewController.m, add the below method. It basically determines which view controller is causing a segue to trigger and, if it’s the editContactSegue, then do some stuff. If it’s not editContactSegue, it does nothing. So, for editContactSegue, it grabs the indexPath of the touched table cell and stores that in our ExistingContactViewController (so that controller can pass the information back, so we know which contact to update when the user clicks Save). It also grabs the Contact object for which row the user tapped on, and copies that data over to ExistingContactViewController.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UIViewController *destination = segue.destinationViewController;
if ([segue.identifier isEqualToString:@"editContactSegue"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Contact *contact = self.contacts[indexPath.row];
ExistingContactViewController *existingVC = [destination childViewControllers][0];
existingVC.firstNameString = contact.firstName;
existingVC.lastNameString = contact.lastName;
existingVC.indexPath = indexPath;
}
}
- Next, also in ContactsViewController.m, implement updateExistingContact. This method grabs the view controller that triggered the segue, then grabs the values of the firstname and lastname fields the user filled in, and it also grabs the stored indexPath, so we know which entry in our contacts array to update. It then does this, using the replaceObjectAtIndex method that NSMutableArray’s have.
- (IBAction)updateExistingContact:(UIStoryboardSegue *)segue {
ExistingContactViewController *existingVC = segue.sourceViewController;
NSString *firstName = existingVC.firstNameTextField.text;
NSString *lastName = existingVC.lastNameTextField.text;
NSIndexPath *indexPath = existingVC.indexPath;
if (firstName.length != 0 || lastName.length != 0) {
NSString *name = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
Contact *contact = [[Contact alloc] initWithName:name];
[self.contacts replaceObjectAtIndex:indexPath.row withObject:contact];
[self.tableView reloadData];
}
}
- Now that updateExistingContact exists, go back to Main.storyboard, to your Edit Contact View Controller, and ctrl+click drag from the Save button to the exit icon, and choose updateExistingContact:.
That should be everything! Run it and try it out!
I tried really hard to make the numbering on each section contiguous, but this editor fought me hard and eventually I gave up!
That’s about it! I’m sure I missed a step somewhere, and if I do please comment and I’ll update my solution, but I hope this post helps you figure out how to do this. It was a fun challenge!