CMIS 215 Xcode project - Sorcerer's Cave

CAUTION: If you change the data base structure, you MUST delete the project from your device to clear the old database, otherwise you will have a database mismatch that will be very hard to diagnose.


Steps:

  1. New project
  2. Master-Detail
  3. Name: cave
    Company Identifier: edu.umuc.cmis215
    prefix: cav
    Devices: iPhone (or iPad or universal - up to you)
    check: Use Core Data
  4. pick a location for the project
  5. Deployment Target: 7.0/6.1/6.0 as your device O/S
  6. (Optional) Create and insert app icons - careful about copyrights!
    Images.xcassets: 58x58, 80x80, 120x120
  7. Create data model - cave.scdatamodeld
    NOTE: if you change this, you MUST delete the project from the device or simulator to get new table definitions to work, and all the data in the previous data base will be lost.
    1. Order:
      1. Entities, (Editor style: table easier) -
        DO NOT worry about the attributes in this step, they will be added in step 2 below.
        DO NOT worry about the relationships at this step, they will be added in steps 3 and 4 below.


      2. attributes (table style easier) - if you change this after you have added Relationships, you seem to need to delete the old relationships and create new ones.
        Again - DO NOT worry about the relationships in the figure, they will be added in steps 3 and 4 below.



      3. Relationships (Editor style: grid is easier, use CTRL+drag to make relationship connections)

      4. toMany types (data model inspector, Editor style: table easier)
        Here: Creature to many Artifacts, for example.


    2. Entities, attributes for this application:

    3. Cave (was Event - need to change name in code, see below)
      1. Date - timeStamp - built-in
      2. String - name
      3. integer - difficulty
      4. Party (1:n) - relationship
    4. Party
      1. String - name
      2. Creature (1:n) - relationship
    5. Creature
      1. String - name
      2. String - type
      3. Party (1:1) - relationship
      4. int - empathy
      5. int - fear
      6. int - capacity
      7. double - age
      8. double - height
      9. double - weight
      10. Treasure (1:n) - relationship
      11. Artifact (1:n) - relationship
    6. Treasure
      1. String - type
      2. Creature (1:1) - relationship
      3. double -  weight
      4. int - value
    7. Artifact
      1. String - name
      2. String -type
      3. Creature (1:1) - relationship
    8. Job - Hmm, I actually haven't added this to the data base entities yet, you might find this an interesting exercise
      1. String - name
      2. Creature (1:1) - relationship
      3. int - reqStone
      4. int - reqPotion
      5. int - reqWand
      6. int - reqWeapon
  8. Create NSManagedObject subclasses for each Entity:
    1. Select all Entities - say in the grid style
    2. Choose Editor from the main Xcode menu
    3. Choose Create Managed Object Subclass ...
    4. Select Data Model - probably one one at this point, Next
    5. Select the Entities - perhaps all of them, Next
    6. Pick directory for target files - same as project seems like a good idea
    7. At this point, you should see a set of new files in the Navigator display, one .h and .m for each entity.
    8. you should take a look at a few of these files just to see what you got for all this work.
  9. Open the right table
    At this point, the table tag in the code is still the default one used by the Master-Detail Application template, so the first thing to do is fix that reference - we will start by creating a new Cave element, since that is the one class that uses the timestamp attribute which was already defined in the template.
    1. In cavMasterViewController.m - where the table is open, change the default "Event" table name to "Cave":
  10. At this point you can test your the program:
    1. The master screen, after + has been pressed a few times:

    2. The master screen after pressing Edit and selecting a line:

    3. The details screen - the default only shows a time stamp

  11. Displaying the data fields of the Cave entity - we will eventually get to the other entities, but one thing at a time
    1. We begin with the Detail view in the storyboard, and add the text fields, type: UITextField, for the the name and level.
      You will note that the return button, "< Master" button is already part of the template, so we don't need to worry about that.
      Also, since we are not changing the data base, the update part of the Xcode relationship with the device will work automatically - thus we can change the display without having to manually delete the application from our device.
      1. We add the UITextField objects to the Detail view
      2. click-drag the new objects to the original object ("Detail view content goes here" Label) to set alignment properties - vertical space, center X alignment constraints.
      3. click-drag the new object to the cavDetailViewController.h file - lines 16 and 17 in this display, adding the outlet names level and name. Note that the types are automatic
      4. put some text in these labels so we can see that this is working
      5. TEST!
        If you run this code now, you will see that you may type into the text fields, but there are some problems:
        1. The level keyboard should be numeric, not alphabetic.
        2. The keyboard just won't go away.
        3. The data you type will be lost if you leave this screen and return.
    2. Here's a graphic from the Xcode screen showing the results of the previous actions. Also, you can put the mouse cursor over the various dots at lines 15, 16 and 17 to see the objects connected to those variables, and you will note that the variable detailDescriptionLabel is connected to the first label already in the template for this project.
    3. Keyboard hiding - this is not really as easy as it sounds. The code to make this happen is fairly simple, but that code will only be called in response to some event - so we need to define an action related to an object. One approach is to define a huge, invisible button, effectively making the background of the view one giant button, and then connecting an action (function) to that button, so that when a user presses the background of the display the hide keyboard action will be triggered, calling our function.
      1. So I created a movie to help make this operation easier to follow:
        Hiding the keyboard - 69 MB Quicktime movie
      2. Steps:
        1. make a button
        2. Edit - Alignment - Send to back
        3. Connect to an action - say hideKeyboard, .h file
        4. make the button tint white or clear
        5. make the button huge - dragging size handles
        6. insert resignFirstResponder methods into the hideKeyboard method, context is the both of the text fields - .m file
          - (IBAction)hideKeyboard:(id)sender {
              [self.name  resignFirstResponder];
              [self.level resignFirstResponder];
          }
      3. TEST THIS!
    4. Numeric keyboard
      1. select the text field of interest, then
      2. go the Utilities panel, 
      3. select the Attributes Inspector
      4. scroll down to Keyboard
      5. change default to Number Pad - notice that there are other interesting options.
      6. TEST THIS! - notice that you get different keypads for the name vs level
  12. Working with the database
    we need to do a number of things:
    > give the data fields default values when new records are created. In this case, we are only working with the Cave table, so values for name and level.
    > display the values in the database in the detail view
    > update the values in the database to reflect changes from the user on the detailed view screen
    1. cavMasterViewController.m - give default values to fields
      insertNewObject method - add the following highlighted lines:
      - (void)insertNewObject:(id)sender
      {
          NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
          NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
          NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
         
          // If appropriate, configure the new managed object.
          // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
          [newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];
         
          // ND: give default values to name and difficulty:
          [newManagedObject setValue:@"AA" forKey:@"name"];
          [newManagedObject setValue:[[NSNumber alloc] initWithInt:35] forKey:@"difficulty"];
         
          // Save the context.
          NSError *error = nil;
          if (![context save:&error]) {
               // Replace this implementation with code to handle the error appropriately.
               // abort() causes the application to generate a crash log and terminate.
               // You should not use this function in a shipping application,
               // although it may be useful during development.

              NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
              abort();
          }
      }
    2. cavDetailViewController.m - this will display the values from the records from the database
      configureView - this method is already part of the template, so we just need to make a few small additions:
      - (void)configureView
      {
          // Update the user interface for the detail item.

          if (self.detailItem) {
              self.detailDescriptionLabel.text = [[self.detailItem valueForKey:@"timeStamp"] description];
              self.name.text = [self.detailItem valueForKey:@"name"];
              int v = [[self.detailItem valueForKey:@"difficulty"] integerValue];
              self.level.text = [[NSString alloc] initWithFormat:@"%d", v];
          }
      }

    3. In cavDetailedViewController.h, the type of the detailItem variable should be changed to reflect actual type, and so that the methods needed to update the data base using this variable are available - otherwise the code in part D will give an error:

      // in cavDetailedViewController.h
      // ND: change to better class with *

      @property (strong, nonatomic) NSManagedObject * detailItem;
      //@property (strong, nonatomic) id                  detailItem;

    4. Finally, recording the updated values only requires some code, in response to the "leave this view" event. The method is already part of the UIViewController class, so we just need to override it.
      - (void) viewWillDisappear:(BOOL) pAnimated {
          [super viewWillDisappear:pAnimated];
         
          [self.detailItem setValue:self.name.text    forKey:@"name"];
         
          NSNumber * v = [NSNumber numberWithInt:[self.level.text integerValue]];
         
          [self.detailItem setValue:v forKey:@"difficulty"];
         
          // ND: do the update - ala master view code
          NSError *error = nil;
          if (![self.detailItem.managedObjectContext save:&error]) {
              // Replace this implementation with code to handle the error appropriately.
              // abort() causes the application to generate a crash log and terminate.
              // You should not use this function in a shipping application, although it may be useful during development.

              NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
              abort();
          }
         
      } // end method veiwWillDisappear

    5. Obviously for Project 1, you will have to make various modifications to this example, and you will not have to create any new Entities, but this should be enough to get you started.
  13. The segue between the master and detailed views.
    1. About the transfer to the detailed view from the master - that is done through a system called segue. Segues are built into the iOS system. The segue in this template is configured by the storyboard and is tied to the cells of the table. Thus the cells of the table act, among things, as buttons. And since they are configured to the segue, pressing one automatically calls the segue with a reference to the particular cell that was pressed.
    2. You can see this if you go to the storyboard, click on the cell in the table in the detailed view, and then go to the Utilities panel (on the right), and click on the Connections Inspector. The very top entry, Triggered Segues shows a push connection to the detailed view controler, along with a parameter called detail, which will be a reference to the cell that was clicked. Thus, if there are lots of cells, the segue will know which one was clicked and transfer that information to the segue event handler.
    3. The segue event handler is the method in the master view .m file called prepareForSegue. This code is perhaps not too hard to understand, but the key element is the last line:
          [[segue destinationViewController] setDetailItem: object
      Which is the line that will use the detailItem a reference to the record in the database of the cell that was selected.
    4. The return from the detailed view to the master view is
    5. Actually, none of this really affects how your code works, and your code should not have to worry about this at all.

By Nicholas Duchon
October 30, 2013