Silver and Gold challenge solution

Silver and Gold solution

This is how I solved the two challenges in this chapter.

  1. Create a new Cocoa Touch class file, language set to Swift, name your class ExistingContactViewController, subclassing from UIViewController.

  2. 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.

  3. 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”.

  4. 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.

  5. 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.

  6. Once you do the previous step, a new cell should appear in IB. Click it and in the inspector, set the Identifier to ContactCell.

  7. Control-click drag from your Prototype Cell to your new Existing Contact Navigation Controller, and choose “Show Segue, Present Modally”.

  8. 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?)

  9. In ExistingContactViewController.swift, create 2 @IBOutlet entries for first name / last name and connect them up in Interface Builder just like before.

  10. 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?
  1. 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
    }
  1. 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]
        }
    }
  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"];
  1. 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.

  2. 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;
    }
}
  1. 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];
    }
}
  1. 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!

1 Like

@lerxst Thank you for this solution! I just finished this first book on Swift…there is no way I would have figured out all the changes that were required in Objective C to the ContactsViewController.m file. Great explanation.

One minor improvement to your solution was a simple change to the getter method for first and last name in the contacts class:

    var firstName:String {
        get {
            var fn:String = ""
            var nameArray = name.components(separatedBy: " ")
            if (nameArray.count >= 1) {
                fn = nameArray[0]
            }
            return fn
        }
    }
    
    var lastName:String {
        get {
            var ln:String = ""
            var nameArray = name.components(separatedBy: " ")
            if (nameArray.count >= 2) {
                for idx in 1...nameArray.count-1 {
                    ln.append("\(nameArray[idx]) ")
                }
            }
            return ln
        }
    }

This handles names like “Dita Von Teese” and “William du Pont Jr.” whereas the original post drops them.

And just a question, but you handled the updateExistingContacts signal from the save button in ContactsViewController.m instead of ExistingContactViewController.swift. I guess this is required since the contacts array is a variable within the .m file, but to help me get a better understanding of the Model-View-Controller pattern shouldn’t the book authors have created a Model class outside of the primary view controller to handle storing the app’s data?

This post has been deleted.

This post has been deleted.

This post has been deleted.