Apple Watchのコンプリケーション


#はじめに
iPhone、Apple Watch用のアプリ開発で調べたことをまとめていきます。
今回はApple Watchのコンプリケーションについてです。

#環境
Xcode Version12.4
iOS 14.4（実機はiPhone 12 Pro）
watchOS 7.2（実機はApple Watch Watch Series 5）

#開発アプリ
英単語、短文暗記アプリ
英短文と和文を表示し、iPhoneでスワイプすると次の英短文が表示される。
Apple watchにも同じ英短文が表示される。
自分用に作っているのでエラー処理は不十分です。

対応済み
・英短文を記載したテキストファイルを読み込む
・覚えたかどうかをUserDefaultsを使って保存。起動時に読み込み。
・mp3の再生
・Today Extension(Widgetはそのうち対応・・・)
・Action Extension
・Apple Watch
・Apple Watch Extension
![iOS_app_image1s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1158403/7291361c-e09e-e660-f4a5-194ff574ac41.png)
![iOS_app_image2s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1158403/02ef006e-6254-5b12-b08a-baa1b7672dfa.png)



#GitHub
https://github.com/tanakat2020/Eng_shuffle2

#コンプリケーション
・graphicCircular
　番号の表示、本日取り組んだ進捗率を円環で表示
　毎日0時になったら、進捗率をリセット
・graphicRectangular
　iPhoneに同期して番号と英短文と和文を表示

#実装（ExtensionDelegate.swift）
　バックグラウンドで文字盤を更新できるようにするため下記のようにした。

```ExtensionDelegate.swift
  func reloadComplications() {
    // Update any complications on active watch faces.
    let server = CLKComplicationServer.sharedInstance()
    for complication in server.activeComplications ?? [] {
        server.reloadTimeline(for: complication)
    }
    //ComplicationDescriptorsについてはよく分かっていない。
    CLKComplicationServer.sharedInstance().reloadComplicationDescriptors()
  }

  func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
        // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
        for task in backgroundTasks {
            // Use a switch statement to check the task type
            switch task {
            case let backgroundTask as WKApplicationRefreshBackgroundTask:
                // Be sure to complete the background task once you’re done.
                self.scheduleBackgroundRefreshTasks()
                backgroundTask.setTaskCompletedWithSnapshot(true)
            case let snapshotTask as WKSnapshotRefreshBackgroundTask:
                // Snapshot tasks have a unique completion call, make sure to set your expiration date
                snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
            case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
                // Be sure to complete the connectivity task once you’re done.
                connectivityTask.setTaskCompletedWithSnapshot(false)
            case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
                // Be sure to complete the URL session task once you’re done.
                urlSessionTask.setTaskCompletedWithSnapshot(false)
            case let relevantShortcutTask as WKRelevantShortcutRefreshBackgroundTask:
                // Be sure to complete the relevant-shortcut task once you're done.
                relevantShortcutTask.setTaskCompletedWithSnapshot(false)
            case let intentDidRunTask as WKIntentDidRunRefreshBackgroundTask:
                // Be sure to complete the intent-did-run task once you're done.
                intentDidRunTask.setTaskCompletedWithSnapshot(false)
            default:
                // make sure to complete unhandled task types
                task.setTaskCompletedWithSnapshot(false)
            }
        }
    }

  func scheduleBackgroundRefreshTasks() {
        
        // Get the shared extension object.
        let watchExtension = WKExtension.shared()
        
        // If there is a complication on the watch face, the app should get at least four
        // updates an hour. So calculate a target date 15 minutes in the future.
        let targetDate = Date().addingTimeInterval(15.0 * 60.0)
        
        // Schedule the background refresh task.
        watchExtension.scheduleBackgroundRefresh(withPreferredDate: targetDate, userInfo: nil) { (error) in
            
            // Check for errors.
            if let error = error {
                print("*** An background refresh error occurred: \(error.localizedDescription) ***")
                return
            }
            print("*** Background Task Completed Successfully! ***")
        }
        
        WKExtension.shared().scheduleSnapshotRefresh(withPreferredDate: targetDate, userInfo: nil) { error in
                if (error == nil) {
                    print("successfully scheduled snapshot.  All background work completed.")
                }
            }
        
        // コンプリケーションデータを更新
        reloadComplications()
    }
```
#実装（ComplicationController.swift）
　コンプリケーションの更新

```ComplicationController.swift
    // Return the current timeline entry.
    func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
        // Call the handler with the current timeline entry
        handler(createTimelineEntry(forComplication: complication, date: Date()))
    }
    
    // Return future timeline entries.
    func getTimelineEntries(for complication: CLKComplication,
                            after date: Date,
                            limit: Int,
                            withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
        
        let fiveMinutes = 5.0 * 60.0
        let twentyFourHours = 24.0 * 60.0 * 60.0
        
        // Create an array to hold the timeline entries.
        var entries = [CLKComplicationTimelineEntry]()
        
        // Calculate the start and end dates.
        var current = date.addingTimeInterval(fiveMinutes)
        let endDate = date.addingTimeInterval(twentyFourHours)

        // Create a timeline entry for every five minutes from the starting time.
        // Stop once you reach the limit or the end date.
        while (current.compare(endDate) == .orderedAscending) && (entries.count < limit) {
            entries.append(createTimelineEntry(forComplication: complication, date: current))
            current = current.addingTimeInterval(fiveMinutes)
        }
        handler(entries)
    }
    
    //ComplicationDescriptorsを何に使うのかよく分からない。
    func getComplicationDescriptors(handler: @escaping ([CLKComplicationDescriptor]) -> Void) {
        let mySupportedFamilies = CLKComplicationFamily.allCases

            // Create the condition descriptor.
            let conditionDescriptor = CLKComplicationDescriptor(
                identifier: "complication_Identifier",
                displayName: "ENS-status",
                supportedFamilies: mySupportedFamilies)

        // Call the handler and pass an array of descriptors.
        handler([conditionDescriptor])
    }
    
    private func createTimelineEntry(forComplication complication: CLKComplication, date: Date) -> CLKComplicationTimelineEntry {
        let template = getComplicationTemplate(forComplication: complication, date: date)
        return CLKComplicationTimelineEntry(date: date, complicationTemplate: template)
    }
    
    func getComplicationTemplate(forComplication complication: CLKComplication, date: Date) -> CLKComplicationTemplate {
        switch complication.family {
        case .graphicCircular:
//            print("graphicCircular")
            return createGraphicCircleTemplate(forDate: date)
        case .graphicRectangular:
//            print("graphicRectangular")
            return createGraphicRectangularTemplate(forDate: date)
        case .modularSmall:
            return createModularSmallTemplate(forDate: date)
        case .modularLarge:
            return createModularLargeTemplate(forDate: date)
        case .utilitarianSmall:
            return createutilitarianSmallTemplate(forDate: date)
        case .utilitarianSmallFlat:
            return createutilitarianSmallTemplate(forDate: date)
        case .utilitarianLarge:
            return createutilitarianLargeTemplate(forDate: date)
        case .circularSmall:
            return createcircularSmallTemplate(forDate: date)
        case .extraLarge:
            return createextraLargeTemplate(forDate: date)
        case .graphicCorner:
            return creategraphicCornerTemplate(forDate: date)
        case .graphicBezel:
            return creategraphicBezelTemplate(forDate: date)
        case .graphicExtraLarge:
            return createGraphicExtraLargeTemplate(forDate: date)
        @unknown default:
            fatalError("*** Unknown Complication Family ***")
        }
    }
    
    //設定する時のプレ画面
    func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {

        let future = Date().addingTimeInterval(25.0 * 60.0 * 60.0)
        let template = getComplicationTemplate(forComplication: complication, date: future)
        handler(template)
    }
    
    private func creategraphicBezelTemplate(forDate date: Date) -> CLKComplicationTemplate {
        // Create the template using the providers.
        let circularTemplate = CLKComplicationTemplateGraphicCircularStackText(line1TextProvider: CLKSimpleTextProvider(text: "0.491"), line2TextProvider: CLKSimpleTextProvider(text: "0.491"))
        let template = CLKComplicationTemplateGraphicBezelCircularText(circularTemplate: circularTemplate)
        return template
    }
    
    private func creategraphicCornerTemplate(forDate date: Date) -> CLKComplicationTemplate {
        // Create the template using the providers.
        let gaugeColor = UIColor(red: 0.0, green: 167.0/255.0, blue: 219.0/255.0, alpha: 1.0)
        let template = CLKComplicationTemplateGraphicCornerGaugeText(gaugeProvider: CLKSimpleGaugeProvider(style: .fill,
                                                                                                           gaugeColor: gaugeColor,
                                                                                                           fillFraction: 0), outerTextProvider: CLKSimpleTextProvider(text: "0.491"))
        return template
    }
    
    
    private func createextraLargeTemplate(forDate date: Date) -> CLKComplicationTemplate {
        // Create the template using the providers.
        let template = CLKComplicationTemplateExtraLargeColumnsText(row1Column1TextProvider: CLKSimpleTextProvider(text: "11"), row1Column2TextProvider: CLKSimpleTextProvider(text: "11"), row2Column1TextProvider: CLKSimpleTextProvider(text: "11"), row2Column2TextProvider: CLKSimpleTextProvider(text: "11"))
        template.column2Alignment = .leading
        template.highlightColumn2 = false
        return template
    }

    private func createcircularSmallTemplate(forDate date: Date) -> CLKComplicationTemplate {
        // Create the template using the providers.
        let template = CLKComplicationTemplateCircularSmallSimpleText(textProvider: CLKSimpleTextProvider(text: "001"))
        return template
    }
    
    private func createutilitarianLargeTemplate(forDate date: Date) -> CLKComplicationTemplate {
        // Create the template using the providers.
        let template = CLKComplicationTemplateUtilitarianLargeFlat(textProvider: CLKSimpleTextProvider(text: "001"))
        return template
    }
    
    private func createutilitarianSmallTemplate(forDate date: Date) -> CLKComplicationTemplate {
        // Create the template using the providers.
        let template = CLKComplicationTemplateUtilitarianSmallFlat(textProvider: CLKSimpleTextProvider(text: "001"))
        return template
    }
    
    private func createModularLargeTemplate(forDate date: Date) -> CLKComplicationTemplate {
        // Create the template using the providers.
       let template = CLKComplicationTemplateModularLargeTable(headerTextProvider: CLKSimpleTextProvider(text: "001"), row1Column1TextProvider: CLKSimpleTextProvider(text: "001"), row1Column2TextProvider: CLKSimpleTextProvider(text: "001"), row2Column1TextProvider: CLKSimpleTextProvider(text: "001"), row2Column2TextProvider: CLKSimpleTextProvider(text: "001"))
        return template
    }
    
    // Return a modular small template.
    private func createModularSmallTemplate(forDate date: Date) -> CLKComplicationTemplate {
        // Create the data providers.
        let template = CLKComplicationTemplateModularSmallSimpleText(textProvider: CLKSimpleTextProvider(text: "001") )
        return template
    }
    
    // Return a graphic circle template.
    private func createGraphicCircleTemplate(forDate date: Date) -> CLKComplicationTemplate {
        // Create the data providers.
        let userDefaults = UserDefaults.standard
    
        if let messageA = userDefaults.string(forKey: "message3") {
            
            //改行区切りでデータを分割して配列に格納する。
            var dataList:[String] = []
            dataList = messageA.components(separatedBy: "\n")
            
            //"-"区切りでデータを分割して配列に格納する。
            var dataList2:[String] = []
            if dataList.count == 5 {
                dataList2 = dataList[4].components(separatedBy: "-")
            }
            else{
                dataList2.append("")
                dataList2.append("")
                dataList2[0] = "000"
                dataList2[1] = "0"
            }

            // centerTextProviderの実装
            let centerText = CLKSimpleTextProvider(text: dataList2[0])
            centerText.tintColor = .white

            let bottomText = CLKSimpleTextProvider(text: "Ens")
            bottomText.tintColor = .white

            //dataList[4] = "999"
            var value:Int = Int(dataList2[1])!

            if value > 20 {
                value = 20
            }

            if dataList2[1] == "999" {
                value = 0
            }

            let value_f:Float = Float(Float(value)/20)
            // gaugeProviderの実装
            //      let gaugeColor = UIColor(red: 255/255, green: 122/255.0, blue: 50/255.0, alpha: 1.0)
            let gaugeColor = UIColor(red: 0.0, green: 167.0/255.0, blue: 219.0/255.0, alpha: 1.0)
            let gaugeProvider =
                CLKSimpleGaugeProvider(style: .fill,
                                       gaugeColor: gaugeColor,
                                       fillFraction: value_f)

            let circularClosedGaugeTemplate = CLKComplicationTemplateGraphicCircularOpenGaugeSimpleText(gaugeProvider: gaugeProvider, bottomTextProvider: bottomText, centerTextProvider: centerText)
            return circularClosedGaugeTemplate
        }
        else{
            // centerTextProviderの実装
            let centerText = CLKSimpleTextProvider(text: "000")
            centerText.tintColor = .white
            
            let bottomText = CLKSimpleTextProvider(text: "Ens")
            bottomText.tintColor = .white
            
            let value_f:Float = 0

            // gaugeProviderの実装
            let gaugeColor = UIColor(red: 0.0, green: 167.0/255.0, blue: 219.0/255.0, alpha: 1.0)
            let gaugeProvider =
                CLKSimpleGaugeProvider(style: .fill,
                                       gaugeColor: gaugeColor,
                                       fillFraction: value_f)
            
            let circularClosedGaugeTemplate = CLKComplicationTemplateGraphicCircularOpenGaugeSimpleText(gaugeProvider: gaugeProvider, bottomTextProvider: bottomText, centerTextProvider: centerText)
            return circularClosedGaugeTemplate
        }       
    }

    // Return a large rectangular graphic template.
    private func createGraphicRectangularTemplate(forDate date: Date) -> CLKComplicationTemplate {
            let userDefaults = UserDefaults.standard

            if let messageA = userDefaults.string(forKey: "message3") {
            
                //改行区切りでデータを分割して配列に格納する。
                var dataList:[String] = []
                dataList = messageA.components(separatedBy: "\n")
                var textString = ""
                var textString2 = ""
                if dataList.count == 3 {
                    textString = "Eng_shu: " + dataList[2]
                }
                else if dataList.count == 5 {
                    textString = "Eng_shu: " + dataList[4]
                    textString2 = dataList[2]
                }
                else{
                    textString = "Eng_shu"
                }

                let textTemplate = CLKComplicationTemplateGraphicRectangularStandardBody(headerTextProvider: CLKSimpleTextProvider(text: textString ), body1TextProvider: CLKSimpleTextProvider(text: dataList[0] ), body2TextProvider: CLKSimpleTextProvider(text: textString2 ))
                    return textTemplate
                }
                else{
                    let textString = "Eng_shu:000-00"
                    let textString2 = "test"
                    let textString3 = "test"
                    
                    let textTemplate = CLKComplicationTemplateGraphicRectangularStandardBody(headerTextProvider: CLKSimpleTextProvider(text: textString ), body1TextProvider: CLKSimpleTextProvider(text: textString2 ), body2TextProvider: CLKSimpleTextProvider(text: textString3 ))
                    return textTemplate                    
                }
            }
    }
```