Using multicolour CLKTextProvider in Swift in watchOS 5

With Wednesday's announcement of new watch faces in watchOS 5 + Series 4 Apple Watch (Infograph and Infograph Modular), one cool feature that went under the radar is the ability to have multicolour text complications.

You can combine multiple CLKTextProviders (each with their own tintColor) into a single text provider!

I'm using this functionality in the new version of Streaks, so wanted to share how I achieved it.

In the following screenshot, "→" is grey text, while "Read a book" is red - but they're the same text provider.

Screen-Shot-2018-09-15-at-6.59.20-pm-1

To achieve this, [CLKTextProvider textProviderWithFormat:] is used - however, since Swift doesn't support variable-length arguments, it's only possible to use this with Objective-C.

In trying to get this work with Swift, the Apple Watch engineering suggested the following to get this working in Swift.


CLKTextProvider+MultiColorPatch.h:

#import <ClockKit/ClockKit.h>

@interface CLKTextProvider (MultiColorPatch)

+ (CLKTextProvider *)textProviderByJoiningTextProviders: (NSArray<CLKTextProvider *> *)textProviders separator:(NSString * _Nullable) separator;

@end

CLKTextProvider+MultiColorPatch.m:

#import "CLKTextProvider+MultiColorPatch.h"

@implementation CLKTextProvider (MultiColorPatch)

+ (CLKTextProvider *)textProviderByJoiningTextProviders: (NSArray<CLKTextProvider *> *)textProviders separator:(NSString * _Nullable) separator {
    
    NSString *formatString = @"%@%@";
    
    if (separator.length > 0) {
        formatString = [NSString stringWithFormat:@"%@%@%@", @"%@", separator, @"%@"];
    }
    
    CLKTextProvider *firstItem = textProviders.firstObject;
    
    for (int index = 1; index < textProviders.count; index++) {
        CLKTextProvider *secondItem = [textProviders objectAtIndex: index];
        firstItem = [CLKTextProvider textProviderWithFormat:formatString, firstItem, secondItem];
    }
    
    return firstItem;
}

@end

And you need to add it to your Objective-C bridging header in order to use it in Swift:

#import "CLKTextProvider+MultiColorPatch.h"

To make use of this in Swift, create and tint each part as a separate text provider:

let arrow = CLKSimpleTextProvider(text: "→")
arrow.tintColor = UIColor.grey

let text = CLKSimpleTextProvider(text: "Read a Book")
text.tintColor = UIColor.red

let separator = " "

let multi = CLKTextProvider(byJoining: [arrow, text], separator: separator)

If you separator was something else (e.g. a bullet point), you could set multi.tintColor to set its color.

To use this text provider, you need to ensure the complication template allows multicolour templates.


Bonus tip: I think you can combine a dynamic text provider (e.g. CLKRelativeDateTextProvider) with static text, and it'll still animate as a timer.