0

I want to have something similar to what the timer app on iOS is doing - it somehow synchronises with apple watch and runs the timer on both devices. Couldn't figure out how they do that - any ideas?

kovpas
  • 9,553
  • 6
  • 40
  • 44
  • you probably need [Handoff](https://developer.apple.com/handoff/) and read about [WCSession](https://developer.apple.com/documentation/watchconnectivity/wcsession) – Gonras Karols Dec 19 '22 at 18:45

1 Answers1

0

Synchronizing an application, like the timer, between the Apple Watch and iPhone requires the use of multiple communications technologies. For example:

Your main focus will be on the WatchConnectivity framework as it is most important in this scenario

  • Bluetooth: This technology allows devices to connect and communicate with each other wirelessly. By using Bluetooth, the iPhone and the Apple Watch can establish a connection and transfer data between each other.

  • iCloud: This is Apple's cloud storage and synchronization service, which allows users to store and access their data on multiple devices. By using iCloud, the app can store data in the cloud and access it on both the iPhone and the Apple Watch, even if the devices are not connected via Bluetooth.

  • WatchConnectivity framework: This framework provides the necessary tools and APIs for the iPhone and the Apple Watch to communicate and transfer data between each other. By using the WatchConnectivity framework, the app can establish a connection between the two devices and transfer data as needed.

You gave the Timer app as an example. The Timer app requires the use of the WatchConnectivity framework as stated above. Here it is in action for both devices (this will require a user interface to be created, but this is the essence of the app):

iPhone

import UIKit
import WatchConnectivity

class ViewController: UIViewController, WCSessionDelegate {
    
    // MARK: Properties
    var timer: Timer?
    var session: WCSession?
    var startTime: Date?
    
    // MARK: Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Configure Watch Connectivity
        if WCSession.isSupported() {
            session = WCSession.default
            session?.delegate = self
            session?.activate()
        }
    }
    
    // MARK: Action
    @IBAction func startTapped(_ sender: Any) {
        startTime = Date()
        timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
        // Send start time to Watch
        sendStartTimeToWatch()
    }
    
    @IBAction func stopTapped(_ sender: Any) {
        // Stop timer
        timer?.invalidate()
        timer = nil
    }
    
    // MARK: Helper
    @objc func updateTimer() {
        // Calculate time elapsed
        guard let startTime = startTime else { return }
        let timeElapsed = Date().timeIntervalSince(startTime)
        // Format time
        let minutes = Int(timeElapsed) / 60 % 60
        let seconds = Int(timeElapsed) % 60
        // Update label
        let timerString = String(format:"%02i:%02i", minutes, seconds)
        // Update label
        // Your label here
    }
    
    func sendStartTimeToWatch() {
        guard let startTime = startTime, let session = session else { return }
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: startTime, requiringSecureCoding: true)
            try session.updateApplicationContext(["startTime": data])
        } catch {
            print("Error: \(error)")
        }
    }
    
    // MARK: WCSessionDelegate
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        // no-op
    }
    
}

Apple Watch

import WatchKit
import Foundation
import WatchConnectivity

class InterfaceController: WKInterfaceController, WCSessionDelegate {
    
    // MARK: Properties
    var timer: Timer?
    var session: WCSession?
    var startTime: Date?
    
    // MARK: Lifecycle
    override func awake(withContext context: Any?) {
        super.awake(withContext: context)
        // Configure Watch Connectivity
        if WCSession.isSupported() {
            session = WCSession.default
            session?.delegate = self
            session?.activate()
        }
    }
    
    // MARK: Action
    @IBAction func startTapped() {
        // Receive start time from iPhone
        receiveStartTimeFromiPhone()
    }
    
    @IBAction func stopTapped() {
        // Stop timer
        timer?.invalidate()
        timer = nil
    }
    
    // MARK: Helper
    func startTimer() {
        guard let startTime = startTime else { return }
        // Calculate time elapsed
        let timeElapsed = Date().timeIntervalSince(startTime)
        // Format time
        let minutes = Int(timeElapsed) / 60 % 60
        let seconds = Int(timeElapsed) % 60
        // Update label
        let timerString = String(format:"%02i:%02i", minutes, seconds)
        // Update label
        // Your label here
        
        timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
    }
    
    @objc func updateTimer() {
        guard let startTime = startTime else { return }
        // Calculate time elapsed
        let timeElapsed = Date().timeIntervalSince(startTime)
        // Format time
        let minutes = Int(timeElapsed) / 60 % 60
        let seconds = Int(timeElapsed) % 60
        // Update label
        let timerString = String(format:"%02i:%02i", minutes, seconds)
        // Update label
        // Your label here
    }
    
    func receiveStartTimeFromiPhone() {
        guard let session = session else { return }
        session.sendMessage(["request": "startTime"], replyHandler: { (response) in
            if let data = response["startTime"] as? Data, let startTime = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Date.self, from: data) {
                self.startTime = startTime
                self.startTimer()
            }
        }, errorHandler: { (error) in
            print("Error: \(error)")
        })
    }
    
    // MARK: WCSessionDelegate
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        // no-op
    }
    
}
Masquerading
  • 209
  • 7
  • Thanks! Does that imply that the watch application is already open, or the awake method will be executed regardless of the user manually starting the app? – kovpas Dec 14 '22 at 07:48
  • It will activate upon waking the Apple Watch. But it still needs the interface; this is just the backend. There’s plenty of opportunity to expand upon it. – Masquerading Dec 14 '22 at 15:03
  • So user would have to wake up the watch… that was the question - if you check the timer app - it activates on the watch without any user interaction, when the timer finishes watch starts vibrating – kovpas Dec 14 '22 at 22:07
  • I see what you're saying. If the iPhone initiates a timer, the timer will begin running on the Watch without any wake. And once the timer finishes on the iPhone, it simultaneously vibrates on the Apple Watch. Based on your original post, I didn't know that was the functionality you had a question about. – Masquerading Dec 15 '22 at 01:10