공식문서
https://developer.apple.com/documentation/watchconnectivity
Watch Connectivity | Apple Developer Documentation
Implement two-way communication between an iOS app and its paired watchOS app.
developer.apple.com
1. Watch Connectivity란?
iOS 앱과 페어링된 watchOS 앱 간의 양방향 통신을 구현할 수 있도록 해주는 프레임워크입니다.
페어링된 기기의 앱과 소량의 데이터 또는 전체 파일을 전달할 수 있도록 해줍니다.
2. WCSession
watchOS 앱과 companion iOS 앱 간의 통신을 시작하는 객체
class WCSession : NSObject
iOS 앱과 watchOS 앱은 실행 도중 특정 시점에서 이 클래스의 인스턴스를 만들고 구성해야 합니다. 양쪽의 세션 객체가 모두 활성화되면, 두 프로세스는 메세지를 주고받으며 통신할 수 있게 됩니다.
‼️메시지를 보내거나 연결 상태에 대한 정보를 얻기 전에 세션 객체를 구성하고 활성화해야 합니다.
→ 세션을 활성화하기 전에 isSupported() 메서드를 호출하여 현재 장치가 Watch Connectivity 프레임워크를 사용할 수 있는지 확인할 수 있습니다.
세션을 구성하고 활성화시키기
세션을 구성하고 활성화하려면 아래 코드와 같이 기본 세션 객체에 델리게이트를 할당하고, 해당 객체의 activate() 메서드를 호출해줍니다. watchOS앱과 iOS 앱 각각에서 자체 세션 객체를 구성해주어야 합니다.
세션을 활성화하면 두 앱 간의 연결이 설정됩니다.
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
3. WCSessionDelegate
WCSession 객체가 보낸 메세지를 수신하기 위한 메서드를 정의하는 델리게이트 프로토콜
protocol WCSessionDelegate
데이터를 주고받기 위한 메서드는 여러 가지가 존재합니다. 그 중 몇 가지만 살펴보겠습니다.
sendMessage(_: replyHandler:errorHandler:)
- 페어링되고 활성화된 기기에 즉시 메시지(데이터)를 전송하고, 설정에 따라 응답을 제어해주는 메서드
- 가능한 한 빠르게 상대 기기에 데이터를 보내고 싶을 때 사용하면 좋습니다.
- 세션이 활성화된 동안에만 메서드를 호출할 수 있습니다. (백그라운드 전송 불가)
func sendMessage(
_ message: [String : Any],
replyHandler: (([String : Any]) -> Void)?,
errorHandler: ((Error) -> Void)? = nil
)
파라미터
- message
- 보내고자 하는 property-list 타입 값의 딕셔너리. 수신하는 기기 쪽에서 받으려는 데이터의 타입과 맞게 타입을 지정해주어야 하며, 이 매개변수는 nil이 되어서는 안됩니다.
- replyHandler
- 상대 기기로부터 응답을 받기 위한 응답 핸들러. 답장을 받고 싶지 않다면 nil을 지정해주면 됩니다.
- errorHandler
- 에러가 발생했을 떄 실행되는 블록. 에러 정보를 신경 쓸 필요가 없다면 nil을 지정해주면 됩니다.
transferUserInfo(_:)
- 상대 기기에 데이터를 전송해주는 메서드
- 이 메서드를 사용하여 전송된 딕셔너리는 다른 기기의 큐에 들어가 대기하게 되고, 순차적으로 전달됩니다.
- 전송이 시작된 후 앱이 일시 중지되더라도 전송 작업은 계속됩니다. (백그라운드에서도 전송 가능)
func transferUserInfo(_ userInfo: [String : Any] = [:]) -> WCSessionUserInfoTransfer transferUserInfo(_ userInfo: [String : Any] = [:]) -> WC
파라미터
- userInfo
- 보내고자 하는 property-list 타입 값의 딕셔너리. 수신하는 기기 쪽에서 받으려는 데이터의 타입과 맞게 타입을 지정해주어야 하며, 이 매개변수는 nil이 되어서는 안됩니다.
🚧 Xcode 시뮬레이터에서는 transferUserInfo(_:) 메서드를 지원하지 않기 때문에 전송 테스트는 실제 기기에서 이루어져야 합니다.
transferFile(_:metadata:)
- 상대 기기에 파일 또는 데이터를 전송해주는 메서드
- transferUserInfo(_:)와 거의 동일하며 파일 전송도 지원해줍니다.
- 전송이 시작된 후 앱이 일시 중지되더라도 전송 작업은 계속됩니다. (백그라운드에서도 전송 가능)
func transferFile(
_ file: URL,
metadata: [String : Any]?
) -> WCSessionFileTransfer
파라미터
- file
- 보낼 파일을 식별하는 파일 기반 URL. 지정된 파일은 현재 앱에서 읽을 수 있어야 하며, 이 매개변수는 nil이 되어서는 안됩니다.
- metadata
- 추가적으로 보내주고자 하는 데이터가 포함된 딕셔너리. 딕셔너리의 값은 모두 property list 객체 타입이어야 합니다.
- 필수 값은 아니기 때문에 nil 값을 지정할 수 있습니다.
전송하는 딕셔너리(metadata)에 non-property list 객체 타입이 포함되어 있거나, 지정된 URL(file)에 유효한 파일이 포함되어 있지 않은 경우 오류가 발생할 수 있습니다.
🚧 Xcode 시뮬레이터에서는 transferFile(_:metadata:) 메서드를 지원하지 않기 때문에 전송 테스트는 실제 기기에서 이루어져야 합니다.
4. Watch Connectivity 활용 예제 코드
샘플 iOS 앱에서는 Send Message 버튼을 눌렀을 때 sendMessage() 메서드를 통해 입력한 텍스트 메세지가 watchOS 앱으로 전송됩니다. 이 버튼은 애플워치가 백그라운드 상태일 때는 동작하지 않습니다.
Send number 버튼을 누르면 -/+로 설정해준 숫자가 transferUserInfo() 메서드를 통해 애플워치로 전달됩니다. 이 버튼은 애플워치가 백그라운드 상태여도 동작합니다.
Update 버튼을 누르면 watchOS 앱이 설치되어 있는지, 또는 현재 워치에 연결할 수 있는지를 확인해줍니다.
iOS 앱에서 데이터를 전송하면 watchOS 앱의 화면에서 확인할 수 있습니다.
코드로 살펴봅시다.
먼저 iOS와 watchOS 각각의 앱에 세션 객체를 구성하고 활성화시켜줍니다.
- iOS 앱에 watchOS와의 연결을 관리해줄 클래스를 하나 만들어줍니다.
- WCSession 객체를 구성해줍니다.
- WCSessionDelegate 프로토콜을 준수하기 위해 3개의 session() 메서드를 필수적으로 구현해주어야 합니다. (watchOS에서는 1개만)
// ViewModelPhone.swift
import Foundation
import WatchConnectivity
// watchOS와의 연결을 관리하는 클래스 -> NSObject, WCSessionDelegate 프로토콜을 준수해야 함
// WCSessionDelegate 프로토콜 준수 시에 아래 3가지 델리게이트 메서드를 정의해줘야함
class ViewModelPhone: NSObject, WCSessionDelegate {
var session: WCSession
init(session: WCSession = .default) {
self.session = session
super.init()
session.delegate = self
session.activate()
}
/**
델리게이트 메서드
- 맨 아래 2개 메서드는 watchOS에서는 구현 X
- iOS에서는 3개 다 구현
*/
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
}
}
- watchOS 앱에서도 iOS 앱과의 연결을 관리해줄 클래스를 만들어줍니다.
// ViewModelWatch.swift
import Foundation
import WatchConnectivity
class ViewModelWatch: NSObject, WCSessionDelegate, ObservableObject {
var session: WCSession
@Published var messageText = "" // iOS 앱에서 수신한 메세지를 화면에 보여주기 위한 문자열
@Published var number = ""
init(session: WCSession = .default) {
self.session = session
super.init()
session.delegate = self
session.activate()
}
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
// 다른 기기의 세션에서 sendMessage() 메서드로 메세지를 받았을 때 호출되는 메서드
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
DispatchQueue.main.async {
// 받은 메세지에서 원하는 Key값(여기서는 "message")으로 메세지 String을 가져온다.
// messageText는 Published 프로퍼티이기 때문에 DispatchQueue.main.async로 실행해줘야함
self.messageText = message["message"] as? String ?? "Unknown"
}
}
// 다른 기기의 세션으로부터 transferUserInfo() 메서드로 데이터를 받았을 때 호출되는 메서드
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
DispatchQueue.main.async {
self.number = userInfo["number"] as? String ?? "0"
}
}
}
iOS와 watchOS 각각의 뷰를 구성하고, 데이터 전송 메서드를 활용한 코드를 작성해줍니다.
- iOS
import SwiftUI
struct ContentView: View {
var model = ViewModelPhone()
@State var reachable = "No"
@State var messsageText = ""
@State var numberValue = 0
var body: some View {
VStack {
Text("Reachable \(reachable)")
/**
WCSession의 isReachanle 프로퍼티를 사용해서 시계 앱이 설치되어 있는지 또는 현재 시계에 연결할 수 있는지를 확인
--> 워치에서 앱 실행하고 Update 버튼을 누르면 No -> Yes로 변함
--> 워치 홈 화면으로 나가고 Update 버튼을 누르면 다시 Yes -> No로 변함
*/
Button {
if self.model.session.isReachable {
self.reachable = "Yes"
}
else {
self.reachable = "No"
}
} label: {
Text("Update")
}
/** 텍스트를 입력하고, 버튼을 누르면 애플워치로 데이터가 전송됨 */
HStack {
TextField("Input your message", text: $messsageText)
Button {
self.model.session.sendMessage(["message" : self.messsageText], replyHandler: nil) { error in
/**
다음의 상황에서 오류가 발생할 수 있음
-> property-list 데이터 타입이 아닐 때
-> watchOS가 reachable 상태가 아닌데 전송할 때
*/
print(error.localizedDescription)
}
} label: {
Text("Send Message")
}
}
.padding()
/** 번호를 입력하고, 버튼을 누르면 데이터가 전송됨 */
HStack {
Button("-") {
if numberValue > 0 {
numberValue -= 1
}
}
Text("\(numberValue)")
Button("+") {
numberValue += 1
}
}
.padding()
Button {
self.model.session.transferUserInfo(["number" : String(self.numberValue)])
} label: {
Text("Send number")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
- watchOS
import SwiftUI
struct ContentView: View {
@ObservedObject var model = ViewModelWatch()
var body: some View {
VStack {
Text(self.model.messageText)
Text(self.model.number)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
'iOS' 카테고리의 다른 글
[ iOS ] String → URL 변환 시 nil이 반환되는 문제 (0) | 2024.02.12 |
---|---|
[ iOS ] 팀 작업 시에 Bundle Identifier 하나로 공유하기 (0) | 2023.08.27 |
[ iOS ] Core Motion 사용해보기 (0) | 2023.08.08 |
[ iOS ] Swift Package(.swiftpm)에서 .mlmodel 모델 불러오기 (0) | 2023.04.22 |
[ iOS ] Xcode 프로젝트 기본 생성 파일 설명 (14.2 버전 기준, UIKit) (0) | 2023.03.06 |