"swipe up으로 한영 전환 하는 거였어."
여기선 SwiftUI가 마이크를 못 열어
"Speak"라고 적힌 SwiftUI 텍스트 필드를 쓴 워치 담기 화면이 진짜 워치에서 키보드를 열었어. 설정 문제도, 워치가 마지막 모드를 기억한 것도 아니었어. SwiftUI 받아쓰기 컨트롤은 watchOS에서 쓸 수 없다고 선언돼 있어. 그래서 텍스트 필드엔 시스템이 기본으로 고른 입력 화면이 뜨고, 앱은 그걸 정할 수 없어. 워치엔 Speech 프레임워크도 없어서, 앱이 고른 언어로 자기 인식기를 돌릴 수도 없어. 되는 길은 WatchKit의 presentTextInputController(withSuggestions:allowedInputMode:completion:)이고, WKApplication.shared().visibleInterfaceController를 거쳐서 부를 수 있어. 이 레슨을 쓰면서 설치된 SDK로 직접 확인했어. SwiftUI 타입엔 @available(watchOS, unavailable)이 붙어 있고, watchOS SDK엔 Speech 프레임워크가 없어.
nil과 []는 다른 호출이야
화면은 suggestions 인자가 정해. nil을 넘기면 목록이 없다는 뜻이라 watchOS가 입력 화면을 건너뛰고 바로 마이크를 열어. []를 넘기면 목록은 있는데 마침 비어 있는 거라서, watchOS가 키보드, 손글씨, 마이크 중에 고르는 입력 화면을 보여줘. 호출하는 코드만 봐선 차이가 안 보이지만, 5초 만에 끝나는 담기냐 한 번 돌아가야 하는 담기냐가 여기서 갈려. 시뮬레이터는 이걸 판정 못 해. 워치 시뮬레이터는 어느 쪽이든 키보드로 넘어가니까, 거기서 찍은 키보드 스크린샷은 아무것도 증명 못 해.
앱 셋이 출시했다가 도로 뺀 두 번째 문
이중 언어 사용자가 영어로 열린 받아쓰기에 한국어로 말했더니 엉터리가 나왔어. 언어 전환 방법을 찾던 세션은 SDK 헤더를 읽다가 사용자가 입력 언어를 바꿀 수 있는 변형을 발견했어. 그러고는 앱 안에서 언어를 바꾸는 유일한 길이 지구본이 있는 빈 목록 화면이라고 결론 내고, 버튼 둘을 출시했지. 빠른 버튼 하나, "다른 언어" 버튼 하나. 다른 앱 둘도 그 버튼 짝을 그대로 따라 했어. 정작 주인 손목에선 두 번째 버튼이 쓸모가 없었고, 진짜 답은 주인이 직접 찾았어. 평범한 받아쓰기 화면에서 위로 쓸어올리면 언어 선택이 나와. 그 버튼은 앱 셋에서 모두 빠졌고, 공유 받아쓰기 모듈의 입구도 이제 하나야.
여기서 규칙 둘이 나왔어. 첫째, 어떤 기능에 닿는 길이 이것뿐이라는 말은 플랫폼 전체에 대한 주장이야. 길 하나를 되게 만들었다고 해서 그 주장이 서진 않아. 누군가는 평범한 길도 직접 해봐야 해. 둘째, 시뮬레이터에서 찍은 버튼 스크린샷은 뷰가 그려졌다는 증거일 뿐이고, 그 버튼이 여는 시스템 시트에 대해선 아무것도 증명하지 못해. 내용을 다시 확인하다가 바로잡은 것도 하나 있어. 언어를 고르는 변형은 한때 Swift에서 못 쓴다고 기록됐는데, 지금 SDK에선 import되고 컴파일돼. 그래도 그 변형은 바로 받아쓰기로 가지 않으니까, 가족 앱의 답은 여전히 문 하나와 위로 쓸기야.
발목 잡는 디테일 둘
visibleInterfaceController는 뷰가 화면에 뜨기 전까진 nil이야. 꺼져 있던 앱을 동작 버튼으로 켜면 바로 그 틈에 호출이 떨어질 수 있어. 그러니까 "받아쓰기 못 씀"은 계속되는 상태가 아니라 그 순간의 사정이야. 세션 내내 붙들고 있지 말고 시도할 때마다 다시 확인해. 또 완료 클로저는 오디오 탭 때와 같은 이유로 nonisolated 팩토리에서 만들어. 메인 액터 메서드 안에서 쓴 클로저는 그 격리를 물려받는데, 프레임워크는 그 클로저를 다른 곳에서 부를 수 있거든.