WatchOS3 Complication, который запускает приложение

Я хотел бы создать услугу для watchOS 3, которая просто запустит мое приложение. Я использовал XCode для создания ComplicationController:

class ComplicationController: NSObject, CLKComplicationDataSource
{

    // MARK: - Timeline Configuration

    func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
        handler([.forward, .backward])
    }

    func getTimelineStartDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
        handler(nil)
    }

    func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
        handler(nil)
    }

    func getPrivacyBehavior(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) {
        handler(.showOnLockScreen)
    }

    // MARK: - Timeline Population

    func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
        // Call the handler with the current timeline entry
        handler(nil)
    }

    func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
        // Call the handler with the timeline entries prior to the given date
        handler(nil)
    }

    func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
        // Call the handler with the timeline entries after to the given date
        handler(nil)
    }

    // MARK: - Placeholder Templates

    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
        handler(nil)
    }

}

и добавлены изображения для циркулярных, модульных и утилитарных активов. Но когда я запускаю приложение Watch, я не могу выбрать свои сложности для лица Watch. Что мне еще нужно сделать?

Спасибо

Greg

4 ответа

Решение

Эти изменения кода необходимы:

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)
    }
}

Кроме того, вы должны предоставить изображения в качестве активов в расширении.

Новые графические усложнения Apple Watch 4 выглядят так:

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:

  1. Open Xcode
  2. Window -> Organizer
  3. Select tab "Crashes"
  4. Select an App Store version

Мы нашли отчет о сбоях для каждого из 2 поддерживаемых нами графических усложнений, все они были связаны с watchOS 5 или 6 и Watch Series 2 или 3, например:

Решение

Встроить загрузку графического ресурса в оператор IF и вернуть nil как шаблон, поэтому он не выйдет из строя на устройствах без актива.

Приведенный выше пример от Золтана будет:

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)
        }

Для эффективного и поддерживаемого кода мы создали функцию многократного использования templateForComplication() который используется во всех трех обязательных функциях делегата:

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
    }
}

Ответ @zoltan-vinkler для Apple Watch 7

      func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
    // Call the handler with the current timeline entry
    switch complication.family {
        case .graphicCircular:
            let image = UIImage(named: "Complication/Graphic Circular")!
            let template = CLKComplicationTemplateGraphicCircularImage(imageProvider: CLKFullColorImageProvider(fullColorImage: image))
            let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
            handler(timelineEntry)
        default:
            handler(nil)
    }
}

func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
    // Call the handler with the current timeline entry
    switch complication.family {
        case .graphicCircular:
            let image = UIImage(named: "Complication/Graphic Circular")!
            let template = CLKComplicationTemplateGraphicCircularImage(imageProvider: CLKFullColorImageProvider(fullColorImage: image))
            let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
            handler(timelineEntry)
        default:
            handler(nil)
    }
}
Другие вопросы по тегам