View in English

  • Apple Developer
    • 시작하기

    시작하기 탐색

    • 개요
    • 알아보기
    • Apple Developer Program

    알림 받기

    • 최신 뉴스
    • Hello Developer
    • 플랫폼

    플랫폼 탐색

    • Apple 플랫폼
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    피처링

    • 디자인
    • 배포
    • 게임
    • 액세서리
    • 웹
    • 홈
    • CarPlay
    • 기술

    기술 탐색

    • 개요
    • Xcode
    • Swift
    • SwiftUI

    피처링

    • 손쉬운 사용
    • 앱 인텐트
    • Apple Intelligence
    • 게임
    • 머신 러닝 및 AI
    • 보안
    • Xcode Cloud
    • 커뮤니티

    커뮤니티 탐색

    • 개요
    • Apple과의 만남 이벤트
    • 커뮤니티 주도 이벤트
    • 개발자 포럼
    • 오픈 소스

    피처링

    • WWDC
    • Swift Student Challenge
    • 개발자 이야기
    • App Store 어워드
    • Apple 디자인 어워드
    • 문서

    문서 탐색

    • 문서 라이브러리
    • 기술 개요
    • 샘플 코드
    • 휴먼 인터페이스 가이드라인
    • 비디오

    릴리즈 노트

    • 피처링 업데이트
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • 다운로드

    다운로드 탐색

    • 모든 다운로드
    • 운영 체제
    • 애플리케이션
    • 디자인 리소스

    피처링

    • Xcode
    • TestFlight
    • 서체
    • SF Symbols
    • Icon Composer
    • 지원

    지원 탐색

    • 개요
    • 도움말
    • 개발자 포럼
    • 피드백 지원
    • 문의하기

    피처링

    • 계정 도움말
    • 앱 심사 지침
    • App Store Connect 도움말
    • 새로 추가될 요구 사항
    • 계약 및 지침
    • 시스템 상태
  • 빠른 링크

    • 이벤트
    • 뉴스
    • 포럼
    • 샘플 코드
    • 비디오
 

비디오

메뉴 열기 메뉴 닫기
  • 컬렉션
  • 전체 비디오
  • 소개

더 많은 비디오

  • 소개
  • 요약
  • 자막 전문
  • 코드
  • 앱에 시각 지능을 통합하기 위한 모범 사례

    시각 지능으로 어떻게 앱 내 콘텐츠 탐색을 혁신할 수 있는지 인사이트를 얻어 보세요. 엔티티를 정의하고, 이미지를 처리하며, 다양한 결과 유형을 효과적으로 처리하는 방법을 살펴보세요. 속도와 관련성을 최적화하는 모범 사례를 알아보고, 인텐트가 탭 한 번으로 콘텐츠 열기 또는 재생하기 같은 직접적인 동작을 어떻게 구현하는지 살펴보세요.

    챕터

    • 0:07 - Introduction
    • 2:02 - Defining your content
    • 5:03 - Implementing a query
    • 8:18 - Opening results
    • 10:03 - Mac and iPad adoption
    • 12:27 - Returning multiple result types
    • 12:56 - Continuing search in your app
    • 14:27 - System store integrations
    • 17:16 - Next steps

    리소스

    • Integrating your app with visual intelligence
    • Visual Intelligence
      • HD 비디오
      • SD 비디오
  • 비디오 검색…

    안녕하세요, 저는 David입니다. System Experience 팀의 ML 엔지니어입니다.

    Visual Intelligence로 무언가를 함께 만들어 봅시다.

    이 세션에서는 단계별로 안내해 드리겠습니다. 여러분의 앱을 Visual Intelligence와 통합하는 방법과 함께 몇 가지 모범 사례를 소개해 드리겠습니다. Visual Intelligence가 도입된 이후, 많은 사람들이 이를 활용해 왔습니다. 주변에 있는 것들에 대해 빠르게 더 많은 정보를 얻기 위해 실제 주변 환경이든 iPhone 화면이든 관계없이요.

    올해는 연락처에 추가하는 기능 등 새로운 기능들을 추가합니다. 여러 캘린더 이벤트 저장, 의료 기기 로깅 기능도 추가되며,

    Visual Intelligence를 iPad와 macOS에도 제공합니다.

    그렇다면 여러분의 앱을 이 경험에 어떻게 통합할 수 있을까요? 직접 만들어 보면서 보여드리겠습니다.

    저는 음악 듣기와 새로운 음악 발견하는 것을 좋아합니다. 그래서 앨범을 발견하고 다가오는 콘서트를 찾아주는 앱을 만들고 싶습니다.

    오늘 만들 것을 보여드리겠습니다. 제 음악 앱입니다. 앨범을 탐색하고, 다가오는 콘서트를 확인하고, 탭 하나로 무엇이든 듣기 시작할 수 있습니다. 앨범 아트워크 사진이나 스크린샷을 찍으면, 검색을 위해 하이라이트 하고, Visual Intelligence에서 바로 일치하는 앨범과 콘서트가 표시됩니다. 다가오는 콘서트에 관한 게시물도 캡처할 수 있고,

    Visual Intelligence를 사용해 이벤트를 캘린더에 추가하면

    콘서트가 자동으로 앱에 표시됩니다. 이 세션이 끝나면 이 모든 것을 만드는 방법을 알게 됩니다. 몇 가지 단계가 있습니다. Visual Intelligence와의 통합을 최대한 활용하기 위한 단계들이며, 오늘 살펴보겠습니다. 먼저, 앱에서 반환할 콘텐츠를 정의하겠습니다. App entities를 사용합니다.

    다음으로, Visual Intelligence가 찾을 수 있도록 쿼리를 구현합니다. 앱의 콘텐츠를 반환하기 위해서입니다.

    그런 다음, iOS를 넘어 통합을 확장하여 Mac과 iPad에도 적용하면서, 각 플랫폼별 고려사항을 함께 살펴보겠습니다.

    마지막으로 시스템 스토어 통합을 살펴보겠습니다. Visual Intelligence로 추출된 정보가 앱에서 자동으로 읽힐 수 있는 방법을, 이미 도입했을 수도 있는 공통 데이터 스토어에서 살펴보겠습니다. Image Search의 기본부터 시작해 봅시다. Image Search 통합은 App Intents 프레임워크와 Visual Intelligence 프레임워크를 모두 활용합니다. App Intents가 처음이시라면, WWDC25의 이 세션들을 확인해 보시기를 권장합니다.

    Image Search 통합의 첫 번째 단계는 반환할 콘텐츠를 정의하는 것입니다.

    App Intents 프레임워크의 App entities를 사용할 것입니다. App entities는 앱 내의 명사입니다. 앱에서 Image Search가 먼저 시각적으로 유사한 앨범을 반환하길 원하므로, 앨범 엔티티를 정의하겠습니다.

    코드에서 어떻게 보이는지 살펴봅시다.

    AlbumEntity 정의부터 시작하겠습니다. Visual Intelligence가 검색 결과에 표시할 수 있도록 합니다.

    먼저 기본 EntityQuery를 추가하고 typeDisplayRepresentation을 추가합니다. 이는 App entity의 기본 사항입니다.

    그런 다음 엔티티의 콘텐츠를 정의합니다.

    각 AlbumEntity에는 식별자, 이름, 아티스트 이름, 앨범 아트워크용 썸네일 데이터가 있습니다.

    그리고 displayRepresentation을 추가합니다. 이는 Visual Intelligence에게 각 결과를 표시하는 방법을 알려줍니다.

    방금 정의한 display representation에 대해 이야기해 봅시다.

    Image Search 결과에서 사람들이 가장 먼저 보는 것입니다. 공간이 많지 않습니다. 제목과 부제목에 약 세 줄의 텍스트와 썸네일 이미지가 표시됩니다.

    가장 중요한 식별 정보를 여기에 넣는 것이 좋습니다. 제 경우에는 앨범 이름과 아티스트입니다.

    이미지 URL로 display representation을 초기화할 때, 이미지 URL을 사용하는 경우 적절한 경우 썸네일 크기의 이미지를 제공하는 것을 권장합니다. 전체 해상도 에셋을 가리키는 것보다요.

    예를 들어, 항상 여러 결과를 반환할 것으로 예상한다면, 더 작은 이미지를 사용하면 결과가 더 빨리 로드되고 2열 레이아웃에서도 보기 좋게 표시됩니다.

    반면 하나의 결과만 반환하는 경우, 이 이미지가 결과 시트의 전체 너비를 차지한다는 점을 기억하세요.

    이제 엔티티가 정의되었으니, Visual Intelligence는 어떻게 실제로 앱에서 결과를 쿼리할까요? 바로 Intent value query가 등장하는 부분입니다. Intent value query는 경량 쿼리 프로토콜입니다. 시스템에 엔티티 값을 제공합니다.

    이미 가지고 있을 수도 있습니다. Siri와 함께 작동하도록 App Intents를 도입했다면 말이죠.

    Visual Intelligence의 경우, 핵심적인 차이는 입력에 있습니다. 시스템은 SemanticContentDescriptor를 전달합니다. 캡처된 이미지에 대한 정보를 포함하고 있습니다. 코드로 돌아가서 이를 구현해 봅시다.

    IntentValueQuery 프로토콜을 채택하고 입력으로 SemanticContentDescriptor가 있는 values for requirement를 구현합니다.

    본문에서 입력으로부터 pixelBuffer를 가져와서 catalog.search 메서드에 전달합니다. 일치하는 앨범이 반환됩니다.

    하지만 검색은 실제로 어떻게 작동할까요? 다음에서 살펴봅시다.

    이 앱에서는 저장된 앨범의 로컬 카탈로그를 사용해 기기에서 검색합니다.

    Vision 프레임워크를 사용할 것입니다. Vision은 컴퓨터 비전 작업을 위한 사전 훈련된 머신러닝 모델을 제공합니다.

    카탈로그의 각 항목에는 featurePrint가 있습니다. 이미지의 간결한 수치 표현으로, 이미지 유사성을 비교하는 데 사용할 수 있습니다.

    feature print를 계산하는 함수를 정의하겠습니다. GenerateImageFeaturePrintRequest를 사용합니다.

    카탈로그의 앨범들에 대해 미리 계산해 두겠습니다. 쿼리 시점에 이 계산을 할 필요가 없도록 합니다.

    쿼리에서는 먼저 pixelBuffer를 CGImage로 변환합니다. VideoToolbox를 사용합니다. 그런 다음 이 이미지에 대한 새로운 feature print를 생성합니다.

    카탈로그에 미리 계산된 feature print들과 비교합니다. WEBVTT 유사하지 않은 결과를 필터링하기 위해 최대 거리 임계값을 적용합니다.

    마지막으로 유사도 순으로 정렬하고 상위 결과를 반환합니다.

    몇 가지 주의할 점이 있습니다. 앨범 카탈로그에 대한 feature print를 미리 계산해 두어 쿼리를 빠르게 유지합니다.

    그리고 최적의 일치 항목이 먼저 나타나도록 유사도로 정렬합니다. 기기에서 검색하든 서버를 사용하든, 동일한 원칙이 적용됩니다. 빠르고 순위가 매겨진 결과를 반환하세요.

    또한 반환되는 결과 수를 제한하여 관련성을 보장하는 것을 권장합니다.

    좋은 일치 항목이 없으면 빈 배열을 반환할 수 있습니다. 시스템이 빈 응답 표시를 처리해 드립니다.

    Vision 프레임워크 API도 살펴보시길 권장합니다. 앱에서 사용할 수 있는 이미지 처리 기법에 대해 더 자세히 알 수 있습니다. feature print로는 겨우 표면만 긁은 것이지만, 텍스트 추출 등 훨씬 더 많은 것을 할 수 있습니다. 바코드 스캔, 얼굴 감지, 이미지 분류 등 몇 가지만 예로 들면 그렇습니다. 이것들은 매우 유용한 기법입니다. 앱의 시각적 검색 기능을 확장하는 데 활용할 수 있습니다.

    이제 결과를 탭했을 때 앱의 올바른 화면으로 이동하려면 어떻게 해야 할까요? 이를 위해 OpenIntent가 필요합니다. Image Search 결과에서 앨범을 탭하면, 시스템이 선택된 엔티티로 이 인텐트를 호출합니다.

    perform 메서드는 앨범 상세 페이지로 이동합니다.

    OpenIntent는 사람들을 선택한 콘텐츠로 바로 이동시켜야 합니다.

    이미 엔티티에 대한 OpenIntent가 있다면, App Intents를 채택하여 다른 기능들을 구동하기 위해 여기서도 재사용할 수 있습니다. Visual Intelligence만을 위한 별도의 것이 필요하지 않습니다.

    가볍게 유지하는 것을 권장합니다. 이 메서드는 앱이 포그라운드로 올라올 때 실행됩니다. 따라서 내비게이션을 수행하고 무거운 로딩 작업은 뷰가 나타난 후로 미루세요.

    이것이 기본 Image Search 통합에 필요한 모든 것입니다. 지금까지 만든 것을 살펴보겠습니다.

    친구가 이 추천 음악을 보내줬는데, Visual Intelligence를 사용해 앱에서 들어봅시다. 스크린샷을 찍고, 검색을 위해 하이라이트하고, 사용 가능한 제공자 중에서 앱을 선택합니다.

    쿼리가 작동했고, 앨범을 찾아 최상위 결과로 반환할 수 있었습니다.

    여러분의 앱이 다른 채택 앱들과 함께 여기에 표시된다는 점을 언급할게요.

    시스템은 순서를 결정합니다. 기기에서 사용 가능한 Image Search 제공자에 따라서요. 이 결과를 탭하면 앱의 앨범 페이지로 바로 이동합니다.

    엔티티, 쿼리, OpenIntent가 모두 함께 작동하고 있습니다. 이제 더 많은 플랫폼으로 가져가 봅시다. 올해, Visual Intelligence는 iPadOS와 macOS에서도 사용 가능합니다. 동일한 API들이 이러한 새 플랫폼에서도 사용 가능하며, 앱에 최소한의 변경만 필요합니다.

    IntentValueQuery, 엔티티, OpenIntent 모두 iOS, iPadOS, macOS에서 작동합니다. 방금 작성한 동일한 코드입니다.

    다만, 몇 가지 플랫폼 차이점을 기억해 두는 것이 좋습니다.

    iOS에서 사람들은 주로 카메라를 통해 Visual Intelligence를 사용합니다. 바이닐 레코드나 콘서트 포스터 같은 물리적 오브젝트를 캡처합니다.

    macOS와 iPad에서 주된 진입점은 스크린샷, 즉 디지털 미디어 캡처입니다.

    검색이 두 종류의 콘텐츠를 모두 잘 처리하도록 하세요.

    또한 Mac에서는 입력 픽셀 버퍼가 iPhone에서 접하는 것보다 훨씬 클 수 있다는 점을 기억하세요.

    사용 사례에 맞게 크기 조정이 필요한지 고려하세요.

    macOS용 앱을 빌드하고 어떻게 보이는지 살펴봅시다.

    동일한 이미지의 스크린샷을 찍겠습니다. 쿼리나 엔티티 코드를 변경하지 않고, 앱의 Image Search가 macOS에서 작동합니다.

    결과가 훌륭해 보입니다.

    기본 사항을 다뤘으니, 앱에 더 많은 기능을 추가하고 싶습니다.

    시각적으로 유사한 앨범뿐만 아니라, 해당 앨범의 아티스트들의 다가오는 콘서트도 검색할 수 있다면 어떨까요? 이를 위해 UnionValue를 사용할 수 있습니다.

    앱에는 하나의 IntentValueQuery만 있을 수 있으므로 SemanticContentDescriptor를 수락하는, 각 엔티티 유형(앨범과 콘서트)에 대한 케이스가 있는 @UnionValue 열거형을 정의합니다.

    이제 두 가지 엔티티 유형이 있으므로 각각에 대한 OpenIntent가 필요합니다.

    그런 다음 이 유니온 타입을 반환하도록 쿼리를 업데이트합니다.

    먼저 상위 일치 앨범들을 검색하고, 해당 앨범들의 아티스트를 사용하여 주변 콘서트를 찾은 다음, 단일 결과 목록으로 결합합니다.

    앱에서 여러 유형의 결과를 반환하는 것이 합리적인지 고려해 보세요.

    그리고 단순히 픽셀을 매칭하는 것 이상으로 앱이 반환할 수 있는 다양한 유형의 콘텐츠에 대해 생각해볼 가치가 있습니다. 이미지 유사성을 통해 앨범을 찾고, 해당 아티스트 이름을 사용하여 주변 콘서트를 표면화했습니다. 완전히 다른 종류의 결과입니다.

    반환하는 콘텐츠 유형에 대해 자유롭게 창의적으로 생각해 보세요. 상황에 맞게 말이죠.

    마지막으로, 사람들이 원하는 결과를 바로 찾지 못하는 경우, 앱 내에서 계속 검색할 수 있는 쉬운 방법을 제공하고 싶습니다.

    semanticContentSearch 스키마를 사용하면 됩니다. semanticContentSearch 스키마를 준수하는 인텐트를 만들겠습니다.

    시스템이 semanticContent 속성을 자동으로 제공합니다. 픽셀 버퍼가 있는 앞서 본 SemanticContentDescriptor와 동일합니다.

    perform에서 앱 내 검색 뷰로 이동하겠습니다. 미리 채워진 검색 결과와 함께요.

    이제 누군가 '더 많은 결과'를 탭하면, 앱의 전체 검색 경험으로 이동하게 됩니다. semantic content search를 사용하여 전체 검색 경험으로 계속 이어갈 수 있는 방법을 제공하는 것이 좋은 방법입니다.

    입력 컨텍스트를 기반으로 검색 뷰를 미리 채울 수 있습니다. 처음부터 시작하는 것보다요.

    앱은 Visual Intelligence 결과 뷰보다 훨씬 더 많은 것을 보여줄 수 있습니다. 필터, 카테고리, 콘텐츠의 전체 깊이까지요. 이를 활용하세요. WEBVTT

    지금까지 만든 모든 것을 실제로 살펴봅시다.

    이 앨범의 스크린샷을 다시 찍겠습니다. 그러면 앱이 일치하는 앨범과 콘서트를 Visual Intelligence에서 바로 반환합니다.

    더 탐색하고 싶다면, '더 많은 결과' 버튼을 탭하면 앱의 전체 검색으로 이동합니다.

    앱이 Visual Intelligence에 결과를 제공하는 것에 대해 이야기했습니다. 하지만 이 이야기의 다른 면도 있습니다.

    앱은 Visual Intelligence로부터 데이터를 받을 수도 있습니다. 시스템 스토어 통합을 통해서요. Visual Intelligence에 결과를 제공하는 것은 Image Search 통합을 통해 이루어지며, 이것이 지금까지 구축한 모든 것입니다.

    다른 Visual Intelligence 작업들은 시스템 스토어에 데이터를 씁니다. 개발자들에게 공유 시스템 데이터에 대한 브릿지를 제공합니다.

    이벤트는 EventKit으로, 연락처 정보는 Contacts 프레임워크로 읽을 수 있고, 의료 기기 측정값은 HealthKit으로 읽을 수 있습니다. 앱이 이미 이 프레임워크의 데이터 스토어에서 읽고 있다면, Visual Intelligence가 자동으로 새로운 입력 소스가 됩니다.

    우리 앱의 경우, 사람들이 어떤 콘서트에 관심 있는지 알고 싶습니다. 콘서트 전에 들을 노래를 제안해 줄 수 있도록요. 이 이벤트에 접근하기 위해 EventKit 통합을 추가해 봅시다.

    이것이 제 UpcomingConcertManager입니다. EKEventStore를 사용합니다.

    캘린더에 대한 읽기 접근을 요청하고, 다가오는 이벤트를 쿼리합니다.

    앱에서는 단순히 가까운 미래의 이벤트를 필터링하여 카탈로그의 아티스트와 일치하는 것을 찾습니다.

    알림 옵저버도 추가하겠습니다. Visual Intelligence로 생성된 것을 포함한 새 이벤트가 자동으로 표시되도록 합니다.

    이제 마지막 부분을 살펴봅시다.

    다가오는 콘서트에 관한 이 소셜 미디어 게시물을 캡처하면, Visual Intelligence가 이벤트를 감지하여 캘린더에 추가할 수 있습니다.

    앱을 열면, Upcoming Concerts에 이미 추가되어 있고, 미리 듣기 제안이 함께 표시됩니다.

    동일한 패턴이 다른 시스템 스토어에도 적용됩니다.

    Visual Intelligence를 통해 추가된 연락처는, 예를 들어 명함에서, CNContactStore를 통해 접근할 수 있습니다.

    그리고 Visual Intelligence로 캡처된 의료 기기 측정값은 혈압 모니터 디스플레이에서, 혈당계나 체중계에서 HKHealthStore를 사용하여 조회할 수 있습니다. 건강이나 피트니스 앱이 HealthKit에서 읽는 경우, Visual Intelligence는 사람들이 데이터를 로깅하는 또 다른 방법이 됩니다. 수동 입력 없이요. 오늘 많은 내용을 다뤘습니다. 요약하면, Visual Intelligence는 앱을 위한 두 가지 강력한 통합 지점을 제공합니다.

    Image Search를 통해 Visual Intelligence에 결과를 제공하고, Image Search를 통해, 시스템 스토어 통합을 통해 Visual Intelligence로부터 데이터를 받을 수 있습니다. Visual Intelligence가 이제 iOS, iPadOS, macOS에서 사용 가능해졌으므로, 통합이 모든 기기에서 사람들에게 다가갈 수 있습니다. 더 알고 싶다면, 개발자 웹사이트에서 사용 가능한 문서를 확인하세요.

    더 많은 기능을 탐색하려면 이 관련 세션들도 볼 수 있습니다. App Intents와 Vision 프레임워크에서요.

    시청해 주셔서 감사합니다. Visual Intelligence로 여러분이 만들 것이 기대됩니다.

    • 3:21 - Define the content you want to return as an App Entity

      // Define the content you want to return as an App Entity
        import AppIntents
        
        struct AlbumEntity: AppEntity {
            var id: String
            @Property var name: String
            @Property var artistName: String
            var coverArtData: Data
            
            var displayRepresentation: DisplayRepresentation {
                DisplayRepresentation( 
                    title: "\(name)",
                    subtitle: "\(artistName)",
                    image: .init(data: coverArtData)
                )   
            }   
            
            static let defaultQuery = AlbumEntityQuery()
            
            static var typeDisplayRepresentation: TypeDisplayRepresentation { "Album" }
        }   
        
        struct AlbumEntityQuery: EntityQuery {
            @Dependency var catalog: AlbumCatalog
            func entities(for identifiers: [String]) async throws -> [AlbumEntity] {
                catalog.albums(for: identifiers)
            }
        }
    • 5:39 - Adopt IntentValueQuery to return results

      // Adopt IntentValueQuery to return visual search results
        import AppIntents
        import VisualIntelligence
        
        struct SearchHandler: IntentValueQuery {
            @Dependency var catalog: AlbumCatalog
            @Dependency var concertFinder: ConcertFinder
            
            func values(for input: SemanticContentDescriptor) async throws -> [VisualSearchResult] {
                guard let pixelBuffer = input.pixelBuffer else {
                    return []
                }   
                
                let albums = try await catalog.search(matching: pixelBuffer)
                
                return albums.map { VisualSearchResult.album($0) }
            }
        }
    • 6:24 - Build a catalog of albums with precomputed feature prints

      // Build a catalog of albums with precomputed feature prints
        import Vision
        
        @Observable
        class AlbumCatalog {
            static let shared = AlbumCatalog()
            
            struct CatalogEntry: Sendable {
                let album: AlbumEntity
                let featurePrint: FeaturePrintObservation
            }   
            
            private(set) var entries: [CatalogEntry] = []
            
            private func generateFeaturePrint(
                for image: CGImage
            ) async throws -> FeaturePrintObservation {
                let request = GenerateImageFeaturePrintRequest()
                let result = try await request.perform(on: image)
                return result
            }
        }
    • 6:45 - Search the catalog for albums matching the captured image

      // Search the catalog for albums matching the captured image
        func search(matching pixelBuffer: CVReadOnlyPixelBuffer, limit: Int = 10, maxDistance: Double = 1.0) async throws ->
        [AlbumEntity] {
            var cgImage: CGImage?
            _ = pixelBuffer.withUnsafeBuffer { VTCreateCGImageFromCVPixelBuffer($0, options: nil, imageOut: &cgImage) }
            guard let cgImage else { return [] }
            
            let queryPrint = try await generateFeaturePrint(for: cgImage)
            
            return try entries.compactMap { entry -> (album: AlbumEntity, distance: Double)? in
                let distance = try queryPrint.distance(to: entry.featurePrint)
                guard distance <= maxDistance else { return nil }
                return (entry.album, distance)
            }   
            .sorted { $0.distance < $1.distance }
            .prefix(limit)
            .map { $0.album }
        }
    • 8:27 - Create an open intent to land users on the right screen

      // Create an open intent to land users on the right screen
        import AppIntents
        
        struct OpenAlbumIntent: OpenIntent {
            static let title: LocalizedStringResource = "Open Album"
            
            @Parameter(title: "Album")
            var target: AlbumEntity
            
            @Dependency var appState: AppState
            
            func perform() async throws -> some IntentResult {
                await appState.openAlbum(id: target.id)
                return .result()
            }
        }
    • 12:05 - Use UnionValue to return multiple visual search result types

      // Use UnionValue to return multiple visual search result types
        @UnionValue
        enum VisualSearchResult {
            case album(AlbumEntity)
            case concert(ConcertEntity)
        }   
        
        struct OpenConcertIntent: OpenIntent {
            static let title: LocalizedStringResource = "Open Concert"
            
            @Parameter(title: "Concert")
            var target: ConcertEntity
            
            @Dependency var appState: AppState
            
            func perform() async throws -> some IntentResult {
                await appState.openConcert(id: target.id)
                return .result()
            }
        }
    • 12:18 - Expand the IntentValueQuery to return the UnionValue

      // Expand the IntentValueQuery to return the UnionValue
        struct SearchHandler: IntentValueQuery {
            @Dependency var catalog: AlbumCatalog
            @Dependency var concertFinder: ConcertFinder
            
            func values(for input: SemanticContentDescriptor) async throws -> [VisualSearchResult] {
                guard let pixelBuffer = input.pixelBuffer else {
                    return []
                }   
                
                let albums = try await catalog.search(matching: pixelBuffer)
                
                let artists = albums.map { $0.artistName }
                
                let concerts = await concertFinder.findNearby(byArtists: artists)
      
                return albums.map { VisualSearchResult.album($0) }
                    + concerts.map { VisualSearchResult.concert($0) }
            }
        }
    • 13:13 - Provide a link to in-app search

      // Provide a link to in-app search
        @AppIntent(schema: .visualIntelligence.semanticContentSearch)
        struct SemanticContentSearchIntent: AppIntent {
            static let title: LocalizedStringResource = "Search in app"
            static let openAppWhenRun: Bool = true
            
            var semanticContent: SemanticContentDescriptor
            @Dependency var catalog: AlbumCatalog
            @Dependency var concertFinder: ConcertFinder
            @Dependency var appState: AppState
            
            func perform() async throws -> some IntentResult {
                guard let pixelBuffer = semanticContent.pixelBuffer else { return .result() }
                let albums = try await catalog.search(matching: pixelBuffer)
                let artists = albums.map { $0.artistName }
                let concerts = await concertFinder.findNearby(byArtists: artists)
                await appState.openSearch(albums: albums, concerts: concerts)
                return .result()
            }   
        }
    • 15:24 - Request calendar access and fetch upcoming concerts

      // Request calendar access and fetch upcoming concerts
        import EventKit
        
        @Observable
        class UpcomingConcertManager {
            private let eventStore = EKEventStore()
            var upcomingConcerts: [EKEvent] = []
            var authorizationStatus: EKAuthorizationStatus = .notDetermined
            
            func requestAccessAndFetch() async throws {
                let granted = try await eventStore.requestFullAccessToEvents()
                guard granted else {
                    authorizationStatus = .denied
                    return
                }   
                authorizationStatus = .fullAccess
                await fetchUpcomingConcerts()
      
                // ...
            }
        }
    • 15:42 - Filter for upcoming events that match known artists in our catalog

      // Filter for upcoming events that match known artists in our catalog
        class UpcomingConcertManager {
            func fetchUpcomingConcerts() async {
                let predicate = eventStore.predicateForEvents(
                    withStart: .now,
                    end: .now.addingTimeInterval(90 * 24 * 60 * 60),
                    calendars: nil
                )   
                
                let events = eventStore.events(matching: predicate)
                
                upcomingConcerts = events.filter { event in
                    AlbumCatalog.shared.entries.contains { entry in
                        event.title?.localizedCaseInsensitiveContains(entry.album.artistName) == true
                    }
                }
            }
        }
    • 15:44 - Observe newly created events

      // Observe newly created events
        @Observable
        class UpcomingConcertManager {
            // ...
      
            func requestAccessAndFetch() async throws {
                // ...
      
                for await _ in NotificationCenter.default
                    .notifications(
                        named: .EKEventStoreChanged
                    ) {
                    await fetchUpcomingConcerts()
                }
            }
        }
    • 0:07 - Introduction
    • Visual Intelligence integration and what's new in iOS 26, iPadOS, and macOS, using a sample music-discovery app built throughout the session. Outlines the agenda: defining content, implementing a query, cross-platform adoption, and system store integrations.

    • 2:02 - Defining your content
    • Model your app's content as an AppEntity so Visual Intelligence can display it in search results. Covers the entity's DisplayRepresentation (title, subtitle, thumbnail) and best practices around concise identifying text and thumbnail-sized images.

    • 5:03 - Implementing a query
    • IntentValueQuery returns results from a SemanticContentDescriptor's pixel buffer — using the Vision framework's GenerateImageFeaturePrintRequest for on-device image similarity, with pre-computed feature prints and distance thresholds to keep results fast.

    • 8:18 - Opening results
    • Implement an OpenIntent to take people straight to the selected content. Keep it lightweight since it runs as the app foregrounds, and reuse an existing OpenIntent rather than creating one specific to Visual Intelligence.

    • 10:03 - Mac and iPad adoption
    • The same entities, query, and OpenIntent carry over to iPadOS and macOS with minimal changes. Account for platform differences such as camera versus screenshot input and the much larger pixel buffers on Mac that may need resizing.

    • 12:27 - Returning multiple result types
    • The @UnionValue type returns more than one entity type from a single query — here albums plus nearby concerts — encouraging you to derive related content rather than only matching pixels.

    • 12:56 - Continuing search in your app
    • The semanticContentSearch schema lets people continue into your full in-app search — pre-populating results from the captured context so they land on filters, categories, and deeper content.

    • 14:27 - System store integrations
    • Visual Intelligence can also write data your app reads back via system stores: events through EventKit (EKEventStore), contacts via CNContactStore, and medical-device readings via HealthKit (HKHealthStore). Observe store-change notifications so captured data appears automatically.

    • 17:16 - Next steps
    • Recaps the two integration points, Image Search and system stores, across iOS, iPadOS, and macOS. Points to documentation and related App Intents and Vision sessions.

Developer Footer

  • 비디오
  • WWDC26
  • 앱에 시각 지능을 통합하기 위한 모범 사례
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • Apple Intelligence
    • 앱 확장 프로그램
    • App Store
    • 오디오 및 비디오(영문)
    • 증강 현실
    • 디자인
    • 배포
    • 교육
    • 서체(영문)
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 머신 러닝 및 AI
    • 오픈 소스(영문)
    • 보안
    • Safari 및 웹(영문)
    메뉴 열기 메뉴 닫기
    • 문서(영문)
    • 튜토리얼
    • 다운로드
    • 포럼(영문)
    • 비디오
    메뉴 열기 메뉴 닫기
    • 지원 문서
    • 문의하기
    • 버그 보고
    • 시스템 상태(영문)
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, 식별자 및 프로파일(영문)
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(영문)
    • Mini Apps Partner Program
    • News Partner Program(영문)
    • Video Partner Program(영문)
    • Security Bounty Program(영문)
    • Security Research Device Program(영문)
    메뉴 열기 메뉴 닫기
    • Apple과의 만남
    • Apple Developer Center
    • App Store 어워드(영문)
    • Apple 디자인 어워드
    • Apple Developer Academy(영문)
    • WWDC
    최신 뉴스 읽기.
    Apple Developer 앱 받기.
    Copyright © 2026 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침