I would like to create a complication for watchOS 3 that will simply launch my App. I have used XCode to create the ComplicationController:
class Complicatio
Apple Watch 4 new graphic complications looks like this:
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
// Call the handler with the current timeline entry
switch complication.family {
case .graphicCorner:
if #available(watchOSApplicationExtension 5.0, *) {
let template = CLKComplicationTemplateGraphicCornerCircularImage()
let image = UIImage(named: "Complication/Graphic Corner")!
template.imageProvider = CLKFullColorImageProvider(fullColorImage: image)
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
handler(timelineEntry)
} else {
handler(nil)
}
case .graphicCircular:
if #available(watchOSApplicationExtension 5.0, *) {
let template = CLKComplicationTemplateGraphicCircularImage()
let image = UIImage(named: "Complication/Graphic Circular")!
template.imageProvider = CLKFullColorImageProvider(fullColorImage: image)
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
handler(timelineEntry)
} else {
handler(nil)
}
default:
handler(nil)
}
}
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
switch complication.family {
case .graphicCorner:
if #available(watchOSApplicationExtension 5.0, *) {
let template = CLKComplicationTemplateGraphicCornerCircularImage()
let image = UIImage(named: "Complication/Graphic Corner")!
template.imageProvider = CLKFullColorImageProvider(fullColorImage: image)
handler(template)
} else {
handler(nil)
}
case .graphicCircular:
if #available(watchOSApplicationExtension 5.0, *) {
let template = CLKComplicationTemplateGraphicCircularImage()
let image = UIImage(named: "Complication/Graphic Circular")!
template.imageProvider = CLKFullColorImageProvider(fullColorImage: image)
handler(template)
} else {
handler(nil)
}
default:
handler(nil)
}
}
Add Apple Watch 4 new graphic complications (watchOS 5.0+) and prevent crash on Apple Watch Series 1, 2 and 3.
Important: the code from Zoltan will crash on Apple Watch series 3 or lower.
According to the Apple documentation, the new graphic complications require watchOS 5.0 or higher and a Watch Series 4 or later:
Note Watch faces that support graphic templates are available only on Apple Watch Series 4 or later.
This means that a Watch Series 3 with watchOS 5 or 6 (due to if #available(watchOSApplicationExtension 5.0, *)
) will attempt to load the complication image from the asset catalogue. However, since App Thinning is enabled by default, the Watch Series 3 doesn't have the image in it's binary and therefor the following line will crash the app:
let image = UIImage(named: "Complication/Graphic Corner")!
We discovered this when we found thousands of crash reports in Xcode:
We found a crash report for each of the 2 Graphic complications that we support, all were with watchOS 5 or 6 and Watch Series 2 or 3, like:
Solution
Embed loading of the graphic asset within an IF statement and return nil
as template, so it won't crash on devices without the asset.
The example above from Zoltan will be:
case .graphicCorner:
if #available(watchOSApplicationExtension 5.0, *) {
let template = CLKComplicationTemplateGraphicCornerCircularImage()
if let image = UIImage(named: "Complication/Graphic Corner") {
template.imageProvider = CLKFullColorImageProvider(fullColorImage: image)
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
handler(timelineEntry)
} else {
handler(nil)
}
} else {
handler(nil)
}
For efficient and maintainable code, we created a reusable function templateForComplication()
that is used in all 3 mandatory delegate functions:
class ComplicationController: NSObject, CLKComplicationDataSource {
// MARK: Mandatory Delegate Methods
func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
// Turn off time travelling:
handler([])
}
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
let template = templateForComplication(complication: complication)
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template!)
handler(timelineEntry)
}
func getPlaceholderTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
handler(templateForComplication(complication: complication))
}
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
handler(templateForComplication(complication: complication))
}
// MARK: Helper Methods
private func templateForComplication(complication: CLKComplication) -> CLKComplicationTemplate? {
// Init default output:
var template: CLKComplicationTemplate? = nil
// Graphic Complications are only availably since watchOS 5.0:
if #available(watchOSApplicationExtension 5.0, *) {
// NOTE: Watch faces that support graphic templates are available only on Apple Watch Series 4 or later. So the binary on older devices (e.g. Watch Series 3) will not contain the images.
if complication.family == .graphicCircular {
let imageTemplate = CLKComplicationTemplateGraphicCircularImage()
// Check if asset exists, to prevent crash on non-supported devices:
if let fullColorImage = UIImage(named: "Complication/Graphic Circular") {
let imageProvider = CLKFullColorImageProvider.init(fullColorImage: fullColorImage)
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
}
else if complication.family == .graphicCorner {
let imageTemplate = CLKComplicationTemplateGraphicCornerCircularImage()
// Check if asset exists, to prevent crash on non-supported devices:
if let fullColorImage = UIImage(named: "Complication/Graphic Corner") {
let imageProvider = CLKFullColorImageProvider.init(fullColorImage: fullColorImage)
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
}
}
// For all watchOS versions:
if complication.family == .circularSmall {
let imageTemplate = CLKComplicationTemplateCircularSmallSimpleImage()
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Circular")!)
imageProvider.tintColor = UIColor.blue
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
else if complication.family == .modularSmall {
let imageTemplate = CLKComplicationTemplateModularSmallSimpleImage()
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Modular")!)
imageProvider.tintColor = UIColor.blue
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
else if complication.family == .utilitarianSmall {
let imageTemplate = CLKComplicationTemplateUtilitarianSmallSquare()
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Utilitarian")!)
imageProvider.tintColor = UIColor.blue
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
return template
}
}
These code changes are required:
func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void)
{
handler([])
}
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void)
{
if complication.family == .circularSmall
{
let template = CLKComplicationTemplateCircularSmallRingImage()
template.imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Circular")!)
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
handler(timelineEntry)
} else if complication.family == .utilitarianSmall
{
let template = CLKComplicationTemplateUtilitarianSmallRingImage()
template.imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Utilitarian")!)
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
handler(timelineEntry)
} else if complication.family == .modularSmall
{
let template = CLKComplicationTemplateModularSmallRingImage()
template.imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Modular")!)
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
handler(timelineEntry)
} else {
handler(nil)
}
}
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void)
{
switch complication.family
{
case .circularSmall:
let image: UIImage = UIImage(named: "Circular")!
let template = CLKComplicationTemplateCircularSmallSimpleImage()
template.imageProvider = CLKImageProvider(onePieceImage: image)
handler(template)
case .utilitarianSmall:
let image: UIImage = UIImage(named: "Utilitarian")!
let template = CLKComplicationTemplateUtilitarianSmallSquare()
template.imageProvider = CLKImageProvider(onePieceImage: image)
handler(template)
case .modularSmall:
let image: UIImage = UIImage(named: "Modular")!
let template = CLKComplicationTemplateModularSmallSimpleImage()
template.imageProvider = CLKImageProvider(onePieceImage: image)
handler(template)
default:
handler(nil)
}
}
Plus you need to provide the images as assets in the extension.