Apple Clockkit - Multiple complications for same family

1k views Asked by At

As a starting point to creating complications, static data can be presented in the following way by implementing the Complications delegate example code shown below:

This structure implies that I am only able to create one complication per complication family. Is this true? Are there any other options I have here?

For example, how would I create another modular small complication in addition to the one below, of a different type, ie. CLKComplicationTemplateModularSmallStackImage so that both would show in the modular small region?

Is this possibly a user-preference that can be managed?

#pragma mark - Placeholder Templates

- (void)getPlaceholderTemplateForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTemplate * __nullable complicationTemplate))handler {
// This method will be called once per supported complication, and the results will be cached


if  (complication.family == CLKComplicationFamilyModularSmall){

    CLKComplicationTemplateModularSmallStackText *template = [[CLKComplicationTemplateModularSmallStackText alloc] init];
    //     template.headerTextProvider = [CLKSimpleTextProvider textProviderWithText:@"Title Text"];
    template.line1TextProvider = [CLKSimpleTextProvider textProviderWithText:@"TEXT1"];
    template.line2TextProvider = [CLKSimpleTextProvider textProviderWithText:@"TEXT2"];
    template.tintColor = [UIColor whiteColor];

    handler(template);


} else if (complication.family == CLKComplicationFamilyModularLarge){


    CLKComplicationTemplateModularLargeStandardBody *template = [[CLKComplicationTemplateModularLargeStandardBody alloc] init];
    template.headerTextProvider = [CLKSimpleTextProvider textProviderWithText:@"Text1"];
    template.body1TextProvider = [CLKSimpleTextProvider textProviderWithText:@"Text2"];
    template.body2TextProvider = [CLKSimpleTextProvider textProviderWithText:@"Text3"];


    UIImage *surfMain = [UIImage imageNamed:@"person"];

    template.headerImageProvider = [CLKImageProvider imageProviderWithOnePieceImage:surfMain];



    handler(template);
}

}
3

There are 3 answers

0
AudioBubble On BEST ANSWER

At this time, you can't offer multiple choices for a particular family. You're expected to choose the best template among the ones offered for that family.

For your particular example, there are seven different modular small templates. Even if you limited yourself to giving the user two choices, it wouldn't scale well if every app that supported complications doubled or tripled the number of choices they offered within a particular family.

  • UX perspective:

    It avoids a disruptive user experience, from having to transition between scrolling by app to scrolling by an app's multiple choices, just for the user to get past your choices to a different app's complication that they want to select.

  • Developer perspective:

    If a user could choose to show two or three of your app's different modular small complications at the same time, the complication server would have to call your particular data source multiple times just to keep every active complication of yours up to date. There's the daily budget to consider, not to mention our extensions becoming a bit harder to read and maintain, if we had to switch on family, and then by a particular template.

Apple appears to have chosen a good design for both users and developers by limiting it to one complication per family. If you have a reason to support more than one, you can submit a feature request to the Apple Watch team.

0
Swanky Coder On

The previous answer is outdated. WatchOS 7 onwards, we can now add multiple complications to the same complication family for our app.

Step 1:

In our ComplicationController.swift file, we can make use of the getComplicationDescriptors function, which allows us to describe what complications we are making available in our app.

In the descriptors array, we can append one CLKComplicationDescriptor() for each kind of complication per family that we want to build.

func getComplicationDescriptors(
  handler: @escaping ([CLKComplicationDescriptor]) -> Void) {
  var descriptors: [CLKComplicationDescriptor] = []
  for progressType in dataController.getProgressTypes() {
    var dataDict = Dictionary<AnyHashable, Any>()
    dataDict = ["id": progressType.id]
    
    // userInfo helps us know which type of complication was interacted with by the user
    let userActivity = NSUserActivity(
       activityType: "org.example.foo")
    userActivity.userInfo = dataDict

    descriptors.append(
       CLKComplicationDescriptor(
         identifier: "\(progressType.id)", 
         displayName: "\(progressType.title)",
         supportedFamilies: CLKComplicationFamily.allCases, // you can replace CLKComplicationFamily.allCases with an array of complication families you wish to support
         userActivity: userActivity)
    )
  }
  handler(descriptors)
}

The app will now have multiple complications (equal to the length of the dataController.getProgressTypes() array) for each complication family that you support.

But how do you now display different data and views for different complications?

Step 2:

In the getCurrentTimelineEntries and getTimelineEntries functions, we can then make use of the complication.identifier value to identify the data that was passed along when this complication entry was called for.

Example, in the getTimelineEntries function:

 func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
    // Call the handler with the timeline entries after the given date
    var entries: [CLKComplicationTimelineEntry] = []
    
    ...
    ...
    ...
    
    var next: ProgressDetails
    // Find the progressType to show using the complication identifier
    if let progressType = dataController.getProgressAt(date: current).first(where: {$0.id == complication.identifier}) {
      next = progressType
    } else {
      next = dataController.getProgressAt(date: current)[0] // Default to the first progressType
    }
     
    let template = makeTemplate(for: next, complication: complication)
    let entry = CLKComplicationTimelineEntry(
        date: current,
        complicationTemplate: template)
    entries.append(entry)

    ...
    ...
    ...

    handler(entries)
  }

You can similarly find the data that is passed in the getCurrentTimelineEntry and the getLocalizableSampleTemplate functions.

Step 3:

Enjoy!

0
Mr the cat On

I propose here a code to do that in the ComplicationController.

  1. Go to the getComplicationDescriptors method and create many CLKComplicationDescriptor.
func getComplicationDescriptors(handler: @escaping ([CLKComplicationDescriptor]) -> Void) {
    let clkComplicationDescriptor = CLKComplicationDescriptor(identifier: "name", displayName: "First Name", supportedFamilies: [.graphicCorner])
    let clkComplicationDescriptor2 = CLKComplicationDescriptor(identifier: "age", displayName: "Age", supportedFamilies: [.graphicCorner])

    handler([clkComplicationDescriptor, clkComplicationDescriptor2]
}

This methods callback many CLKComplicationDescriptor with 3 parameters:

identifier: The identifier of your complications displayName: The name displayed when the user choose complications supportedFamilies: The families for your complication

  1. Go to getCurrentTimelineEntry and create your complication with datas
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
    
    print("ID: \(complication.identifier.description)") // return the identifier of complication in the step 1

   var firstName = "Ada"
   var name = "Lovelace"
   var age = 22

    switch complication.family {
       case .graphicCorner: 
          switch complication.identifier.description {
             case "name": 
               let innerText = CLKSimpleTextProvider(text: name)
               innerText.tintColor = innerTextColor
   
               let outerText = CLKSimpleTextProvider(text: firstName)
               outerText.tintColor = outerTextColor
    
               let cornerTemplate = CLKComplicationTemplateGraphicCornerStackText(innerTextProvider: innerText, outerTextProvider: outerText)
               let entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: cornerTemplate)
               handler(entry)

             case "age": 
                let innerText = CLKSimpleTextProvider(text: age)
                innerText.tintColor = innerTextColor
   
                let outerText = CLKSimpleTextProvider(text: "old")
                outerText.tintColor = outerTextColor
    
                let cornerTemplate = CLKComplicationTemplateGraphicCornerStackText(innerTextProvider: innerText, outerTextProvider: outerText)
               let entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: cornerTemplate)
               handler(entry)

              default: 
                handler(nil)
        }

      default: 
        handler(nil)
          
    }


}

You can choose now among several complications for the corner family. Don't forget to implement getLocalizableSampleTemplate.