Search code examples
objective-cswiftwatchkitwatchos-2apple-watch-complication

How would I create a model for these WatchKit Complications?


I watched the Creating Complications with ClockKit presentation well-known Apple engineer Eliza Block gave at WWDC and coded the whole project as she went along. Great tutorial btw. Really thorough.

She showed the interface but didn't reveal the implementation of the mini model for her soccer matches. She probably thought it was super easy so just didn't show it. However being a new programmer I'd like to see some examples of the implementation and how it is done.

I have included the ComplicationController class and SoccerMatch model interface. I attempted to create a date using NSDateComponents but Xcode is warning me of unused variable 'date'. Also the date @property was already created by Apple in the interface. What should I write in the implementation to get a complete model?

import ClockKit

let MatchDuration = NSTimeInterval(60 * 90)

class ComplicationController: NSObject, CLKComplicationDataSource {

// MARK: - Timeline Configuration

func getSupportedTimeTravelDirectionsForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimeTravelDirections) -> Void) {
    handler([.Forward, .Backward])
}

func getTimelineStartDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
    let startDate = timelineEntryDateForMatch(SoccerMatch.firstMatch())
    handler(startDate)

}

func getTimelineEndDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {

    let endDate = (SoccerMatch.lastMatch().date.dateByAddingTimeInterval(MatchDuration))
    handler(endDate)
}

func getPrivacyBehaviorForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationPrivacyBehavior) -> Void) {
    handler(.ShowOnLockScreen)

}

// MARK: - Timeline Population

func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {

        getTimelineEntriesForComplication(complication, beforeDate: NSDate(), limit: 1) { (entries) -> Void in
        handler(entries?.first)
}

}

func getTimelineEntriesForComplication(complication: CLKComplication, beforeDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {

    var entries = [CLKComplicationTimelineEntry]()
    var match : SoccerMatch? = SoccerMatch.lastMatch()

    while let thisMatch = match {

        let thisEntryDate = timelineEntryDateForMatch(thisMatch)

        if date.compare(thisEntryDate) == .OrderedDescending {

            let tmpl = templateForMatch(thisMatch)

            let entry = CLKComplicationTimelineEntry(date: thisEntryDate, complicationTemplate: tmpl)

            entries.append(entry)

            if entries.count == limit { break }
        }
        match = match?.previousMatch()
    }
    handler(entries)

}

func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {

    var entries = [CLKComplicationTimelineEntry]()
    var match : SoccerMatch? = SoccerMatch.firstMatch()

    while let thisMatch = match {

        let thisEntryDate = timelineEntryDateForMatch(thisMatch)

        if date.compare(thisEntryDate) == .OrderedAscending {

            let tmpl = templateForMatch(thisMatch)
            let entry = CLKComplicationTimelineEntry(date: thisEntryDate, complicationTemplate: tmpl)

            entries.append(entry)

            if entries.count == limit { break }

        }

        match = match?.nextMatch()

    }

    handler(entries)
}

// MARK: - Update Scheduling

func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
    handler(nil);
}

// MARK: - Placeholder Templates

func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {

    let tmpl = CLKComplicationTemplateModularLargeStandardBody()

    let soccerBall = UIImage(named: "soccer_ball")!
    tmpl.headerImageProvider = CLKImageProvider(onePieceImage: soccerBall)
    tmpl.headerTextProvider = CLKSimpleTextProvider(text: "Match schedule")
    tmpl.body1TextProvider = CLKSimpleTextProvider(text: "2015 Women's Tournament")

    handler(tmpl)
}

private func templateForMatch(match: SoccerMatch) -> CLKComplicationTemplate {

    let tmpl = CLKComplicationTemplateModularLargeStandardBody()
    let soccerBall = UIImage(named: "soccer_ball")!

    tmpl.headerImageProvider = CLKImageProvider(onePieceImage: soccerBall)
    tmpl.headerTextProvider = CLKTimeTextProvider(date: match.date)
    tmpl.body1TextProvider = CLKSimpleTextProvider(text: match.teamDescription)
    tmpl.body2TextProvider = CLKSimpleTextProvider(text: match.groupDescription)

    return tmpl

}

private func timelineEntryDateForMatch(match: SoccerMatch) -> NSDate {

    if let previousMatch = match.previousMatch() {
        return previousMatch.date.dateByAddingTimeInterval(MatchDuration)
    } else {
        return match.date.dateByAddingTimeInterval(-6 * 60)
    }

}

SoccerMatch.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SoccerMatch : NSObject

@property (nonatomic, readonly) NSDate *date;               // match date
@property (nonatomic, readonly) NSString *teamDescription;  // who's playing
@property (nonatomic, readonly) NSString *groupDescription; // group in the tournament this is

// The model can tell us what the first match in the schedule is and the last match
+ (instancetype)firstMatch;
+ (instancetype)lastMatch;

// and each match can tell us the previous and the next
- (nullable SoccerMatch *)previousMatch;
- (nullable SoccerMatch *)nextMatch;

@end

NS_ASSUME_NONNULL_END

SoccerMatch.m

#import "SoccerMatch.h"

@implementation SoccerMatch

+ (instancetype)firstMatch {

NSCalendar *calendar = [NSCalendar currentCalendar];

// I found out I can create a date using NSDateComponents but Xcode tells me 'date' is unused.
// Apple already created a 'date' in SoccerMatch.h which is used in the ComplicationController so I'm not sure how to fix this.

NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:2016];
[components setMonth:3];
[components setDay:21];
[components setHour:10];
[components setMinute:30];
[components setSecond:0];

NSDate *firstMatchDate = [calendar dateFromComponents:components];

SoccerMatch *firstMatchDetails = [[SoccerMatch alloc] init];
firstMatchDetails.date = firstMatchDate;
firstMatchDetails.teamDescription = @"The Bears";
firstMatchDetails.groupDescription = @"Group A";

return firstMatchDetails;

};

+ (instancetype)lastMatch {

NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:2016];
[components setMonth:3];
[components setDay:21];
[components setHour:17];
[components setMinute:00];
[components setSecond:0];

NSDate *lastMatchDate = [calendar dateFromComponents:components];

SoccerMatch *lastMatchDetails = [[SoccerMatch alloc] init];
lastMatchDetails.date = lastMatchDate;
lastMatchDetails.teamDescription = @"The Hawks";
lastMatchDetails.groupDescription = @"Group B";

return lastMatchDetails;

};

- (nullable SoccerMatch *)previousMatch { 
// Eliza said each match can tell us the previous and the next, but how is that written?   
};

- (nullable SoccerMatch *)nextMatch {
};
@end

Solution

  • Your +firstMatch and +lastMatch methods need to return an instance of the SoccerMatch class—that’s what the instancetype keyword means. Your date-creation code looks fine, but in each method you need to create a SoccerMatch object, fill out its properties with the date and team/group descriptions, then return that instance.