SiriKit의 의도가 앱에 적합합니까? 그렇다면 사용 방법은 다음과 같습니다.

게시 됨: 2022-03-10
빠른 요약 ↬ 작년부터 Apple의 사전 정의된 사용 사례 중 하나에 맞는 앱에 Siri 지원을 추가할 수 있었습니다. SiriKit이 적합한지 여부와 사용 방법을 알아보십시오.

iOS 5부터 Siri는 iPhone 사용자가 Apple 앱으로 메시지를 보내고, 미리 알림을 설정하고, 레스토랑을 찾는 데 도움이 되었습니다. iOS 10부터 일부 자체 앱에서도 Siri를 사용할 수 있게 되었습니다.

이 기능을 사용하려면 앱이 Apple의 사전 정의된 Siri "도메인 및 의도"에 맞아야 합니다. 이 기사에서는 이것이 무엇인지 알아보고 앱에서 사용할 수 있는지 여부를 확인합니다. 할 일 목록 관리자인 간단한 앱을 사용하여 Siri 지원을 추가하는 방법을 알아보겠습니다. 또한 SiriKit과 함께 도입된 새로운 유형의 확장에 대한 구성 및 Swift 코드에 대한 Apple 개발자 웹 사이트의 지침 인 Intents 확장을 살펴보겠습니다.

이 기사의 코딩 부분에 도달하면 Xcode(최소한 버전 9.x)가 필요하며, 우리가 작은 작업에 Siri를 추가할 것이기 때문에 Swift에서 iOS 개발에 익숙하다면 좋을 것입니다. 앱. Apple 개발자 웹 사이트에서 확장 프로그램을 설정하고 앱에 Siri 확장 코드를 추가하는 단계를 살펴보겠습니다.

"시리야, 내가 왜 네가 필요해?"

때로는 소파에 앉아 양손을 자유롭게 사용하며 화면에 온전히 집중할 수 있습니다. 아마도 나는 우리 엄마의 생일을 계획하기 위해 내 여동생에게 문자를 보내거나 Trello에서 질문에 답장을 보낼 것입니다. 앱을 볼 수 있습니다. 화면을 탭할 수 있습니다. 입력할 수 있습니다.

하지만 시계에 문자가 왔을 때 팟캐스트를 들으면서 동네를 돌아다닐 수도 있습니다. 휴대폰은 주머니에 넣고 걸어가는 동안 쉽게 전화를 받지 못한다.

점프 후 더! 아래에서 계속 읽기 ↓

Siri를 사용하면 헤드폰의 제어 버튼을 누른 상태에서 "언니에게 2시까지 가겠다고 문자를 보내세요."라고 말할 수 있습니다. Siri는 이동 중에 휴대전화에 온전히 집중할 수 없거나 상호 작용이 미미할 때 유용하지만 여러 번 탭하고 여러 번 입력해야 합니다.

이러한 상호 작용에 Apple 앱을 사용하려는 경우 괜찮습니다. 그러나 메시징과 같은 일부 앱 범주에는 매우 인기 있는 대안이 있습니다. 차량을 예약하거나 레스토랑에서 테이블을 예약하는 것과 같은 다른 활동은 Apple의 내장 앱으로도 불가능하지만 Siri에게는 완벽합니다.

음성 비서에 대한 Apple의 접근 방식

타사 앱에서 Siri를 활성화하기 위해 Apple은 사용자의 음성에서 소리를 가져와 어떻게든 요청을 수행할 수 있는 방식으로 앱에 전달하는 메커니즘을 결정해야 했습니다. 이를 가능하게 하기 위해 Apple은 사용자가 요청에 앱 이름을 언급할 것을 요구하지만 나머지 요청을 처리할 수 있는 몇 가지 옵션이 있었습니다.

  • 앱에 사운드 파일을 보낼 수 있습니다.
    이 접근 방식의 이점은 앱이 말 그대로 사용자가 요청할 수 있는 모든 요청을 처리하려고 시도할 수 있다는 것입니다. 아마존이나 구글은 이미 정교한 음성 인식 서비스를 가지고 있기 때문에 이 접근 방식을 좋아했을 것입니다. 그러나 대부분의 앱은 이를 쉽게 처리할 수 없습니다.
  • 연설을 텍스트로 변환하여 보낼 수 있습니다.
    많은 앱에 정교한 자연어 구현이 없기 때문에 사용자는 일반적으로 매우 특정한 구문을 고수해야 하며 비영어 지원은 앱 개발자가 구현해야 합니다.
  • 당신이 이해하는 문구 목록을 제공하도록 요청할 수 있습니다.
    이 메커니즘은 Amazon이 Alexa로 수행하는 작업에 더 가깝고("기술" 프레임워크에서) SiriKit이 현재 처리할 수 있는 것보다 훨씬 더 많은 Alexa 사용을 가능하게 합니다. Alexa 기술에서는 Alexa가 자동으로 채울 자리 표시자 변수가 있는 구를 제공합니다. 예를 들어 "Alexa, $TIME$ 에서 $REMINDER$ 로 알림" — Alexa는 사용자가 말한 내용에 대해 이 구문을 실행하고 TIMEREMINDER 값을 알려줍니다. 이전 메커니즘과 마찬가지로 개발자가 모든 번역을 수행해야 하며 사용자가 약간 다르게 말하면 유연성이 많지 않습니다.
  • 매개변수가 있는 요청 목록을 정의하고 앱에 구조화된 요청을 보낼 수 있습니다.
    이것이 실제로 Apple이 하는 일이며 이점은 다양한 언어를 지원할 수 있다는 것입니다. 사용자가 요청을 표현하는 모든 방법을 이해하기 위해 모든 작업을 수행합니다. 가장 큰 단점은 Apple이 정의한 요청에 대해서만 처리기를 구현할 수 있다는 것입니다. 예를 들어 메시징 앱이 있는 경우에는 유용하지만 음악 스트리밍 서비스나 팟캐스트 플레이어가 있다면 지금 당장 SiriKit을 사용할 방법이 없습니다.

유사하게, 앱이 사용자에게 다시 이야기하는 세 가지 방법이 있습니다. 소리로, 변환되는 텍스트로, 또는 당신이 말하고 싶은 종류를 표현하고 시스템이 그것을 표현하는 정확한 방법을 알아내도록 하는 것입니다. 마지막 솔루션(Apple이 하는 일)은 번역의 부담을 Apple에 지우지만, 사물을 설명하기 위해 자신의 단어를 사용하는 데 제한된 방법을 제공합니다.

처리할 수 있는 요청의 종류는 SiriKit의 도메인 및 의도에 정의되어 있습니다. 인텐트는 연락처에 문자 메시지를 보내거나 사진을 찾는 것과 같이 사용자가 만들 수 있는 요청 유형입니다. 각 인텐트에는 매개변수 목록이 있습니다. 예를 들어 문자 메시지를 보내려면 연락처와 메시지가 필요합니다.

도메인은 관련된 인텐트의 그룹일 뿐입니다. 문자를 읽고 문자를 보내는 것은 모두 메시징 도메인에 있습니다. 라이드를 예약하고 위치를 파악하는 것은 라이드 예약 영역에 있습니다. VoIP 전화 걸기, 운동 시작하기, 사진 검색 등을 위한 도메인이 있습니다. SiriKit의 문서에는 도메인 및 해당 의도의 전체 목록이 포함되어 있습니다.

Siri에 대한 일반적인 비판은 Google 및 Alexa만큼 요청을 처리하지 못하는 것 같고 Apple의 경쟁 업체가 지원하는 타사 음성 생태계가 더 풍부하다는 것입니다.

나는 그 비판에 동의한다. 앱이 현재 의도에 맞지 않으면 SiriKit을 사용할 수 없으며 할 수 있는 일이 없습니다. 앱이 적합하더라도 Siri가 말하거나 이해하는 모든 단어를 제어할 수는 없습니다. 따라서 앱에서 특정한 방식으로 이야기하는 경우 항상 Siri에게 가르칠 수는 없습니다.

iOS 개발자의 희망은 Apple이 의도 목록을 크게 확장하고 자연어 처리가 훨씬 더 좋아지는 것입니다. 그렇게 하면 개발자가 번역을 하거나 같은 말을 하는 모든 방법을 이해할 필요 없이 작동하는 음성 도우미가 생깁니다. 그리고 구조화된 요청에 대한 지원을 구현하는 것은 실제로 매우 간단합니다. 자연어 파서를 구축하는 것보다 훨씬 쉽습니다.

인텐트 프레임워크의 또 다른 큰 이점은 Siri 및 음성 요청에 국한되지 않는다는 것입니다. 지금도 지도 앱은 앱의 인텐트 기반 요청(예: 식당 예약)을 생성할 수 있습니다. 이것은 프로그래밍 방식으로 수행됩니다(음성 또는 자연어가 아님). Apple이 앱이 서로의 노출된 의도를 발견하도록 허용했다면 앱이 함께 작동하는 훨씬 더 나은 방법이 생겼을 것입니다(x-콜백 스타일 URL과 반대).

마지막으로 인텐트는 매개변수가 있는 구조화된 요청이기 때문에 앱에서 매개변수가 누락되었거나 일부 옵션을 구별하는 데 도움이 필요함을 표현할 수 있는 간단한 방법이 있습니다. 그런 다음 Siri는 앱이 대화를 수행할 필요 없이 매개변수를 해결하기 위해 후속 질문을 할 수 있습니다.

승차 예약 도메인

도메인과 인텐트를 이해하기 위해 승차 예약 도메인을 살펴보겠습니다. 이것은 Siri에게 Lyft 차량을 가져오도록 요청하는 데 사용할 도메인입니다.

Apple은 승차를 요청하는 방법과 이에 대한 정보를 얻는 방법을 정의하지만 실제로 이 요청을 처리할 수 있는 내장 Apple 앱은 없습니다. 이것은 SiriKit 지원 앱이 필요한 몇 안 되는 도메인 중 하나입니다.

음성을 통해 또는 지도에서 직접 의도 중 하나를 호출할 수 있습니다. 이 도메인의 의도 중 일부는 다음과 같습니다.

  • 승차 요청
    이것을 사용하여 승차를 예약하십시오. 픽업 및 하차 위치를 제공해야 하며 앱이 파티 규모와 원하는 차량 종류를 알아야 할 수도 있습니다. 샘플 문구는 "<앱 이름>으로 승차를 예약해 주세요."일 수 있습니다.
  • 라이드 상태 가져오기
    이 인텐트를 사용하여 요청이 수신되었는지 확인하고 위치를 포함하여 차량 및 운전자에 대한 정보를 얻으십시오. 지도 앱은 이 인텐트를 사용하여 다가오는 자동차의 업데이트된 이미지를 표시합니다.
  • 라이드 취소
    예약한 라이드를 취소할 때 사용합니다.

이러한 의도에 대해 Siri는 더 많은 정보를 알아야 할 수 있습니다. 의도 처리기를 구현할 때 알 수 있듯이 Intents 확장은 필수 매개 변수가 누락되었음을 Siri에 알릴 수 있으며 Siri는 사용자에게 이를 묻는 메시지를 표시합니다.

지도에서 인텐트를 프로그래밍 방식으로 호출할 수 있다는 사실은 인텐트가 향후 앱 간 통신을 어떻게 활성화할 수 있는지 보여줍니다.

참고 : Apple의 개발자 웹사이트에서 전체 도메인 목록과 해당 의도를 얻을 수 있습니다. 승차 예약을 포함하여 구현된 많은 도메인 및 인텐트가 있는 샘플 Apple 앱도 있습니다.

앱에 목록 및 메모 도메인 지원 추가하기

이제 SiriKit의 기본 사항을 이해했으므로 처리하려는 각 인텐트에 대한 많은 구성과 클래스가 포함된 앱에서 Siri 지원을 추가하는 방법을 살펴보겠습니다.

이 문서의 나머지 부분은 앱에 Siri 지원을 추가하는 자세한 단계로 구성되어 있습니다. 수행해야 할 5가지 높은 수준의 작업이 있습니다.

  1. Apple의 개발자 웹 사이트에서 새 권한이 있는 프로비저닝 프로필을 만들어 앱에 새 확장을 추가할 준비를 하십시오.
  2. 권한을 사용하도록 앱을 구성합니다( plist 를 통해).
  3. Xcode의 템플릿을 사용하여 몇 가지 샘플 코드를 시작하십시오.
  4. Siri 의도를 지원하는 코드를 추가합니다.
  5. plist 를 통해 Siri의 어휘를 구성합니다.

걱정하지 마십시오. 각 단계를 진행하면서 확장 및 자격에 대해 설명하겠습니다.

Siri 부분에만 집중하기 위해 간단한 할일 목록 관리자인 List-o-Mat을 준비했습니다.

List-o-Mat의 데모를 보여주는 애니메이션 GIF
List-o-Mat에서 목록 만들기(큰 미리보기)

GitHub에서 샘플 List-o-Mat의 전체 소스를 찾을 수 있습니다.

그것을 만들기 위해 내가 한 일은 Xcode Master-Detail 앱 템플릿으로 시작하여 두 화면을 모두 UITableView 로 만드는 것뿐이었습니다. 목록 및 항목을 추가 및 삭제하는 방법과 완료한 항목을 확인하는 방법을 추가했습니다. 모든 탐색은 템플릿에 의해 생성됩니다.

데이터를 저장하기 위해 구조체를 JSON으로 변환하고 documents 폴더의 텍스트 파일에 저장하는 Codable 프로토콜(WWDC 2017에서 소개됨)을 사용했습니다.

나는 의도적으로 코드를 매우 간단하게 유지했습니다. Swift에 대한 경험이 있고 뷰 컨트롤러를 만든 적이 있다면 문제가 없을 것입니다.

이제 SiriKit 지원을 추가하는 단계를 진행할 수 있습니다. 상위 수준 단계는 모든 앱과 구현하려는 도메인 및 인텐트에 대해 동일합니다. 우리는 주로 Apple의 개발자 웹사이트를 다루며 plist 를 편집하고 약간의 Swift를 작성할 것입니다.

List-o-Mat의 경우 메모 작성 앱 및 할 일 목록과 같은 항목에 광범위하게 적용할 수 있는 목록 및 메모 도메인에 중점을 둘 것입니다.

목록 및 메모 도메인에는 앱에 적합한 다음 인텐트가 있습니다.

  • 작업 목록을 가져옵니다.
  • 목록에 새 작업을 추가합니다.

Siri와의 상호 작용은 실제로 앱 외부에서 발생하기 때문에(앱이 실행되고 있지 않을 때도 마찬가지일 수 있음) iOS는 확장 기능을 사용하여 이를 구현합니다.

의도 확장

확장으로 작업한 적이 없다면 다음 세 가지 주요 사항을 알아야 합니다.

  1. 확장은 별도의 프로세스입니다. 앱 번들 내부에 제공되지만 자체 샌드박스와 함께 완전히 자체적으로 실행됩니다.
  2. 앱과 확장 프로그램은 동일한 앱 그룹에 속해 있어 서로 통신할 수 있습니다. 가장 쉬운 방법은 그룹의 공유 샌드박스 폴더를 사용하는 것입니다.
  3. 확장 프로그램에는 고유한 앱 ID, 프로필 및 권한이 필요합니다.

앱에 확장 프로그램을 추가하려면 먼저 개발자 계정에 로그인하고 "인증서, 식별자 및 프로필" 섹션으로 이동하세요.

Apple 개발자 앱 계정 데이터 업데이트

Apple 개발자 계정에서 가장 먼저 해야 할 일은 앱 그룹을 만드는 것입니다. "식별자" 아래의 "앱 그룹" 섹션으로 이동하여 하나를 추가합니다.

앱 그룹 등록을 위한 Apple 개발자 웹 사이트 대화 상자의 스크린샷
앱 그룹 등록(큰 미리보기)

group 으로 시작해야 하며 그 뒤에 일반적인 역 도메인 기반 식별자가 와야 합니다. 접두사가 있기 때문에 나머지는 앱의 식별자를 사용할 수 있습니다.

그런 다음 이 그룹을 사용하고 Siri를 활성화하려면 앱의 ID를 업데이트해야 합니다.

  1. "앱 ID" 섹션으로 이동하여 앱의 ID를 클릭합니다.
  2. "편집" 버튼을 클릭하십시오.
  3. 앱 그룹을 활성화합니다(다른 확장에 대해 활성화되지 않은 경우).
    앱 ID에 대한 앱 그룹을 활성화하는 Apple 개발자 웹 사이트의 스크린샷
    앱 그룹 활성화(큰 미리보기)
  4. 그런 다음 "편집" 버튼을 클릭하여 앱 그룹을 구성합니다. 이전의 앱 그룹을 선택합니다.
    앱 그룹 이름을 설정하기 위한 Apple 개발자 웹 사이트 대화 상자의 스크린샷
    앱 그룹 이름 설정(큰 미리보기)
  5. SiriKit을 활성화합니다.
    활성화된 SiriKit의 스크린샷
    SiriKit 활성화(큰 미리보기)
  6. "완료"를 클릭하여 저장합니다.

이제 확장에 대한 새 앱 ID를 만들어야 합니다.

  1. 동일한 '앱 ID' 섹션에서 새 앱 ID를 추가합니다. 이것은 접미사가 있는 앱의 식별자입니다. Intents 만을 접미사로 사용하지 마세요 . 이 이름이 Swift에서 모듈의 이름이 되고 실제 Intents 와 충돌할 것이기 때문입니다.
    앱 ID를 생성하기 위한 Apple 개발자 화면의 스크린샷
    Intents 확장에 대한 앱 ID 만들기(큰 미리보기)
  2. 앱 그룹에 대해서도 이 앱 ID를 활성화하고 이전과 같이 그룹을 설정합니다.

이제 Intents 확장에 대한 개발 프로비저닝 프로필을 만들고 앱의 프로비저닝 프로필을 다시 생성합니다. 평소와 같이 다운로드하여 설치하십시오.

이제 프로필이 설치되었으므로 Xcode로 이동하여 앱의 자격을 업데이트해야 합니다.

Xcode에서 앱의 권한 업데이트

Xcode로 돌아가서 프로젝트 탐색기에서 프로젝트 이름을 선택합니다. 그런 다음 앱의 주요 대상을 선택하고 "기능" 탭으로 이동합니다. 거기에 Siri 지원을 켜는 스위치가 있습니다.

SiriKit이 활성화되었음을 보여주는 Xcode의 자격 화면 스크린샷
앱의 자격에서 SiriKit을 활성화합니다. (큰 미리보기)

목록 아래에서 앱 그룹을 켜고 구성할 수 있습니다.

앱 그룹이 활성화되고 구성되었음을 보여주는 Xcode의 자격 화면 스크린샷
앱의 앱 그룹 구성(큰 미리보기)

올바르게 설정했다면 앱의 .entitlements 파일에 다음과 같이 표시됩니다.

자격이 설정되었음을 보여주는 앱 plist의 스크린샷
plist는 설정한 자격을 보여줍니다(큰 미리보기).

이제 마침내 Intents 확장 대상을 프로젝트에 추가할 준비가 되었습니다.

인텐트 확장 추가

드디어 확장 기능을 추가할 준비가 되었습니다. Xcode에서 "파일" → "새 대상"을 선택합니다. 이 시트가 나타납니다:

Xcode의 New Target 대화 상자에서 Intents 확장을 보여주는 스크린샷
프로젝트에 Intents 확장 추가(큰 미리보기)

"Intents Extension"을 선택하고 "Next" 버튼을 클릭합니다. 다음 화면을 작성하십시오.

Intents 확장을 구성하는 방법을 보여주는 Xcode의 스크린샷
Intents 확장 구성(큰 미리보기)

제품 이름은 Apple 개발자 웹 사이트의 인텐트 앱 ID에서 접미사를 지정한 것과 일치해야 합니다.

인텐트 UI 확장을 추가하지 않기로 선택했습니다. 이것은 이 문서에서 다루지 않지만 필요할 경우 나중에 추가할 수 있습니다. 기본적으로 Siri의 시각적 결과에 자신의 브랜딩 및 디스플레이 스타일을 적용하는 방법입니다.

완료되면 Xcode는 Siri 구현을 위한 시작 부분으로 사용할 수 있는 인텐트 핸들러 클래스를 생성합니다.

인텐트 핸들러: 해결, 확인 및 처리

Xcode는 우리를 위한 출발점이 있는 새로운 타겟을 생성했습니다.

가장 먼저 해야 할 일은 이 새 대상이 앱과 동일한 앱 그룹에 있도록 설정하는 것입니다. 이전과 마찬가지로 대상의 "기능" 탭으로 이동하여 앱 그룹을 켜고 그룹 이름으로 구성합니다. 같은 그룹의 앱에는 서로 파일을 공유하는 데 사용할 수 있는 샌드박스가 있습니다. Siri 요청이 앱에 도달하려면 이것이 필요합니다.

List-o-Mat에는 그룹 문서 폴더를 반환하는 기능이 있습니다. 공유 파일을 읽거나 쓰고 싶을 때마다 사용해야 합니다.

 func documentsFolder() -> URL? { return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.app-o-mat.ListOMat") }

예를 들어 목록을 저장할 때 다음을 사용합니다.

 func save(lists: Lists) { guard let docsDir = documentsFolder() else { fatalError("no docs dir") } let url = docsDir.appendingPathComponent(fileName, isDirectory: false) // Encode lists as JSON and save to url }

Intents 확장 템플릿은 IntentHandler 라는 클래스와 함께 IntentHandler.swift 라는 파일을 생성했습니다. 또한 확장의 plist 에서 의도의 진입점으로 구성했습니다.

IntentHandler가 진입점으로 구성되는 방법을 보여주는 Xcode의 스크린샷
인텐트 확장 plist는 IntentHandler를 진입점으로 구성합니다.

이 동일한 plist 에서 우리가 지원하는 인텐트를 선언하는 섹션을 볼 수 있습니다. 목록 검색을 허용하는 INSearchForNotebookItemsIntent 로 시작하겠습니다. IntentsSupported 아래의 배열에 추가합니다.

확장 plist가 처리하는 인텐트를 나열해야 함을 보여주는 Xcode의 스크린샷
인텐트의 이름을 인텐트 plist에 추가합니다(큰 미리보기).

이제 IntentHandler.swift 로 이동하여 내용을 다음 코드로 바꿉니다.

 import Intents class IntentHandler: INExtension { override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() default: return nil } } }

handler 함수는 특정 의도를 처리할 개체를 가져오기 위해 호출됩니다. 이 클래스의 모든 프로토콜을 구현하고 self 를 반환할 수 있지만 더 잘 정리된 상태로 유지하기 위해 각 인텐트를 자체 클래스에 넣을 것입니다.

우리는 몇 가지 다른 클래스를 가질 계획이므로 그들 사이에 공유해야 하는 코드에 대한 공통 기본 클래스를 제공하겠습니다.

 class ListOMatIntentsHandler: NSObject { }

인텐트 프레임워크는 NSObject 에서 상속하도록 요구합니다. 우리는 나중에 몇 가지 방법을 채울 것입니다.

다음과 같이 검색 구현을 시작합니다.

 class SearchItemsIntentHandler: ListOMatIntentsHandler, INSearchForNotebookItemsIntentHandling { }

인텐트 핸들러를 설정하려면 세 가지 기본 단계를 구현해야 합니다.

  1. 매개변수를 해결 합니다.
    필수 매개변수가 제공되었는지 확인하고 완전히 이해하지 못하는 매개변수는 명확하게 지정하십시오.
  2. 요청이 가능한지 확인 합니다.
    이는 선택 사항인 경우가 많지만 각 매개 변수가 좋다는 것을 알고 있더라도 여전히 외부 리소스에 액세스해야 하거나 다른 요구 사항이 있을 수 있습니다.
  3. 요청을 처리 합니다.
    요청받은 일을 하십시오.

우리가 구현할 첫 번째 의도인 INSearchForNotebookItemsIntent 는 작업 검색으로 사용할 수 있습니다. 이에 대해 처리할 수 있는 요청의 종류는 "In List-o-Mat에서 식료품 매장 목록을 보여주세요." 또는 "In List-o-Mat에서 매장 목록을 보여주세요."입니다.

제쳐두고 : "List-o-Mat"는 Siri가 앱에서 하이픈을 사용하는 데 어려움을 겪기 때문에 실제로 SiriKit 앱의 나쁜 이름입니다. 운 좋게도 SiriKit을 사용하면 다른 이름을 사용하고 발음을 제공할 수 있습니다. 앱의 Info.plist 에서 다음 섹션을 추가하세요.

앱 plist가 대체 앱 이름과 발음을 추가할 수 있음을 보여주는 Xcode의 스크린샷
앱 plist에 대체 앱 이름 및 발음 가이드 추가

이를 통해 사용자는 "list oh mat"라고 말하고 하이픈 없이 단일 단어로 이해할 수 있습니다. 화면에서는 이상해 보이지 않지만 Siri가 없으면 "목록"과 "매트"가 별개의 단어로 생각되어 매우 혼란스러워집니다.

해결: 매개변수 파악

노트북 항목을 검색하기 위한 몇 가지 매개변수가 있습니다.

  1. 항목 유형(작업, 작업 목록 또는 메모),
  2. 항목의 제목,
  3. 항목의 내용,
  4. 완료 상태(작업이 완료로 표시되었는지 여부),
  5. 연결된 위치,
  6. 관련된 날짜입니다.

처음 두 개만 필요하므로 해결 기능을 작성해야 합니다. INSearchForNotebookItemsIntent 에는 구현할 메서드가 있습니다.

우리는 작업 목록 표시에만 관심이 있기 때문에 항목 유형에 대한 해결 방법으로 이를 하드코딩할 것입니다. SearchItemsIntentHandler 에서 다음을 추가합니다.

 func resolveItemType(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INNotebookItemTypeResolutionResult) -> Void) { completion(.success(with: .taskList)) }

따라서 사용자가 무엇을 말하든 우리는 작업 목록을 검색할 것입니다. 검색 지원을 확장하려는 경우 Siri가 원래 구문에서 이를 파악하도록 한 다음 항목 유형이 누락된 경우 completion(.needsValue()) 를 사용하도록 했습니다. 또는 제목과 일치하는 항목을 보고 제목에서 추측할 수 있습니다. 이 경우 Siri가 그것이 무엇인지 알면 성공으로 완료하고 여러 가능성을 시도할 때 completion(.notRequired()) 을 사용할 것입니다.

제목 해상도가 조금 더 까다롭습니다. 우리가 원하는 것은 Siri가 당신이 말한 것과 정확히 일치하는 목록을 찾으면 목록을 사용하는 것입니다. 확실하지 않거나 하나 이상의 가능성이 있는 경우 Siri가 이를 파악하는 데 도움을 요청하기를 바랍니다. 이를 위해 SiriKit은 다음에 발생하려는 작업을 표현할 수 있는 일련의 해상도 열거형을 제공합니다.

따라서 "식료품 매장"이라고 말하면 Siri는 정확히 일치합니다. 그러나 "Store"라고 말하면 Siri는 일치하는 목록 메뉴를 표시합니다.

기본 구조를 제공하기 위해 이 함수로 시작하겠습니다.

 func resolveTitle(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) { guard let title = intent.title else { completion(.needsValue()) return } let possibleLists = getPossibleLists(for: title) completeResolveListName(with: possibleLists, for: title, with: completion) }

ListOMatIntentsHandler 기본 클래스에서 getPossibleLists(for:)completeResolveListName(with:for:with:) 을 구현합니다.

getPossibleLists(for:) 는 Siri가 우리에게 전달한 제목을 실제 목록 이름과 퍼지 일치시키려고 시도해야 합니다.

 public func getPossibleLists(for listName: INSpeakableString) -> [INSpeakableString] { var possibleLists = [INSpeakableString]() for l in loadLists() { if l.name.lowercased() == listName.spokenPhrase.lowercased() { return [INSpeakableString(spokenPhrase: l.name)] } if l.name.lowercased().contains(listName.spokenPhrase.lowercased()) || listName.spokenPhrase.lowercased() == "all" { possibleLists.append(INSpeakableString(spokenPhrase: l.name)) } } return possibleLists }

우리는 모든 목록을 반복합니다. 정확히 일치하면 반환하고, 일치하지 않으면 가능성 배열을 반환합니다. 이 함수에서 우리는 단순히 사용자가 말한 단어가 목록 이름에 포함되어 있는지 여부를 확인합니다(따라서 매우 간단한 일치). 이렇게 하면 "식료품"이 "식료품 가게"와 일치합니다. 더 고급 알고리즘은 소리가 같은 단어를 기반으로 일치를 시도할 수 있습니다(예: Soundex 알고리즘 사용).

completeResolveListName(with:for:with:) 은 이 가능성 목록으로 무엇을 할지 결정하는 역할을 합니다.

 public func completeResolveListName(with possibleLists: [INSpeakableString], for listName: INSpeakableString, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) { switch possibleLists.count { case 0: completion(.unsupported()) case 1: if possibleLists[0].spokenPhrase.lowercased() == listName.spokenPhrase.lowercased() { completion(.success(with: possibleLists[0])) } else { completion(.confirmationRequired(with: possibleLists[0])) } default: completion(.disambiguation(with: possibleLists)) } }

정확히 일치하면 Siri에게 성공했다고 알립니다. 정확하지 않은 일치 항목이 하나 있으면 Siri에게 사용자에게 추측했는지 묻도록 지시합니다.

일치하는 항목이 여러 개인 경우 completion(.disambiguation(with: possibleLists)) 를 사용하여 Siri에게 목록을 표시하고 사용자가 하나를 선택하도록 합니다.

이제 요청이 무엇인지 알았으므로 전체를 살펴보고 처리할 수 있는지 확인해야 합니다.

확인: 모든 종속성 확인

이 경우 모든 매개변수를 해결했다면 항상 요청을 처리할 수 있습니다. 일반적인 confirm() 구현은 외부 서비스의 가용성을 확인하거나 권한 부여 수준을 확인할 수 있습니다.

confirm() 은 선택 사항이므로 아무 것도 할 수 없으며 Siri는 확인된 매개 변수로 모든 요청을 처리할 수 있다고 가정합니다. 명시적으로 다음을 사용할 수 있습니다.

 func confirm(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { completion(INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil)) }

이것은 우리가 무엇이든 처리할 수 있음을 의미합니다.

핸들: 하세요

마지막 단계는 요청을 처리하는 것입니다.

 func handle(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { guard let title = intent.title, let list = loadLists().filter({ $0.name.lowercased() == title.spokenPhrase.lowercased()}).first else { completion(INSearchForNotebookItemsIntentResponse(code: .failure, userActivity: nil)) return } let response = INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil) response.tasks = list.items.map { return INTask(title: INSpeakableString(spokenPhrase: $0.name), status: $0.done ? INTaskStatus.completed : INTaskStatus.notCompleted, taskType: INTaskType.notCompletable, spatialEventTrigger: nil, temporalEventTrigger: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: "\(list.name)\t\($0.name)") } completion(response) }

먼저 제목을 기준으로 목록을 찾습니다. 이 시점에서 resolveTitle 은 이미 정확히 일치하는지 확인했습니다. 그러나 문제가 있는 경우 여전히 실패를 반환할 수 있습니다.

실패가 발생하면 사용자 활동을 전달할 수 있는 옵션이 있습니다. 앱이 Handoff를 사용하고 이 정확한 유형의 요청을 처리하는 방법이 있는 경우 Siri는 앱이 요청을 시도하도록 연기할 수 있습니다. 음성 전용 컨텍스트(예: "Siri야"로 시작)에 있을 때는 이 작업을 수행하지 않으며 다른 경우에도 수행할 것이라고 보장하지 않으므로 기대하지 마십시오.

이제 테스트할 준비가 되었습니다. Xcode의 대상 목록에서 인텐트 확장을 선택합니다. 그러나 실행하기 전에 구성표를 편집하십시오.

스키마 편집 방법을 보여주는 Xcode의 스크린샷
디버깅을 위한 샘플 구문을 추가하도록 의도의 체계를 편집합니다.

그러면 쿼리를 직접 제공하는 방법이 나타납니다.

편집 구성표 대화 상자를 보여주는 Xcode의 스크린샷
구성표의 실행 섹션에 샘플 구문을 추가합니다. (큰 미리보기)

위에서 언급한 하이픈 문제 때문에 "ListOMat"을 사용하고 있습니다. 운 좋게도 내 앱 이름과 발음이 같으므로 큰 문제는 되지 않습니다.

앱으로 돌아가서 "식료품점" 목록과 "철물점" 목록을 만들었습니다. Siri에게 "스토어" 목록을 요청하면 다음과 같은 명확성 경로를 거치게 됩니다.

스토어 목록 표시 요청을 처리하는 Siri를 보여주는 애니메이션 GIF
Siri는 설명을 요청하여 요청을 처리합니다. (큰 미리보기)

"Grocery Store"라고 말하면 정확히 일치하는 결과를 얻을 수 있습니다.

Siri를 통해 항목 추가하기

이제 해결, 확인 및 처리의 기본 개념을 알았으므로 목록에 항목을 추가하려는 의도를 빠르게 추가할 수 있습니다.

먼저 INAddTasksIntent 를 확장의 plist에 추가합니다.

plist에 추가되는 새 의도를 보여주는 XCode의 스크린샷
확장 plist에 INAddTasksIntent 추가(큰 미리보기)

그런 다음 IntentHandlerhandle 함수를 업데이트합니다.

 override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() case is INAddTasksIntent: return AddItemsIntentHandler() default: return nil } }

새 클래스에 대한 스텁을 추가합니다.

 class AddItemsIntentHandler: ListOMatIntentsHandler, INAddTasksIntentHandling { }

항목을 추가하려면 제목 대신 대상 작업 목록을 제외하고 검색을 위해 유사한 resolve 방법이 필요합니다.

 func resolveTargetTaskList(for intent: INAddTasksIntent, with completion: @escaping (INTaskListResolutionResult) -> Void) { guard let title = intent.targetTaskList?.title else { completion(.needsValue()) return } let possibleLists = getPossibleLists(for: title) completeResolveTaskList(with: possibleLists, for: title, with: completion) }

completeResolveTaskListcompleteResolveListName 과 같지만 유형이 약간 다릅니다(작업 목록의 제목 대신 작업 목록).

 public func completeResolveTaskList(with possibleLists: [INSpeakableString], for listName: INSpeakableString, with completion: @escaping (INTaskListResolutionResult) -> Void) { let taskLists = possibleLists.map { return INTaskList(title: $0, tasks: [], groupName: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil) } switch possibleLists.count { case 0: completion(.unsupported()) case 1: if possibleLists[0].spokenPhrase.lowercased() == listName.spokenPhrase.lowercased() { completion(.success(with: taskLists[0])) } else { completion(.confirmationRequired(with: taskLists[0])) } default: completion(.disambiguation(with: taskLists)) } }

동일한 명확성 논리를 가지며 정확히 동일한 방식으로 작동합니다. "Store"라고 말하는 것은 명확해야 하며 "Grocery Store"라고 말하는 것은 정확히 일치합니다.

confirm 은 구현되지 않은 상태로 두고 기본값을 수락합니다. handle 의 경우 목록에 항목을 추가하고 저장해야 합니다.

 func handle(intent: INAddTasksIntent, completion: @escaping (INAddTasksIntentResponse) -> Void) { var lists = loadLists() guard let taskList = intent.targetTaskList, let listIndex = lists.index(where: { $0.name.lowercased() == taskList.title.spokenPhrase.lowercased() }), let itemNames = intent.taskTitles, itemNames.count > 0 else { completion(INAddTasksIntentResponse(code: .failure, userActivity: nil)) return } // Get the list var list = lists[listIndex] // Add the items var addedTasks = [INTask]() for item in itemNames { list.addItem(name: item.spokenPhrase, at: list.items.count) addedTasks.append(INTask(title: item, status: .notCompleted, taskType: .notCompletable, spatialEventTrigger: nil, temporalEventTrigger: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil)) } // Save the new list lists[listIndex] = list save(lists: lists) // Respond with the added items let response = INAddTasksIntentResponse(code: .success, userActivity: nil) response.addedTasks = addedTasks completion(response) }

항목 목록과 대상 목록을 얻습니다. 목록을 찾아 항목을 추가합니다. 또한 Siri가 추가된 항목과 함께 표시하고 완료 기능으로 보낼 응답을 준비해야 합니다.

이 함수는 "ListOMat에서 식료품 목록에 사과 추가"와 같은 구문을 처리할 수 있습니다. "쌀, 양파, 올리브"와 같은 항목 목록도 처리할 수 있습니다.

식료품 가게 목록에 항목을 추가하는 Siri를 보여주는 시뮬레이터의 스크린샷
Siri가 식료품점 목록에 몇 가지 항목을 추가합니다.

거의 완료되었습니다. 몇 가지 설정만 더하면 됩니다.

이 모든 것이 시뮬레이터 또는 로컬 장치에서 작동하지만, 이를 제출하려면 Siri를 사용하는 용도를 설명하는 문자열과 함께 NSSiriUsageDescription 키를 앱의 plist 에 추가해야 합니다. "목록에 대한 요청이 Siri로 전송됩니다"와 같은 것은 괜찮습니다.

다음에 대한 호출도 추가해야 합니다.

 INPreferences.requestSiriAuthorization { (status) in }

이것을 기본 보기 컨트롤러의 viewDidLoad 에 넣어 사용자에게 Siri 액세스를 요청합니다. This will show the message you configured above and also let the user know that they could be using Siri for this app.

A screenshot of the dialog that a device pops up when you ask for Siri permission
The device will ask for permission if you try to use Siri in the app.

Finally, you'll need to tell Siri what to tell the user if the user asks what your app can do, by providing some sample phrases:

  1. Create a plist file in your app (not the extension), named AppIntentVocabulary.plist .
  2. Fill out the intents and phrases that you support.
A screenshot of the AppIntentVocabulary.plist showing sample phrases
Add an AppIntentVocabulary.plist to list the sample phrases that will invoke the intent you handle. (큰 미리보기)

There is no way to really know all of the phrases that Siri will use for an intent, but Apple does provide a few samples for each intent in its documentation. The sample phrases for task-list searching show us that Siri can understand “Show me all my notes on <appName>,” but I found other phrases by trial and error (for example, Siri understands what “lists” are too, not just notes).

요약

As you can see, adding Siri support to an app has a lot of steps, with a lot of configuration. But the code needed to handle the requests was fairly simple.

There are a lot of steps, but each one is small, and you might be familiar with a few of them if you have used extensions before.

Here is what you'll need to prepare for a new extension on Apple's developer website:

  1. Make an app ID for an Intents extension.
  2. Make an app group if you don't already have one.
  3. Use the app group in the app ID for the app and extension.
  4. Add Siri support to the app's ID.
  5. Regenerate the profiles and download them.

And here are the steps in Xcode for creating Siri's Intents extension:

  1. Add an Intents extension using the Xcode template.
  2. Update the entitlements of the app and extension to match the profiles (groups and Siri support).
  3. Add your intents to the extension's plist .

And you'll need to add code to do the following things:

  1. Use the app group sandbox to communicate between the app and extension.
  2. Add classes to support each intent with resolve, confirm and handle functions.
  3. Update the generated IntentHandler to use those classes.
  4. Ask for Siri access somewhere in your app.

Finally, there are some Siri-specific configuration settings:

  1. Add the Siri support security string to your app's plist .
  2. Add sample phrases to an AppIntentVocabulary.plist file in your app.
  3. Run the intent target to test; edit the scheme to provide the phrase.

OK, that is a lot, but if your app fits one of Siri's domains, then users will expect that they can interact with it via voice. And because the competition for voice assistants is so good, we can only expect that WWDC 2018 will bring a bunch more domains and, hopefully, much better Siri.

추가 읽기

  • “SiriKit,” Apple
    The technical documentation contains the full list of domains and intents.
  • “Guides and Sample Code,” Apple
    Includes code for many domains.
  • “Introducing SiriKit” (video, Safari only), WWDC 2016 Apple
  • “What's New in SiriKit” (video, Safari only), WWDC 2017, Apple
    Apple introduces lists and notes
  • “Lists and Notes,” Apple
    The full list of lists and notes intents.