Skip to content

Instantly share code, notes, and snippets.

@daniellevass
Last active August 19, 2017 23:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daniellevass/fe57857f8fdb5f9efe7e to your computer and use it in GitHub Desktop.
Save daniellevass/fe57857f8fdb5f9efe7e to your computer and use it in GitHub Desktop.
Hierarchical Apple Watch Apps

#Hierarchical Apple Watch Apps

##Introduction

Now we've got our project set up we can start to write some code! Today we're going to take a look at creating a heirarchical Apple Watch App using tables and segues.

###1. Storyboard

Firstly, we're going to start by removing whatever we had on our app interface. We then want to drag out a Table from the UI object items - this should automtically create you a "row" and a "group". The row is like a UITableCell in a UITableVIew, however the group will hold all the UI items for us. Next, we want to drag out a Label on to our table - I positioned mine to go horizontally and vertically central.

Imgur

This is how my storyboard looks

###2. SubClass Row

We should now subclass our row, so we can set up the label title dynamically. First, in the top toolbar click File - new File - Cocoa Toach Class. I called mine a ColourTableRow and subclassed NSObject. Note, before pressing add you want to make sure you add the file to both Watch App and Watch Extension target!

Next, back in the Storyboard view, click on the TableRowController in the Document Outline - and set the class to be the one you just created.

Whilst you in the TableRowController you should also set the identifier - these are essentially the same as as a UITableCell's reusable identifier so we can use them later!

Now, open the SubClass .h file - we want to create a setter for our label

-(void) setTitle:(NSString *) title;

Next we need to open our .m file - we firstly want to set an interface and create an outlet for our Label. You might as this point also need to include the WatchKit SDK if it didn't automatically:

#import <WatchKit/WatchKit.h>

@interface DEV_ColourTableRow()
@property (weak, nonatomic) IBOutlet WKInterfaceLabel *lblTitle;
@end

Next inside the implementation, we can write the code to set our label to whatever text we provide:

-(void) setTitle:(NSString *)title
{
    [_lblTitle setText:title];
    
}

Imgur

This is how my storyboard interface now looks

###2. Interface Controller

Now the fun bit - to make some rows actually appear when we run our app! First, we need to open the InterfaceController.m file and create an outlet for our table. Whilst we're here we can create two arrays to hold label text, and a UIColor - we could have done this using a custom class but I wanted something that was simple to see.

@property (weak, nonatomic) IBOutlet WKInterfaceTable *table; //link to our table
@property (strong, nonatomic) NSMutableArray *colourTitles; //array of NSString colour titles
@property (strong, nonatomic) NSMutableArray *colours; //array of UIColors

Next, I initialised all my arrays with some colour values:

//1. set up our arrays
    _colours = [[NSMutableArray alloc] initWithObjects:
                [UIColor redColor], [UIColor orangeColor], [UIColor yellowColor],
                [UIColor greenColor], [UIColor blueColor], [UIColor purpleColor],
                nil];
    
    _colourTitles = [[NSMutableArray alloc] initWithObjects:@"Red", @"Orange", @"Yellow",
                     @"Green", @"Blue", @"Purple",  nil];

So, WatchKit doesn't currently have any delegates or data sources, so we need to explicilty tell our table how many rows it will be holding of what type which matches the identifier of the row controller. I'll show you at the end of this post how to set up multiple row types.

//2. Set number of rows in our table
    [_table setNumberOfRows:[_colourTitles count]withRowType:@"ColourRow"];

Then, we can loop through all the rows we have and set the text:

//3. Loop through each row and set the title
    for (int i = 0; i < [_colourTitles count]; i++){
        DEV_ColourTableRow *colourRow = [_table rowControllerAtIndex:i];
        
        [colourRow setTitle:[_colourTitles objectAtIndex:i]];
        
    }

Imgur

When you run your app it should look like this

###3. Push Segue

First, open the interface builder, and drag out another Interface Controller next to your original app interface. To create a segue, we need to connect the table row controller with the next screen, not the entire table. You should also set up a segue identifier whilst this bit is open.

Imgur

Segue between the table row controller and the next interface controller

Next, we need to subclass our new Interface controller - you want to subclass a WKInterfaceController and ensure you add it to the Watch App and Watch Extension targets. We also want to drag out a group onto the interface controller as there is now current way programmatically to change the background color - this group also needs to be "relative to container" so that it fills all the space available!

Now, if we open the new interface controller .m file, we need to set up an outlet between the group and class. Next, we should look at the awakeWithContext. This passes a context object of type id - this is whatever the previous screen will have sent us. We're intending to send a UIColor so we can do the following:

    //get the colour from context
    UIColor *colour = context;
    
    //set the background colour
    [_group setBackgroundColor:colour];

The final piece of the puzzle is going back to the original interface controller .m file and implementing the following function:

-(id)contextForSegueWithIdentifier:(NSString *)segueIdentifier 
                           inTable:(WKInterfaceTable *)table 
                          rowIndex:(NSInteger)rowIndex
{
    if ([segueIdentifier isEqualToString:@"heirarchicalToColour"]){
        return [_colours objectAtIndex:rowIndex];
    } else {
        return nil;
    }
}

This allows us to check what segue is about to be performed, and send the colour corresponding to the row index to the next controller.

Hopefully if you run your app again you should be able to tap on a row and view it's colour!

###4. Bonus - multiple table row types...

It's possible to use multiple row types in a single table, for example you could have a "loading" row whilst data is being retrieved, or add a heading row. I'm going to make a heading row for my table, by copying and pasting the ColourRow in the document outline in the storyboard. I also need to remove the subclass and give it an identifier.

Next, where we originally told our table how many rows we'd have of type ColourRow, we can give it an array of types. This however, is a little tedious as we want to give it 1 HeadingRow and 6 ColourRows. I cheated and created a loop over all my colour titles to add a row for it like this:

    //add our heading row
    [controllers addObject:@"HeadingRow"];
    
    //add a row for each item in colour titles
    for (NSString *i in _colourTitles){
        [controllers addObject:@"ColourRow"];
    }
    
    //set the row types
    [_table setRowTypes:controllers];

Next, we need to loop over all the ColourRows to set the titles, node that the rowControlleratIndex is now +1

    //Loop through each row and set the title
    for (int i = 0; i < [_colourTitles count]; i++){
        
        //like previously, but start at index 1!
        DEV_ColourTableRow *colourRow = [_table rowControllerAtIndex:i+1];
        
        [colourRow setTitle:[_colourTitles objectAtIndex:i]];
        
    }

Finally, as your segue was connected to a specific row type we don't need to do much but edit which index of the colours we're providing the next context!

##Conclusion

All my project code is available on GitHub at https://github.com/calvium/WatchKit-Tutorial if you got stuck or want to see what I did. Next we're going to take a look at what a paged app might look like.

-(void) setTitle:(NSString *) title;
    //add our heading row
    [controllers addObject:@"HeadingRow"];
    
    //add a row for each item in colour titles
    for (NSString *i in _colourTitles){
        [controllers addObject:@"ColourRow"];
    }
    
    //set the row types
    [_table setRowTypes:controllers];
    //Loop through each row and set the title
    for (int i = 0; i < [_colourTitles count]; i++){
        
        //like previously, but start at index 1!
        DEV_ColourTableRow *colourRow = [_table rowControllerAtIndex:i+1];
        
        [colourRow setTitle:[_colourTitles objectAtIndex:i]];
        
    }
#import <WatchKit/WatchKit.h>

@interface DEV_ColourTableRow()
@property (weak, nonatomic) IBOutlet WKInterfaceLabel *lblTitle;
@end
-(void) setTitle:(NSString *)title
{
    [_lblTitle setText:title];
    
}
@property (weak, nonatomic) IBOutlet WKInterfaceTable *table; //link to our table
@property (strong, nonatomic) NSMutableArray *colourTitles; //array of NSString colour titles
@property (strong, nonatomic) NSMutableArray *colours; //array of UIColors
//1. set up our arrays
    _colours = [[NSMutableArray alloc] initWithObjects:
                [UIColor redColor], [UIColor orangeColor], [UIColor yellowColor],
                [UIColor greenColor], [UIColor blueColor], [UIColor purpleColor],
                nil];
    
    _colourTitles = [[NSMutableArray alloc] initWithObjects:@"Red", @"Orange", @"Yellow",
                     @"Green", @"Blue", @"Purple",  nil];
//2. Set number of rows in our table
    [_table setNumberOfRows:[_colourTitles count]withRowType:@"ColourRow"];
//3. Loop through each row and set the title
    for (int i = 0; i < [_colourTitles count]; i++){
        DEV_ColourTableRow *colourRow = [_table rowControllerAtIndex:i];
        
        [colourRow setTitle:[_colourTitles objectAtIndex:i]];
        
    }
    //get the colour from context
    UIColor *colour = context;
    
    //set the background colour
    [_group setBackgroundColor:colour];
-(id)contextForSegueWithIdentifier:(NSString *)segueIdentifier 
                           inTable:(WKInterfaceTable *)table 
                          rowIndex:(NSInteger)rowIndex
{
    if ([segueIdentifier isEqualToString:@"heirarchicalToColour"]){
        return [_colours objectAtIndex:rowIndex];
    } else {
        return nil;
    }
}
@Reiszecke
Copy link

Thanks so much for this. Too bad it's objective-C, currently learning Swift although I hate it.

Just wanted you to know that your effort wasn't for nothing, this is gist very well-written, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment