View in English

  • 메뉴 열기 메뉴 닫기
  • Apple Developer
검색
검색 닫기
  • Apple Developer
  • 뉴스
  • 둘러보기
  • 디자인
  • 개발
  • 배포
  • 지원
  • 계정
페이지에서만 검색

빠른 링크

5 빠른 링크

비디오

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

더 많은 비디오

  • 소개
  • 요약
  • 자막 전문
  • 코드
  • 앱 인텐트의 새로운 업데이트 살펴보기

    올해 릴리스에서 앱 인텐트 프레임워크의 새로운 개선 사항을 모두 살펴보세요. 지연된 속성과 같은 개발자의 편리성을 높이는 기능, 대화형 앱 인텐트 스니펫과 같은 새로운 기능, 엔티티 뷰 주석, Visual Intelligence 통합 방법 등을 확인할 수 있습니다. 그 어느 때보다 표현력이 풍부해지고 쉽고 부드럽게 적용되는 앱 인텐트를 소개합니다. 또한 Spotlight과 Visual Intelligence 등 올해 앱 인텐트의 흥미로운 새 클라이언트를 소개하고 이러한 맥락에서 효과적으로 작동하는 앱 인텐트를 작성 방법을 알아보겠습니다.

    챕터

    • 0:00 - 서론
    • 0:55 - 대화형 스니펫
    • 8:15 - 새 시스템 통합
    • 15:01 - 사용자 경험 개선 사항
    • 21:02 - 편의 API

    리소스

    • Accelerating app interactions with App Intents
    • Adopting App Intents to support system experiences
    • App intent domains
    • App Intents
    • App Shortcuts
    • Building a workout app for iPhone and iPad
    • Creating your first app intent
    • Integrating actions with Siri and Apple Intelligence
    • Making actions and content discoverable and widely available
    • PurchaseIntent
      • HD 비디오
      • SD 비디오

    관련 비디오

    WWDC25

    • 대화형 스니펫 설계하기
    • 앱 인텐트 알아보기
    • 앱 인텐트로 단축어 및 Spotlight 개발하기

    WWDC24

    • 시스템 경험을 위한 앱 인텐트 디자인하기
    • 앱 인텐트로 사용자에게 앱의 핵심 기능 제공하기
    • 앱 인텐트의 새로운 내용
    • 앱을 Siri로 가져오기
  • 비디오 검색…

    안녕하세요, App Intents 팀 엔지니어 Jeff입니다 오늘은 앱 인텐트의 새로운 기술에 대해 알아보겠습니다

    앱 인텐트는 앱 기능을 기기 전반에서 통합할 수 있도록 도와줍니다 이를테면 단축어, Spotlight 시각 지능과 같은 곳에서 말이죠

    이 프레임워크가 익숙하지 않다면 먼저 ‘앱 인텐트 알아보기’ 세션을 확인하세요 언제든 다시 찾아와 주셔도 좋습니다 오늘은 대화형 스니펫을 사용해 빌드할 수 있는 새로운 경험과 앱이 시스템 전체에서 앱 인텐트를 활용할 수 있는 추가적인 방법 앱의 사용자 경험 개선을 위해 사용할 수 있는 더 많은 기능 개발자 경험 향상을 위한 다양한 편의 API에 대해 이야기하겠습니다 먼저 대화형 스니펫부터 시작해 보죠

    스니펫은 앱 인텐트를 통해 맞춤형 뷰를 표시할 수 있습니다 확인을 요청하거나 결과를 표시할 수 있죠 이제 상호작용하도록 스니펫을 구현할 수 있습니다 예를 들어 정원 가꾸기 스니펫을 만들 수 있는데 토양이 너무 건조할 때 스프링클러 작동을 제안할 수 있죠 또는 음식 주문 전에 구성을 맞춤화할 수도 있습니다 실시간 현황과 같은 다른 시스템 기능과 통합해서 점수 확인 후 스포츠 경기를 바로 팔로우할 수도 있습니다 샘플 앱으로 대화형 스니펫의 데모를 간단히 보여 드릴게요 그런 다음 구현 방법을 살펴보겠습니다

    TravelTracking 앱에는 전 세계 수많은 랜드마크가 담겨 있습니다 가장 가까운 랜드마크를 찾는 앱 인텐트 실행 컨트롤을 탭할게요

    랜드마크를 찾으면 랜드마크가 표시된 스니펫이 나타나고 제목 옆에 하트 버튼이 표시됩니다 저는 토론토 출신이라 나이아가라 폭포는 아주 익숙해요 하트 버튼을 눌러서 즐겨찾기에 추가하겠습니다

    스니펫은 즉시 상태를 업데이트하여 표시합니다 이 기능은 새로운 스니펫 인텐트 프로토콜로 구현됩니다 이러한 인텐트는 매개변수와 앱 상태에 따라 뷰를 렌더링합니다 작업 결과를 표시하거나 확인을 요청하는 데 사용할 수 있습니다 먼저 결과 스니펫 작동 방법을 살펴보겠습니다 앱의 모든 인텐트는 결과로 스니펫 인텐트를 반환할 수 있습니다 매개변수 값을 포함해서 말이죠 시스템에서 스니펫을 새로 고칠 때마다 이 결과를 사용합니다 그 과정에서 시스템은 스니펫 인텐트의 매개변수를 지정된 값으로 채웁니다 앱 엔티티가 포함되어 있다면 쿼리를 통해 가져옵니다

    그 후 시스템은 스니펫 인텐트의 perform 메서드를 실행합니다 여기에서 인텐트는 매개변수와 앱 상태를 사용해 뷰를 렌더링하고 결과의 일부로 반환할 수 있습니다 뷰는 버튼이나 토글을 앱의 앱 인텐트와 연동할 수 있습니다

    예에서 하트 버튼은 즐겨찾기 업데이트 인텐트를 실행하고 티켓 찾기 버튼은 티켓 찾기 인텐트를 실행합니다 이들 인텐트를 스니펫 전용으로 새로 만들 필요는 없습니다 기존 인텐트를 그대로 재사용할 수 있습니다

    하트 버튼을 탭하면 시스템에서 해당 인텐트가 실행되고 완료될 때까지 대기합니다 이렇게 하면 즐겨찾기 상태가 최신 상태가 됩니다 완료되면 시스템은 매개변수화된 스니펫 인텐트를 사용합니다 다른 업데이트를 트리거하기 위해 앞서 반환했던 것이죠 이전과 동일하게 매개변수를 지정된 값으로 채웁니다 앱 엔티티도 해당 쿼리에서 다시 가져옵니다 그런 다음 시스템은 perform 메서드를 실행해 스니펫 인텐트가 업데이트된 뷰를 렌더링할 수 있게 합니다 이 경우에는 하트 아이콘이 채워졌습니다 뷰의 모든 변경 사항은 애니메이션으로 표시되는데 SwiftUI의 contentTransition API를 따릅니다 스니펫이 닫힐 때까지 이 순환은 계속됩니다

    스니펫 인텐트를 반환하는 기능을 구현해 보겠습니다 먼저, 기존의 인텐트를 가져와 반환 유형에 ShowsSnippetIntent를 추가합니다 그런 다음, result 메서드에 새 매개변수를 사용하여 스니펫 인텐트를 제공합니다 이것은 시스템에 스니펫 인텐트가 사용될 때마다 주어진 랜드마크로 매개변수를 채우라고 지시합니다

    이제 스니펫 인텐트 자체를 구현해 보겠습니다 우선, SnippetIntent 프로토콜을 준수해야 합니다 이제 뷰를 올바르게 렌더링하는 데 필요한 변수를 추가합니다 매개변수로 표시되어야 합니다 그렇지 않으면 시스템이 변수를 채워 넣을 수 없습니다 그리고 앱 인텐트이므로 AppDependency 액세스 권한도 있습니다 perform 메서드 반환 유형에서 ShowsSnippetView를 추가합니다 body에서 메서드를 사용하여 뷰 생성에 필요한 상태를 가져오고 result 메서드에서 view 매개변수를 사용하여 반환합니다

    SwiftUI 뷰와 유사하게 스니펫 인텐트가 수명 주기 동안 여러 번 생성 및 실행됩니다 따라서 스니펫 인텐트가 앱 상태를 변경하지 않도록 주의해야 합니다 시스템은 다크 모드 전환과 같은 기기 변경 사항에도 대응하기 위해 스니펫 인텐트를 여러 번 실행할 수도 있습니다 뷰를 빠르게 렌더링해야 하며 반응이 없다고 느껴지지 않도록 해야 합니다

    그리고 매개변수 값은 한 번만 지정하기 때문에 매번 쿼리되는 앱 엔티티와 절대 변하지 않는 원시 값에만 사용해야 합니다 다른 모든 값은 perform 메서드에서 가져와야 합니다

    스니펫이 빌드되었으니 이제 SwiftUI 뷰에서 인텐트를 트리거하는 방법을 살펴보겠습니다

    View body에서 LandmarkView는 Button 이니셜라이저를 사용하여 해당 앱 인텐트를 연결합니다 토글에도 비슷한 API가 있습니다

    이러한 API는 대화형 위젯 및 contentTransition 한정자와 함께 도입되었으며, 애니메이션을 맞춤화할 수 있도록 해줍니다

    자세히 알아보려면 2023년 Luca의 세션을 확인하세요 이제 확인 스니펫을 사용해 티켓 찾기 기능을 구현해 보겠습니다

    티켓 찾기 버튼을 탭하면 검색할 티켓 수를 묻는 메시지가 표시됩니다 친구들과 함께 갈 테니까 티켓 수를 네 장으로 바꾼 다음 검색을 시작하겠습니다 가장 저렴한 가격이 결과 스니펫을 통해 표시됩니다 단계별로 살펴보겠습니다 결과 스니펫부터 보면 버튼이나 토글이 자체 스니펫을 표현하는 인텐트를 트리거하는 경우 기존 스니펫이 대체됩니다 원래 스니펫이 결과를 표시할 때만 이렇게 작동한다는 데 유의하세요 후속 인텐트는 두 가지 유형의 스니펫 모두를 표시할 수 있지만 여기서는 확인 요청을 사용해 구성 뷰를 표시하겠습니다 requestConfirmation 메서드를 호출하기만 하면 됩니다 작업 이름을 맞춤화할 수 있고 확인 경험을 가능하게 하는 스니펫 인텐트를 제공합니다

    이 메서드는 스니펫이 취소되면 오류를 발생시킵니다 오류를 잡으려고 하지 말고 perform 메서드가 종료되게 두세요

    그러면 뷰와의 모든 상호작용이 결과 스니펫과 동일한 업데이트 주기를 따를 겁니다

    예에서 스니펫 인텐트는 항상 업데이트된 티켓 수를 보여 줍니다 어떻게 그렇게 표시할까요? 검색 요청을 AppEntity로 모델링했기 때문에 해당 매개변수를 채울 때 시스템은 항상 최신 값을 쿼리에서 가져옵니다 그래서 뷰에 그대로 전달하면 자동으로 최신 값이 표시되죠

    스니펫이 표시되는 한 시스템이 앱을 종료하지 않으므로 상태를 메모리에 보관해도 됩니다 데이터베이스에 저장할 필요가 없습니다

    모든 상호작용이 끝나고 검색 실행 버튼을 누르면 원래 앱 인텐트 실행이 재개됩니다

    때로는 작업 중간에 스니펫을 업데이트해야 할 수 있습니다 새로운 상태가 생겼을 경우에요

    그럴 때는 스니펫 인텐트에서 정적 reload 메서드를 호출하면 스니펫이 업데이트됩니다

    이 세션에서 최상의 대화형 스니펫 디자인에 대한 내용을 확인하세요 스니펫은 시작에 불과합니다 이제 앱 인텐트가 시스템 전체에 앱을 통합하도록 도와주는 더 많은 방법을 살펴보겠습니다 첫 번째는 이미지 검색입니다 이 기능을 사용하면 사람들이 카메라로 촬영한 사진이나 스크린샷에서 검색할 수 있습니다 iOS 26의 새로운 기능으로 앱의 검색 결과도 표시 가능합니다 여기 랜드마크의 스크린샷이 있습니다 스크린샷을 강조 표시해 이미지 검색을 수행하겠습니다 그러면 시스템에 검색 패널이 표시됩니다 TravelTracking 앱을 선택해서 결과를 볼 수 있습니다 그리고 결과를 탭하면 앱에서 해당하는 랜드마크의 페이지가 열립니다

    이미지 검색을 지원하려면 IntentValueQuery 프로토콜을 준수하는 쿼리를 구현하세요 이 프로토콜은 입력으로 SemanticContentDescriptor 유형을 받고 AppEntity 배열을 반환합니다 이미지 검색에서 디스플레이 표현을 사용하여 AppEntity가 표시됩니다

    결과를 탭하면 해당 AppEntity가 OpenIntent로 전달되어 앱에서 이를 처리하게 됩니다 OpenIntent는 꼭 있어야 하고 없으면 앱이 표시되지 않습니다

    쿼리를 구현하려면 IntentValueQuery 프로토콜을 따르는 struct로 시작하세요 values 메서드는 입력값으로 SemanticContentDescriptor를 받아야 하고 여기에는 선택한 영역의 픽셀이 담겨 있습니다 Video Toolbox 또는 CoreImage의 API를 사용하여 CGImage와 같은 익숙한 유형으로 변환할 수 있습니다

    검색을 수행한 후 일치하는 엔티티의 배열을 반환합니다 다음으로, 결과를 탭할 수 있도록 OpenIntent 프로토콜을 준수하는 인텐트를 구현합니다 target 매개변수는 결과와 동일한 엔티티 유형이어야 합니다 OpenIntent는 이미지 검색뿐만 아니라 Spotlight와 같이 다른 곳에서도 호출될 수 있어 사용자가 앱 내 엔티티로 쉽게 이동할 수 있게 해줍니다

    원하는 결과를 빠르게 찾을 수 있을 때 사용자 경험이 극대화됩니다 결과를 여러 페이지로 반환해 시스템 UI에 정확한 결과가 표시될 가능성을 높이세요 서버를 사용해 더 큰 데이터세트를 검색할 수 있지만 검색 시간이 너무 길면 응답이 느려질 수 있으니 주의하세요 사용자가 원하는 결과를 찾지 못했을 때 앱에서 검색을 계속 할 수 있도록 지원하는 것도 좋습니다 TravelTracking에 이를 추가해 보죠

    원하는 결과가 목록에 보이지 않을 때 결과 더 보기 버튼을 탭해 앱을 열고 검색 뷰로 이동할 수 있습니다 이 기능을 구현하려면 새 AppIntent 매크로를 사용해 semanticContentSearch 스키마를 지정합니다 이는 AssistantIntent 매크로를 대체하는 새로운 API입니다 왜냐하면 우리는 어시스턴트 외의 기능, 예를 들어 VisualIntelligence 같은 기능에도 스키마를 확장했기 때문이죠 스키마에 필요한 semanticContent 속성을 추가합니다 이 속성은 매크로에 의해 자동으로 인텐트 매개변수로 표시됩니다

    perform 메서드에서는 검색 메타데이터를 처리한 후 검색 뷰로 이동합니다 추가 기능으로 TravelTracking에서 컬렉션을 표시하고 싶습니다 일치하는 랜드마크가 모여 있는 컬렉션이죠 그런데 하나의 쿼리에서 LandmarkEntity와 CollectionEntity를 함께 반환하려면 어떻게 할까요? 그 해답은 UnionValue입니다 먼저 쿼리에서 반환할 수 있는 각 엔티티 유형을 나타내는 케이스를 포함한 UnionValue를 선언합니다 그런 다음, 결과 유형을 배열로 변경합니다 이제 두 가지 엔티티를 혼합해 반환할 수 있습니다 각각의 엔티티 유형에 대해 OpenIntent를 구현하는 것도 잊지 마세요 UnionValue에 대한 자세한 내용은 Kenny의 2024년 앱 인텐트 세션을 확인하세요

    앱 외부에서 앱 콘텐츠를 검색할 수 있다는 점도 좋지만 앱이 실행 중일 때 Apple Intelligence와 함께 할 수 있는 일이 더 많습니다 이제 화면의 엔티티에 대해 알아보겠습니다 NSUserActivity를 사용하여 앱 화면 속 콘텐츠에 엔티티를 연결할 수 있습니다 이를 통해 사용자는 ChatGPT에 현재 앱에 표시된 내용에 대해 물어볼 수 있습니다

    TravelTracking 앱에서 나이아가라 폭포를 보면서 Siri에게 이 장소가 바다 근처인지 물어볼 수 있습니다 Siri는 화면에 표시된 콘텐츠를 인식하고 ChatGPT에 스크린샷을 보낼지 제안합니다 하지만 LandmarkEntity는 PDF를 지원하므로 전체 콘텐츠 옵션을 선택하겠습니다 미리보기로 내용을 간단히 확인한 후 전송할 수 있습니다

    응답이 표시되며, 오대호는 바다만큼 크지만 바다는 아니라는 것을 알게 됩니다 엔티티를 뷰에 연결하려면 먼저 userActivity 한정자를 LandmarkDetailView에 추가합니다 그런 다음 업데이트 클로저에서 엔티티 식별자를 해당 활동과 연결합니다 다음으로 LandmarkEntity를 ChatGPT가 이해할 수 PDF 등의 데이터 형식으로 변환할 수 있게 지원해야 합니다

    먼저 Transferable 프로토콜을 준수하도록 한 다음 PDF 데이터 표현을 제공합니다 이 외에도 일반 텍스트와 서식 있는 텍스트도 지원됩니다 이 앱 인텐트 문서에서 화면의 엔티티에 대해 자세히 알아보세요

    앱 콘텐츠를 시스템에 노출하는 것과 관련하여 Spotlight에 대해 간단히 이야기해 보겠습니다 이제 Mac의 Spotlight에서 직접 작업을 실행할 수 있으므로 앱 인텐트를 사용해 최고의 경험을 선사할 수 있는 몇 가지 방법이 있습니다

    먼저, 앱 엔티티가 IndexedEntity를 준수하도록 하고 Spotlight에 등록합니다 그러면 Spotlight 검색으로 매개변수의 필터링 경험이 가능합니다 엔티티 속성을 Spotlight 키에 연결하기 위해 이제 새 indexingKey 매개변수를 사용할 수 있습니다 Property 속성에서요 이 예에서는 continent에 customIndexingKey를 부여했는데 사람들이 텍스트 필드에 아시아를 입력하기만 하면 아시아의 랜드마크를 검색할 수 있습니다 이 API를 채택하면 단축어 앱이 해당 엔티티에 대해 자동으로 찾기 작업을 생성할 수 있습니다

    두 번째로는, 화면 속 콘텐츠를 엔티티로 주석 처리하면 해당 뷰가 표시될 때 우선순위가 높아집니다 셋째, PredictableIntent를 구현해 시스템이 학습하여 사용자 인텐트에 대한 추천을 제공할 수 있습니다 이전 사용자 행동의 인텐트와 매개변수를 바탕으로 말이죠 값에 따라 맞춤화된 설명을 제공할 수도 있습니다

    이 기능에 대해 자세히 알고 싶다면 단축어와 Spotlight에 관한 이 세션을 확인하세요 이제 앱에 이런 기능들이 모두 통합되었으니 사용자 경험을 개선하는 새로운 방법을 소개해 드릴게요 실행 취소부터 시작하겠습니다 사람들은 나중에 취소할 수 있다는 걸 알면 다양한 시도를 하게 됩니다 그래서 앱에서 수행한 작업을 되돌릴 수 있는 기능이 중요합니다 새로운 UndoableIntent 프로토콜을 사용하면 익숙한 제스처로 앱 인텐트를 손쉽게 취소할 수 있습니다

    여기 TravelTracking 앱의 컬렉션 일부가 있습니다 타이핑으로 Siri 사용을 이용해서 맞춤형 단축어 중 하나를 실행해 Sweet Deserts 컬렉션을 삭제하겠습니다 삭제를 확인하면 해당 컬렉션이 앱에서 제거됩니다 만약 마음이 바뀌었다면 세 손가락으로 왼쪽으로 스와이프해서 실행 취소하고 컬렉션을 복원할 수 있습니다

    DeleteCollectionIntent는 실행 취소 스택에 참여하기 위해 먼저 UndoableIntent 프로토콜을 준수하는데 이는 선택적인 undoManager 속성을 제공합니다 이 속성을 실행 취소 작업에 등록할 수 있습니다 setActionName과 같은 다른 작업도 사용할 수 있습니다 시스템은 이 속성을 통해 인텐트에 가장 관련이 있는 UndoManager를 제공합니다 해당 인텐트가 확장 프로그램에서 실행될 때도 마찬가지입니다 이렇게 하면 UI와 앱 인텐트 간의 실행 취소 작업이 동기화되어 작업을 올바른 순서대로 실행 취소할 수 있습니다 삭제에 대한 대안을 제공해서 이 인텐트를 더 개선하겠습니다

    새로운 다중 선택 API를 사용하여 여러 옵션을 제시할 수 있습니다 사람들이 선택할 수 있게 말이죠 컬렉션을 삭제하는 인텐트를 다시 실행해 보겠습니다 이번에는 삭제 확인을 묻는 것과 더불어 보관이라는 추가 옵션을 제공합니다

    이 컬렉션은 추억을 떠올리게 하네요 보관해야겠어요 perform 메서드에서 다중 선택 스니펫을 표시합니다 requestChoice 메서드를 호출하고 between 매개변수로 옵션 배열을 전달해서 말이죠 옵션은 맞춤형 제목으로 생성할 수 있고 스타일을 지정해 시스템에 렌더링 방법을 알려줄 수도 있습니다

    대화상자와 맞춤화된 SwiftUI 뷰로 스니펫을 사용자화할 수 있습니다

    옵션이 선택되면 requestChoice 메서드에서 반환되는데 perform 메서드를 종료하는 오류를 발생시키는 취소는 예외입니다 이 오류는 처리하지 말아야 합니다 요청이 취소되면 인텐트는 즉시 중단되어야 합니다

    그 이후에는 switch 문을 사용해 선택한 옵션에 따라 분기합니다 예상 값으로 생성한 원래 옵션을 사용할 수 있습니다 개선할 수 있는 또 다른 부분에 대해 이야기해 보겠습니다 지원 모드는 앱 포그라운드 전환 시 인텐트에 더 많은 제어권을 줍니다. 이는 사용자가 기기와 상호작용하는 방식에 따라 인텐트가 다르게 동작하게 합니다 예를 들어, 운전 중이라면 인텐트가 음성으로만 정보를 제공했으면 합니다 그러나 기기를 보고 있는 경우라면 앱으로 바로 이동시켜야 합니다 앱에서 더 많은 정보를 확인할 수 있으니까요 이 기능을 사용하면 두 가지를 모두 수행하는 앱 인텐트를 하나만으로 구현할 수 있습니다 보여드릴게요

    이 인텐트는 나이아가라 폭포의 혼잡도를 가져옵니다

    실행 시 열기 옵션을 해제하겠습니다 이건 앱을 포그라운드로 띄울 수 없는 상황 즉, 헤드폰을 낀 채 Siri를 사용할 때와 유사합니다 앱 실행 시 대화 상자만 표시되고 Siri가 내용을 읽어줄 수 있습니다

    하지만 대화상자를 끄고 실행 시 열기를 활성화한 상태로 실행하면 혼잡도와 현재 날씨를 함께 보여 주는 랜드마크 페이지로 바로 이동하게 됩니다

    이 기능을 인텐트에 추가해 보겠습니다

    먼저, static 변수를 사용하여 지원되는 모드를 추가하겠습니다 백그라운드 모드로만 설정하면 이 인텐트는 앱을 포그라운드로 띄우지 않겠다고 시스템에 알립니다 하지만 가능하다면 앱으로 이동시키고 싶기 때문에 포그라운드 모드도 추가해 앱 실행 후 인텐트가 실행되도록 요청하겠습니다

    새 currentMode 속성을 사용하여 현재 포그라운드 상태인지 확인할 수 있습니다 그렇다면 앱으로 이동할 수 있습니다 음, 잠시만요 근데 왜 랜드마크에 아무도 없다고 나오는 거죠? 아, 영업시간이 아니군요 이럴 때는 앱이 열리지 않게 인텐트를 수정해 보겠습니다 perform 메서드에서 랜드마크가 운영 중인지 확인하고 운영 시간이 아니라면 바로 종료할 수 있습니다 이 경우에는 시스템이 앱을 띄우지 않도록 하고 싶습니다 인텐트가 실행 되기 전에 말이죠 그래서 포그라운드 모드를 dynamic으로 수정하겠습니다 지원 모드에는 백그라운드 모드와 세 가지 포그라운드 모드가 있죠 Immediate는 인텐트 실행 전에 앱이 포그라운드에서 실행되도록 시스템에 알립니다 Dynamic은 앱 실행 여부를 인텐트가 결정할 수 있게 합니다 Deferred는 인텐트가 앱을 나중에 포그라운드에 띄우고 즉시 실행하지는 않습니다 GetCrowdStatus는 앱을 꼭 띄우지 않아도 되므로 dynamic이 좋습니다

    이 두 모드에서는 인텐트가 continueInForeground 메서드를 사용해 앱을 언제 띄울지 직접 제어할 수 있습니다

    랜드마크가 현재 운영 중임을 확인하고 systemContext에 대해 새 속성을 사용해 앱을 포그라운드로 띄울 수 있는지 확인합니다 가능하다면 continueInForeground 메서드를 호출하여 앱을 띄웁니다 alwaysConfirm을 false로 설정하면 최근 몇 초간 활동이 있었을 경우 시스템에서 메시지를 표시하지 않습니다 앱 실행이 성공하면 이제 혼잡도 뷰로 이동할 수 있습니다 하지만 시스템이나 사용자가 실행 요청을 거부하면 이 메서드는 오류를 발생시킵니다 이 오류를 포착해서 적절히 처리할 수 있습니다 마무리하기 전에 앱 인텐트 개발을 더 쉽게 만들어 주는 방법 몇 가지를 소개하겠습니다

    앱 인텐트가 UI 탐색을 수행하려면 뷰를 구동하는 앱 상태에 접근할 수 있어야 합니다 이것은 전역적으로 공유되는 객체를 통해서만 가능합니다 AppDependency나 싱글톤처럼요 하지만 새로운 뷰 컨트롤 API를 사용하면 앱 인텐트에서 UI 코드를 제거하고 뷰가 직접 처리하게 할 수 있습니다 이것을 오픈 랜드마크 인텐트 리팩터링에 활용해 봅시다 먼저 인텐트가 TargetContentProvidingIntent 프로토콜을 따르도록 해야 합니다 SwiftUI 뷰에서 path라는 속성을 사용해 NavigationStack을 프로그래밍 방식으로 수정할 수 있는데 이 속성은 State로 선언되어 있으므로 View body에서만 액세스할 수 있습니다 여기서 onAppIntentExecution 뷰 한정자를 사용할 수 있습니다 이 한정자는 처리하고자 하는 앱 인텐트의 유형과 그 인텐트가 전달되는 작업 클로저를 받습니다 클로저 내부에서 인텐트의 매개변수를 참조하고 UI를 적절히 수정할 수 있습니다 이 코드가 있으면 인텐트 내에서 UI 코드를 제거할 수 있고 perfom 메서드나 더 이상 필요하지 않은 종속성도 제거할 수 있습니다 정말 유용하죠?

    시스템은 작업 클로저를 잠깐 실행한 후에 앱을 포그라운드에 띄웁니다 따라서 인텐트에서 매개변수 값을 읽는 것만 가능하고 값 요청과 같은 다른 모든 작업은 지원되지 않습니다 여러 뷰에 동일한 한정자가 있을 경우 모든 뷰에서 실행되므로 각 뷰가 적절히 응답할 수 있습니다 앱이 여러 개의 장면이나 윈도우를 지원하는 경우 어떤 것이 특정 인텐트를 실행할지 제어할 수 있어야 합니다 예를 들어, TravelTracking 앱에서 컬렉션을 편집하고 있을 때 해당 장면이 있는 랜드마크로 이동하지 않아야 합니다 방해가 될 테니까요 이럴 땐 handlesExternalEvents API를 사용해 제어할 수 있습니다 TargetContentProvidingIntent에는 contentidentifier 속성이 있는데 이 속성은 기본적으로 persistentidentifier로 설정되죠 이는 보통 인텐트의 구조체 이름이 됩니다 이 값은 언제든지 더 구체적으로 맞춤화할 수 있습니다

    이 값을 장면에서 HandlesExternalEvents 한정자로 적용하면 시스템은 활성화 조건에 따라 어떤 장면이 앱 인텐트를 실행할지 결정합니다 아직 없으면 새로 생성됩니다 배열에 지정한 식별자가 처리하려는 인텐트의 contentidentifier 속성과 일치해야 합니다 그러나 활성화 조건이 dynamic이면 장면 대신 뷰에 동일한 한정자를 사용할 수 있습니다 여기서는 OpenLandmarkIntent를 장면이 처리하도록 제한했습니다 컬렉션을 편집 중이지 않을 때 말이죠 SwiftUI 장면과 활성화 조건에 대해 자세히 알아보려면 이 두 세션을 확인하세요 UIKit에서는 인텐트가 UISceneAppIntent 프로토콜을 따르면 해당 멤버를 사용하여 UIScene에 액세스할 수 있습니다 또는 장면 위임자가 인텐트 실행에 응답할 수 있습니다 AppIntentSceneDelegate를 준수해서 말이죠 마지막으로 활성화 조건을 사용해 어떤 장면이 앱 인텐트를 처리할지 결정할 수도 있습니다 다음 개선 사항은 새로운 ComputedProperty 매크로입니다 AppEntity에서 값을 별도로 저장하지 않아도 되게 하죠 여기 SettingsEntity는 UserDefaults의 defaultPlace를 복사하고 있는데 구조체에 중복 저장하는 걸 피하고 싶습니다 그 대신, 소스에서 직접 파생되어야 합니다 새로운 ComputedProperty 매크로를 사용하면 getter에서 직접 UserDefaults에 액세스해서 구현할 수 있습니다

    AppEntity 인스턴스화에 따른 리소스 부담을 줄이기 위해 DeferredProperty 매크로도 새로 추가했습니다 LandmarkEntity의 crowdStatus 속성은 네트워크 서버에서 가져오는 값이므로 비교적 많은 리소스가 소모됩니다 이럴 땐 시스템에서 명시적으로 요청할 때만 가져오고 싶습니다

    이 속성에 DeferredProperty를 지정하면 비동기 getter를 제공할 수 있고 내부에서 네트워크 요청 메서드를 호출하면 됩니다 이 비동기 getter는 단축어 같은 시스템 기능이 요청할 경우에만 호출됩니다 LandmarkEntity가 생성되어 전달될 때는 호출되지 않죠

    이 세 가지 속성 유형의 주요 차이점은 다음과 같습니다 일반적으로 시스템 오버헤드가 낮은 ComputedProperty를 선택하고 해당 속성의 계산 비용이 큰 경우에만 Deferred를 사용하세요 마지막으로, 이제 앱 인텐트를 Swift 패키지에 넣을 수 있습니다

    예전에는 프레임워크와 동적 라이브러리만 AppIntents 코드를 패키징할 수 있었지만 이제는 Swift 패키지와 정적 라이브러리에서도 가능합니다 AppIntentsPackage 프로토콜 사용 방법을 자세히 알아보려면 ‘앱 인텐트 알아보기’ 세션을 확인하세요 이러한 기능을 함께 알아보는 시간이 즐거우셨기를 바랍니다 다음 단계로 대화형 스니펫을 사용해서 인텐트에서 어떤 일을 할 수 있는지 직접 확인해 보세요 엔티티를 화면 속 콘텐츠와 연결해서 시스템이 자동으로 사용자에게 제안할 수 있도록 하고 다중 선택 API로 다양한 선택지를 제공하며 다양한 모드를 지원하여 인텐트가 어떤 방식으로 실행되든 최상의 경험을 제공하도록 만들어 보세요 코드를 더 자세히 살펴보려면 샘플 앱을 확인하세요 개발자 웹사이트에서 확인하실 수 있습니다 이 모든 내용을 여러분께 소개해 드릴 수 있어 정말 기쁩니다 여러분의 창의적인 아이디어를 기대하겠습니다

    그럼 마치겠습니다 시청해 주셔서 감사합니다

    • 4:08 - Returning a Snippet Intent

      import AppIntents
      import SwiftUI
      
      struct ClosestLandmarkIntent: AppIntent {
          static let title: LocalizedStringResource = "Find Closest Landmark"
      
          @Dependency var modelData: ModelData
      
          func perform() async throws -> some ReturnsValue<LandmarkEntity> & ShowsSnippetIntent & ProvidesDialog {
              let landmark = await self.findClosestLandmark()
      
              return .result(
                  value: landmark,
                  dialog: IntentDialog(
                      full: "The closest landmark is \(landmark.name).",
                      supporting: "\(landmark.name) is located in \(landmark.continent)."
                  ),
                  snippetIntent: LandmarkSnippetIntent(landmark: landmark)
              )
          }
      }
    • 4:31 - Building a SnippetIntent

      struct LandmarkSnippetIntent: SnippetIntent {
          static let title: LocalizedStringResource = "Landmark Snippet"
      
          @Parameter var landmark: LandmarkEntity
          @Dependency var modelData: ModelData
      
          func perform() async throws -> some IntentResult & ShowsSnippetView {
              let isFavorite = await modelData.isFavorite(landmark)
      
              return .result(
                  view: LandmarkView(landmark: landmark, isFavorite: isFavorite)
              )
          }
      }
    • 5:45 - Associate intents with buttons

      struct LandmarkView: View {
          let landmark: LandmarkEntity
          let isFavorite: Bool
      
          var body: some View {
              // ...
              Button(intent: UpdateFavoritesIntent(landmark: landmark, isFavorite: !isFavorite)) { /* ... */ }
      
              Button(intent: FindTicketsIntent(landmark: landmark)) { /* ... */ }
              // ...
          }
      }
    • 6:53 - Request confirmation snippet

      struct FindTicketsIntent: AppIntent {
      
          func perform() async throws -> some IntentResult & ShowsSnippetIntent {
              let searchRequest = await searchEngine.createRequest(landmarkEntity: landmark)
      
              // Present a snippet that allows people to change
              // the number of tickets.
              try await requestConfirmation(
                  actionName: .search,
                  snippetIntent: TicketRequestSnippetIntent(searchRequest: searchRequest)
              )
      
              // Resume searching...
          }
      }
    • 7:24 - Using Entities as parameters

      struct TicketRequestSnippetIntent: SnippetIntent {
          static let title: LocalizedStringResource = "Ticket Request Snippet"
      
          @Parameter var searchRequest: SearchRequestEntity
      
          func perform() async throws -> some IntentResult & ShowsSnippetView {
              let view = TicketRequestView(searchRequest: searchRequest)
      
              return .result(view: view)
          }
      }
    • 8:01 - Updating a snippet

      func performRequest(request: SearchRequestEntity) async throws {
          // Set to pending status...
         
          TicketResultSnippetIntent.reload()
      
          // Kick off search...
      
          TicketResultSnippetIntent.reload()
      }
    • 9:24 - Responding to Image Search

      struct LandmarkIntentValueQuery: IntentValueQuery {
      
          @Dependency var modelData: ModelData
      
          func values(for input: SemanticContentDescriptor) async throws -> [LandmarkEntity] {
              guard let pixelBuffer: CVReadOnlyPixelBuffer = input.pixelBuffer else {
                  return []
              }
      
              let landmarks = try await modelData.searchLandmarks(matching: pixelBuffer)
      
              return landmarks
          }
      }
    • 9:51 - Support opening an entity

      struct OpenLandmarkIntent: OpenIntent {
          static var title: LocalizedStringResource = "Open Landmark"
      
          @Parameter(title: "Landmark")
          var target: LandmarkEntity
      
          func perform() async throws -> some IntentResult {
              /// ...
          }
      }
    • 10:53 - Show search results in app

      @AppIntent(schema: .visualIntelligence.semanticContentSearch)
      struct ShowSearchResultsIntent {
          var semanticContent: SemanticContentDescriptor
      
          @Dependency var navigator: Navigator
      
          func perform() async throws -> some IntentResult {
              await navigator.showImageSearch(semanticContent.pixelBuffer)
      
              return .result()
          }
      
          // ...
      }
    • 11:40 - Returning multiple entity types

      @UnionValue
      enum VisualSearchResult {
          case landmark(LandmarkEntity)
          case collection(CollectionEntity)
      }a
      
      struct LandmarkIntentValueQuery: IntentValueQuery {
          func values(for input: SemanticContentDescriptor) async throws -> [VisualSearchResult] {
              // ...
          }
      }
      
      struct OpenLandmarkIntent: OpenIntent { /* ... */ }
      struct OpenCollectionIntent: OpenIntent { /* ... */ }
    • 13:00 - Associating a view with an AppEntity

      struct LandmarkDetailView: View {
      
          let landmark: LandmarkEntity
      
          var body: some View {
              Group{ /* ... */ }
              .userActivity("com.landmarks.ViewingLandmark") { activity in
                  activity.title = "Viewing \(landmark.name)"
                  activity.appEntityIdentifier = EntityIdentifier(for: landmark)
              }
          }
      }
    • 13:21 - Converting AppEntity to PDF

      import CoreTransferable
      import PDFKit
      
      extension LandmarkEntity: Transferable {
          static var transferRepresentation: some TransferRepresentation {
              DataRepresentation(exportedContentType: .pdf) {landmark in
                  // Create PDF data...
                  return data
              }
          }
      }
    • 14:05 - Associating properties with Spotlight keys

      struct LandmarkEntity: IndexedEntity {
      
          // ...
      
          @Property(indexingKey: \.displayName)
          var name: String
      
          @Property(customIndexingKey: /* ... */)
          var continent: String
      
          // ...
      }
    • 15:49 - Making intents undoable

      struct DeleteCollectionIntent: UndoableIntent {
          // ...
      
          func perform() async throws -> some IntentResult {
      
              // Confirm deletion...
      
              await undoManager?.registerUndo(withTarget: modelData) {modelData in
                  // Restore collection...
              }
              await undoManager?.setActionName("Delete \(collection.name)")
      
             // Delete collection...
          }
      }
    • 16:52 - Multiple choice

      struct DeleteCollectionIntent: UndoableIntent {
          func perform() async throws -> some IntentResult & ReturnsValue<CollectionEntity?> {
              let archive = Option(title: "Archive", style: .default)
              let delete = Option(title: "Delete", style: .destructive)
      
              let resultChoice = try await requestChoice(
                  between: [.cancel, archive, delete],
                  dialog: "Do you want to archive or delete \(collection.name)?",
                  view: collectionSnippetView(collection)
              )
      
              switch resultChoice {
              case archive: // Archive collection...
              case delete: // Delete collection...
              default: // Do nothing...
              }
          }
          // ...
      }
    • 18:47 - Supported modes

      struct GetCrowdStatusIntent: AppIntent {
      
          static let supportedModes: IntentModes = [.background, .foreground]
      
          func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog {
              if systemContext.currentMode == .foreground {
                  await navigator.navigateToCrowdStatus(landmark)
              }
      
              // Retrieve status and return dialog...
          }
      }
    • 19:30 - Supported modes

      struct GetCrowdStatusIntent: AppIntent {
          static let supportedModes: IntentModes = [.background, .foreground(.dynamic)]
      
          func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog {
              guard await modelData.isOpen(landmark) else { /* Exit early... */ }
      
              if systemContext.currentMode.canContinueInForeground {
                  do {
                      try await continueInForeground(alwaysConfirm: false)
                      await navigator.navigateToCrowdStatus(landmark)
                  } catch {
                      // Open app denied.
                  }
              }
      
              // Retrieve status and return dialog...
          }
      }
    • 21:30 - View Control

      extension OpenLandmarkIntent: TargetContentProvidingIntent {}
      
      struct LandmarksNavigationStack: View {
      
          @State var path: [Landmark] = []
      
          var body: some View {
              NavigationStack(path: $path) { /* ... */ }
              .onAppIntentExecution(OpenLandmarkIntent.self) { intent in
                  self.path.append(intent.landmark)
              }
          }
      }
    • 23:13 - Scene activation condition

      @main
      struct AppIntentsTravelTrackerApp: App {
          var body: some Scene {
              WindowGroup { /* ... */ }
      
              WindowGroup { /* ... */ }
              .handlesExternalEvents(matching: [
                  OpenLandmarkIntent.persistentIdentifier
              ])
          }
      }
    • 23:33 - View activation condition

      struct LandmarksNavigationStack: View {
          var body: some View {
              NavigationStack(path: $path) { /* ... */ }
              .handlesExternalEvents(
                  preferring: [],
                  allowing: !isEditing ? [OpenLandmarkIntent.persistentIdentifier] : []
              )
          }
      }
    • 24:23 - Computed property

      struct SettingsEntity: UniqueAppEntity {
      
          @ComputedProperty
          var defaultPlace: PlaceDescriptor {
              UserDefaults.standard.defaultPlace
          }
      
          init() {
          }
      }
    • 24:48 - Deferred property

      struct LandmarkEntity: IndexedEntity {
          // ...
      
          @DeferredProperty
          var crowdStatus: Int {
              get async throws {
                  await modelData.getCrowdStatus(self)
              }
          }
      
          // ...
      }
    • 25:50 - AppIntentsPackage

      // Framework or dynamic library
      public struct LandmarksKitPackage: AppIntentsPackage { }
      
      // App target
      struct LandmarksPackage: AppIntentsPackage {
          static var includedPackages: [any AppIntentsPackage.Type] {
              [LandmarksKitPackage.self]
          }
      }
    • 0:00 - 서론
    • 앱 인텐트는 앱이 단축어, Spotlight, 시각 지능 등의 기능을 여러 기기에 걸쳐 통합할 수 있도록 지원하는 프레임워크입니다. 새로운 대화형 스니펫, 시스템 전반의 앱 통합, 향상된 사용자 경험, 개발자용 편리한 API에 대해 알아보세요.

    • 0:55 - 대화형 스니펫
    • 최신 업데이트를 통해 이제 대화형 스니펫으로 앱을 개선할 수 있습니다. 이러한 스니펫은 앱 인텐트에 따라 맞춤형 정보를 표시하는 동적 뷰입니다. 예를 들어, 가드닝 앱은 스프링클러를 켜라고 제안하거나 음식 주문 앱은 사람들이 주문을 하기 전에 주문을 구성하도록 할 수 있습니다. TravelTracking 샘플 앱은 이를 보여주는 훌륭한 예입니다. 사용자가 가장 가까운 랜드마크를 검색하면 대화형 스니펫이 나타납니다. 이 스니펫에는 랜드마크의 이름과 하트 버튼이 포함되어 있습니다. 하트 버튼을 탭하면 현재 뷰에서 벗어나지 않고도 사용자는 랜드마크를 즐겨찾기에 추가할 수 있고 스니펫은 새로운 상태를 반영하여 즉시 업데이트됩니다. 이러한 상호작용은 새로운 ‘SnippetIntent’ 프로토콜을 통해 달성됩니다. 동작 후 정보를 표시하는 결과 스니펫을 만들 수 있고 이러한 스니펫에는 다른 앱 인텐트를 트리거하는 버튼 또는 토글을 포함할 수 있습니다. 예를 들어, TravelTracking 앱에서 하트 버튼은 즐겨찾기 업데이트 인텐트를 실행하고 티켓 찾기 버튼을 추가하여 다른 인텐트를 실행할 수 있습니다. 누군가 이러한 버튼과 상호작용하면 시스템은 해당 인텐트를 실행하고 스니펫도 그에 따라 업데이트됩니다. 시스템은 스니펫이 새로 고침될 때마다 쿼리에서 앱 엔터티를 가져와서 데이터가 항상 최신 상태로 업데이트되었는지 확인합니다. 이 프로세스는 원활하고 생동감 있게 진행되어 원활한 사용자 경험을 제공합니다. 계속 진행하기 전에 추가 정보를 제공하도록 요청하는 확인 스니펫을 생성할 수도 있습니다. 예를 들어, TravelTracking 앱에서 누군가가 티켓 찾기를 탭하면 확인 스니펫이 나타나 몇 개의 티켓을 검색할 것인지 묻습니다. 그러면 사용자는 이 스니펫과 상호작용할 수 있고 시스템은 사용자의 입력을 기반으로 실시간으로 뷰를 업데이트합니다.

    • 8:15 - 새 시스템 통합
    • iOS 26의 앱 인텐트는 시스템 통합을 강화하여 카메라 캡처 또는 스크린샷에서 바로 이미지를 검색할 수 있도록 지원합니다. 이제 앱에서 시스템 검색 패널에 검색 결과를 표시할 수 있습니다. 이 기능을 지원하려면 ‘IntentValueQuery’ 프로토콜을 준수하는 쿼리를 구현하고 ‘SemanticContentDescriptor’ 데이터를 처리하여 앱 엔티티 배열을 반환합니다. 누군가 결과를 탭하면 해당 ‘OpenIntent’가 트리거되어 앱에서 관련 페이지가 열립니다. ‘OpenIntents’는 이미지 검색에만 국한되지 않고 Spotlight에서도 사용할 수 있습니다. 검색 성능을 최적화하고, 여러 결과 페이지를 반환하며, 앱 내에서 사용자가 검색을 계속할 수 있도록 허용하는 것을 고려하세요. 이미지 검색 외에도 앱 인텐트는 화면 엔티티를 활성화하여 사용자가 앱에서 볼 수 있는 콘텐츠와 관련하여 Siri 및 ChatGPT와 상호작용할 수 있도록 허용합니다. 엔티티를 뷰와 연결하고, ‘Transferable’ 프로토콜을 준수하며, PDF, 일반 텍스트, 리치 텍스트 등 다양한 데이터 유형을 지원할 수 있습니다. Spotlight 검색을 강화하려면 앱 엔티티가 ‘IndexedEntity’를 준수하도록 하고, 이를 Spotlight에 기부하며, 엔티티로 화면 콘텐츠에 주석을 추가합니다. ‘PredictableIntent’를 구현하면 시스템이 사용자 행동을 학습하고 개인화된 제안을 제공할 수 있습니다.

    • 15:01 - 사용자 경험 개선 사항
    • 앱 개발 플랫폼의 새로운 기능은 개선된 실행 취소 기능, 다중 선택 옵션, 지원되는 모드를 통해 사용자 경험을 향상시킵니다. ‘UndoableIntent’ 프로토콜을 사용하면 익숙한 제스처를 사용하여 앱 인텐트로 수행한 작업을 되돌릴 수 있어 실험을 위한 안전망을 제공합니다. 프로토콜에 대한 인텐트를 준수하고 실행 취소 관리자에 실행 취소 작업을 등록하여 이를 구현할 수 있습니다. 다중 선택 API를 사용하면 이진 확인뿐만 아니라, 인텐트에 대한 여러 가지 옵션을 사람들에게 제시할 수 있습니다. 또한 대화 상자 및 사용자 정의 SwiftUI 뷰로 사용자 정의할 수 있습니다. 지원되는 모드로 인텐트에 더 많은 앱 포그라운드 제어 기능을 제공합니다. 이 기능을 사용하면 사용자가 기기와 상호작용하는 방식에 따라 인텐트가 다르게 행동하도록 할 수 있습니다. 예를 들어, 인텐트는 사람이 운전하는 동안에는 음성으로만 정보를 제공하지만, 사용자가 기기를 보고 있는 동안에는 앱으로 바로 이동하도록 지원할 수 있습니다. 해당 인텐트에 지원되는 모드를 지정하고 ‘currentMode’ 속성을 사용하여 어떤 모드가 활성화되어 있는지 확인할 수 있습니다.

    • 21:02 - 편의 API
    • SwiftUI의 새로운 뷰 컨트롤 API를 사용하면 UI 코드를 제거하고 뷰에서 직접 탐색을 처리하도록 하여 앱 인텐트를 리팩토링할 수 있습니다. ‘onAppIntentExecution’ 뷰 수정자를 사용하면 뷰가 특정 앱 인텐트에 응답하고 이에 따라 UI를 수정할 수 있습니다. 시스템은 앱을 포그라운드로 전환하기 직전에 액션 클로저를 실행하고 여러 뷰는 동일한 인텐트에 응답할 수 있습니다. ‘handlesExternalEvents’ API를 사용하여 인텐트를 처리하는 장면을 제어하여 상황에 맞게 적절히 탐색할 수 있습니다. 또한 ‘ComputedProperty’ 및 ‘DeferredProperty’와 같은 새로운 매크로는 ‘AppEntities’를 최적화하여 스토리지 및 인스턴스화 비용을 줄입니다. 이제 앱 인텐트도 Swift 패키지로 패키징되어 유연성과 재사용성이 더 높아집니다.

Developer Footer

  • 비디오
  • WWDC25
  • 앱 인텐트의 새로운 업데이트 살펴보기
  • 메뉴 열기 메뉴 닫기
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    메뉴 열기 메뉴 닫기
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    메뉴 열기 메뉴 닫기
    • 손쉬운 사용
    • 액세서리
    • 앱 확장 프로그램
    • App Store
    • 오디오 및 비디오(영문)
    • 증강 현실
    • 디자인
    • 배포
    • 교육
    • 서체(영문)
    • 게임
    • 건강 및 피트니스
    • 앱 내 구입
    • 현지화
    • 지도 및 위치
    • 머신 러닝 및 AI
    • 오픈 소스(영문)
    • 보안
    • Safari 및 웹(영문)
    메뉴 열기 메뉴 닫기
    • 문서(영문)
    • 튜토리얼
    • 다운로드(영문)
    • 포럼(영문)
    • 비디오
    메뉴 열기 메뉴 닫기
    • 지원 문서
    • 문의하기
    • 버그 보고
    • 시스템 상태(영문)
    메뉴 열기 메뉴 닫기
    • Apple Developer
    • App Store Connect
    • 인증서, 식별자 및 프로파일(영문)
    • 피드백 지원
    메뉴 열기 메뉴 닫기
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi 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 © 2025 Apple Inc. 모든 권리 보유.
    약관 개인정보 처리방침 계약 및 지침