← 모든 글

iOS 27의 접근성: 읽기 앱과 커스텀 컨트롤

긴 글을 읽는 일은 UI를 탐색하는 일과는 다른 종류의 문제예요. 목표는 컨트롤 사이를 건너뛰는 것이 아니라 텍스트 안을 매끄럽게 이동하는 것이고, WWDC26의 두 접근성 세션은 바로 이 경계선을 따라 정확히 나뉘어요. 하나는 읽기라는 표면을 다루고, 다른 하나는 그 주위를 둘러싼 컨트롤을 다뤄요.

이렇게 나누는 것이 중요한 이유는 고쳐야 할 문제의 성격이 서로 다르기 때문이에요. 읽기 앱의 결함은 연속성에 관한 것이에요. 문단을 넘어 이어지지 않는 텍스트, 페이지 맨 아래에서 멈춰 버리는 전체 읽기 같은 것들이죠. 반면 커스텀 컨트롤의 결함은 변환에 관한 것이에요. 모든 것을 시각적으로는 전달하지만 VoiceOver에는 아무것도 전달하지 못하는 제스처가 그 예예요. iOS 27은 이 둘 모두를 겨냥한 API를 제공하며, 그중 하나인 accessibilityLinkedGroup은 올해 새로 추가된 것이에요.

TL;DR

  • 읽기 앱은 먼저 시스템 텍스트 뷰를 선택해야 해요. UITextView, TextEditor, 그리고 선택을 활성화한 TextUITextInput 프로토콜을 채택하고 있어서 줄·단어·문자 단위 내비게이션과 선택을 별도 작업 없이 얻을 수 있어요1.
  • 레이아웃 때문에 텍스트 요소를 분리할 수밖에 없을 때는, VoiceOver가 그 경계를 넘어 이동할 수 있도록 요소들을 연결하세요. iOS 18은 accessibilityNextTextNavigationElement/accessibilityPreviousTextNavigationElement를 도입했고, iOS 27은 같은 효과를 내는 SwiftUI의 accessibilityLinkedGroup 모디파이어를 추가해요1.
  • 페이지로 나뉜 콘텐츠에서는 causesPageTurn 트레잇을 accessibilityScroll과 짝지으면, 전체 읽기 도중에 Speak Screen과 VoiceOver가 자동으로 페이지를 넘겨요1.
  • 직접 렌더링한 텍스트(스캔한 페이지, 고급 타이포그래피)는 이 모든 것을 잃어버려요. UITextInput을 완전히 채택하면 되살릴 수 있어요. selectionRects를 통한 기하 정보, textInRange를 통한 부분 문자열, 그리고 줄·단어·문자 내비게이션을 위한 토크나이저가 그것이에요1.
  • 커스텀 컨트롤은 네 가지 지침을 따라요. 목적, 값, 동작, 피드백이에요. 그 도구는 accessibilityLabel/accessibilityValue, accessibilityAdjustableAction을 동반한 .adjustable 트레잇, 다축 컨트롤을 위한 커스텀 액션, 그리고 제스처 중심 표면을 위한 다이렉트 터치(allowsDirectInteraction)예요2.

읽기 앱: 레이아웃이 떼어 놓은 텍스트 잇기

읽기 앱 세션은 겉보기에는 단순한 제약을 중심으로 구성되어 있어요. 발표자의 여행 가이드 앱은 페이지 전체를 하나의 뷰에 담는 대신, 레이아웃상의 요구 때문에 문단마다 별도의 UITextView를 사용해요1. 개별 텍스트 뷰는 그 자체로는 접근 가능해요. 문제는 그것들 사이의 경계에서 나타나요.

Watch on Apple Developer ↗
Apple은 각 문단이 별도의 뷰이기 때문에 VoiceOver가 한 문단 안에서 줄 단위 탐색에 갇혀 다음 문단으로 넘어가지 못하는 모습을 보여 준 다음, 텍스트 내비게이션 요소 API로 그것들을 연결해요.

발표자는 앱에 세 가지 목표를 세워요. VoiceOver와 Speak Screen이 텍스트 안을 매끄럽게 이동하는 세밀한 텍스트 내비게이션, 중단 없는 연속적인 읽기 경험, 그리고 완전한 텍스트 선택이에요1. 세션의 나머지는 각각을 어떤 API가 충족하는지 둘러보는 여정이에요.

별도의 뷰들을 가로지르는 내비게이션의 답은 iOS 18에서 도입된 텍스트 내비게이션 요소 API예요. 각 텍스트 요소에 대해, VoiceOver가 이동해야 할 다음과 이전의 접근 가능한 텍스트 요소를 반환해요. 세션의 예에서, 문단 1은 accessibilityNextTextNavigationElement에서 문단 2를 반환하고, 문단 2는 accessibilityPreviousTextNavigationElement에서 문단 1을 반환해요1. 한번 연결해 두면, VoiceOver는 막다른 길 소리를 내는 대신 한 문단의 끝을 지나 다음 문단의 첫 줄로 넘어가요.

iOS 27의 추가 사항은 SwiftUI에 있어요. 발표자의 말을 빌리면, iOS 27부터는 accessibilityLinkedGroup 모디파이어로 여러 텍스트 요소를 함께 연결하면 같은 효과를 얻을 수 있어요1. 연결할 요소들에 같은 idnamespace를 부여하면, 다음/이전을 일일이 관리하지 않고도 요소 간 텍스트 내비게이션 동작을 물려받아요. AppKit은 Mac에서 같은 결과를 내는 대응물 accessibilitySharedTextUIElements를 제공해요1. 이 클러스터의 SwiftUI를 이루는 것들 글은 이런 SwiftUI 모디파이어가 내부의 접근성 트리로 어떻게 해석되는지를 다뤄요.

연속성이 두 번째 목표예요. 페이지로 나뉜 콘텐츠는 스와이프가 필요하지만, 전체 읽기는 오디오북처럼 페이지 경계를 무시해야 해요. 세션에서 Speak Screen은 발표자가 각 페이지의 마지막 문단에 causesPageTurn 트레잇을 적용하기 전까지 첫 페이지 맨 아래에서 딱 멈춰 버려요. accessibilityScroll과 짝지으면, 끝에 도달했을 때 Speak Screen과 VoiceOver가 자동으로 다음 페이지로 스크롤하며, 이 트레잇은 UIKit과 SwiftUI 모두에서 사용할 수 있어요1.

세 번째 목표인 선택은 대부분 시스템 텍스트 뷰에서 별도 작업 없이 얻을 수 있지만, 세션은 사려 깊은 손길을 더해요. VoiceOver의 편집 로터에서 호출할 수 있는 “추천에 저장” 액션이에요. 발표자는 문단 텍스트 뷰에서 accessibilityCustomActions를 재정의하고, 일반 액션이 아니라 텍스트 선택 작업과 나란히 편집 로터에 나타나도록 편집 카테고리로 커스텀 액션을 만들어요1. 지침은 분명해요. 커스텀 액션이 텍스트 선택과 연관될 때는 편집 카테고리를 사용하세요.

직접 텍스트를 렌더링할 때: UITextInput을 완전하게

읽기 세션의 후반부는 시스템 뷰가 선택지가 아닌 경우를 다뤄요. 커스텀 텍스트는 고급 타이포그래피를 위한 전용 읽기 앱, 앱을 가로질러 공유되는 코드, 또는 스캔한 페이지에서 등장해요. 발표자의 예가 가장 날카로워요. 여행 가이드의 텍스트 뷰를 손으로 쓴 노트를 스캔한 페이지로 바꾸는 거예요. 그 대가는 전면적이에요. 이미지로 바꾸면 UITextView가 별도 작업 없이 제공하던 접근성 동작을 잃어버리고, 가장 기본적인 것, 즉 텍스트를 읽어 주는 것조차 안 돼요. VoiceOver는 그저 “이미지”라고만 말해요1.

해결책은 UITextInput 프로토콜을 채택하는 거예요. 이것은 어떤 접근성 요소에든 얹을 수 있으며, 렌더링된 텍스트나 이미지 속 텍스트를 표준 텍스트 뷰만큼 접근 가능하게 만들어요1. 다만 세션에서 솔직하게 밝히는 주의점이 있어요. 온전한 이점을 얻으려면 이것을 전부 구현해야 한다는 거예요. 발표자는 핵심이 되는 부분들을 차례로 짚어 가요.

  • 기하 정보. selectionRects는 주어진 범위에 대한 하이라이트 사각형을 계산해요. 손글씨 이미지로 작업하는 발표자는 각 줄의 알려진 높이와 너비를 사용해 커스텀 selectionRectFromImage 함수로 사각형을 근사한 다음, 조립한 배열을 반환해요1.
  • 부분 문자열. textInRange는 보조 기술이 질의한 텍스트의 일부만 반환해요1.
  • 토크나이저. 줄·문장·단어·문자 단위 내비게이션은 토크나이저를 거쳐요. 세션은 UIKit의 UITextInputStringTokenizer를 서브클래싱해 커스텀 레이아웃에 맞춰요1.

한 가지 다듬기는 명시적으로 선택 사항이에요. 핸들과 하이라이트로 선택을 완성된 느낌으로 만들기 위해, 발표자는 페이지 뷰에 UITextInteraction을 추가하고, 선택이 바뀔 때 입력 델리게이트를 호출해 시스템이 시각 표현을 갱신하게 해요. 세션은 이 단계가 UITextInput 자체가 요구하는 것은 아니라고 언급해요. 표준 텍스트 뷰에 맞먹도록 경험을 마무리하는 것일 뿐이에요1. 그리고 UITextInput은 앞서의 API와 함께 작동하므로, causesPageTurn과 내비게이션 요소도 커스텀 텍스트에서 동작해요.

세션이 짚어 주는, 과소평가하기 쉬운 보상이 하나 있어요. 이 작업은 VoiceOver와 Speak Screen에만 도움이 되는 게 아니에요. iOS 26부터 Accessibility Reader는 더 읽기 쉽도록 조정된 화면으로 앱의 콘텐츠를 열 수 있고, 같은 접근 가능한 텍스트 실천은 그 경험도 향상시켜요1.

커스텀 컨트롤: 목적, 값, 동작, 피드백

커스텀 컨트롤 세션은 표준 SwiftUI 슬라이더와, 그것이 왜 작동하는가에 대한 논의로 시작해요. 트랙, 가운데에 놓인 핸들, 끌 수 있다는 단서, 그리고 즉각적인 피드백을 한눈에 읽어 낼 수 있어요. 누구도 그것을 설명해 주지 않았는데도요. 그러자 세션은 당연한 질문을 던져요. 화면을 볼 수 없는 사람이라면 어떨까요? VoiceOver는 “밝기, 50%, 조절 가능”이라고 읽어 주고 위아래로 스와이프하라는 힌트를 더해 답해요. 이것은 시각적으로 전달되던 같은 네 가지, 즉 목적, 값, 가능한 동작, 그리고 값이 바뀔 때의 피드백을 전달해요2.

Watch on Apple Developer ↗
Apple은 커스텀 커피 디스펜서 컨트롤을, 밋밋한 “버튼, 6온스”에서, 레이블·값·조절 액션을 동반한 .adjustable 트레잇을 더해 VoiceOver로 조작할 수 있는 조절 가능한 슬라이더로 바꿔요.

이 네 단어(목적, 값, 동작, 피드백)가 세션의 지침이며, 모든 예가 그것으로 되돌아와요2. 첫 번째는 커피 디스펜서 컨트롤이에요. 위로 끌면 커피가 늘고, 아래로 끌면 줄며, 채워진 높이가 온스를 나타내요. 아무 작업도 하기 전에는 VoiceOver가 이것을 값을 바꾸는 방법에 대한 단서 없이 일반적인 “버튼, 6온스”로 읽어요2. 고치는 과정은 점진적이에요.

  1. 목적과 값. accessibilityLabel이 이것을 “커피 디스펜서”로 명명하고, accessibilityValue가 현재 채워진 양을 알려요2.
  2. 동작. .adjustable 트레잇은 이 컨트롤이 위아래 스와이프에 반응한다고 VoiceOver에 알리고, accessibilityAdjustableAction.increment 또는 .decrement라는 방향 매개변수를 가진 클로저를 제공해 각 경우를 처리해요2.

이렇게 하면 한 번에 1온스씩 조절할 수 있어요. 더 세밀한 제어를 위해, 세션은 VoiceOver에 내장된 패스스루 제스처를 사용해요. 더블 탭 후 길게 누르는 동작으로, 컨트롤의 accessibilityActivationPoint에서 시작해 손가락이 움직이는 대로 터치 이벤트를 컨트롤에 직접 보내요. 발표자는 활성화 지점을 현재 채워진 양에 맞춰 설정해요2. 패스스루 도중의 피드백은 절제에 관한 작은 교훈이에요. 세션은 값이 실제로 바뀌었고 또한 0.3초 이상 지났을 때만 안내를 게시해요. 모든 변화를 안내하면 시끄러울 테니까요2.

이퀄라이저 패드는 기준을 한 단계 높여요. 이것은 2차원 컨트롤이고, 세션은 .adjustable이 잘못된 도구라고 솔직히 인정해요. 그 증가/감소 액션은 하나의 축만 다루기 때문이에요. 답은 커스텀 액션이에요. accessibilityAction 모디파이어를 “위로 이동”, “오른쪽으로 이동”, “아래로 이동”, “왼쪽으로 이동”의 네 번 적용하며, 각각은 하나의 축을 범위 안에 묶인 고정 단계만큼 움직여요2. 조절 액션과 달리, 커스텀 액션은 정의한 어떤 동작이든 지원하고, Switch Control과 Voice Control 사용자에게도 닿아요2.

다이렉트 터치: 제스처 자체가 핵심일 때

세션의 마지막 예는 쓰다듬고, 두드리고, 핀치하면 서로 다른 반응을 보이는 가상 고양이 컨트롤이에요. 여기서는 패스스루가 잘 맞지 않는다고 발표자는 말해요. 사람들이 한 동작을 거듭 반복하고 싶거나 여러 제스처를 쓰고 싶을 수 있기 때문이에요2. 그래서 이 컨트롤은 다이렉트 터치를 선택해요.

Watch on Apple Developer ↗
Apple은 제스처 기반 컨트롤에 .requiresActivation을 동반한 .accessibilityDirectTouch 모디파이어를 더해, 터치가 VoiceOver에 가로채이지 않고 곧장 고양이로 전달되게 해요.

allowsDirectInteraction 트레잇은 영역을 다이렉트 터치 영역으로 표시해요. 터치 이벤트가 VoiceOver에 처리되지 않고 곧장 컨트롤로 전달되어, 컨트롤이 지원하는 모든 제스처가 작동해요2. 두 가지 옵션이 그 동작을 형성해요. .requiresActivation은 더블 탭하기 전까지 컨트롤을 반응하지 않는 상태로 두어, 실수로 작동시키지 않고 화면을 가로질러 끌 수 있게 하며, 그다음 다이렉트 터치는 포커스가 그 요소를 벗어날 때까지 활성 상태로 남아요. .silentOnTouch는 그 영역에서 VoiceOver를 조용히 시켜요. 이는 VoiceOver 음성이 덮어 버릴 수 있는 자체 오디오를 내는 컨트롤을 위한 것이에요2. 가상 고양이는 .requiresActivation을 동반한 .accessibilityDirectTouch를 사용해요2.

세션은 플랫폼으로서의 접근성에서 펼친 플랫폼 접근성 논의로 이어지는 단서로 마무리돼요. 모든 사람이 다이렉트 터치 제스처를 할 수 있는 것은 아니므로, 가능한 한 커스텀 액션 같은 다른 경로를 제공해 Switch Control과 Voice Control 사용자가 같은 상호작용에 닿게 하세요2.

도입 지침

두 세션 모두 같은 지시로 끝나요. VoiceOver를 켜고 자신의 앱을 직접 점검하세요. 구체적으로는,

  • 시스템 텍스트 뷰 위의 읽기 표면에서는, 전체 읽기 제스처를 시도하고, 줄 로터로 탐색하고, 텍스트를 선택해 보세요. 전체 읽기가 페이지 경계에서 멈춘다면 accessibilityScroll과 함께 causesPageTurn을 채택하세요. 별도의 텍스트 요소 사이에서 줄 내비게이션이 막다른 길에 다다른다면, 내비게이션 요소 API(UIKit)나 accessibilityLinkedGroup(SwiftUI, iOS 27)으로 그것들을 연결하세요1.
  • 직접 텍스트를 렌더링한다면, 부분적이 아니라 완전한 UITextInput 채택을 계획하세요. 이 프로토콜은 전부 아니면 전무이고, 선택 사항인 UITextInteraction 단계가 선택을 네이티브처럼 느껴지게 만들어요1.
  • 어떤 커스텀 컨트롤이든, 네 가지 지침을 순서대로 짚으세요. VoiceOver 사용자가 그것이 무엇인지(레이블), 어떤 상태인지(값), 무엇을 할 수 있는지(adjustable 트레잇이나 커스텀 액션), 그리고 무슨 일이 일어났는지(안내)를 알 수 있나요? 다이렉트 터치는 그 값이 제스처 자체인 컨트롤에만 아껴 쓰고, 제스처가 아닌 폴백과 짝지으세요2.

거듭 나타나는 주제는 먼저 시스템 컴포넌트를 선택하고, 커스텀 경로는 진짜로 완전한 작업을 요구하는 예외로 다루라는 거예요. iOS 앱의 세 가지 표면 글은 접근성을, 보이는 UI 및 App Intents와 나란히 1급 표면으로 자리매김해요. 이 세션들은 텍스트와 컨트롤에 대해 그 표면을 제대로 마무리한다는 것이 어떤 모습인지 보여 줘요.

FAQ

iOS 27의 읽기 앱을 위한 새로운 접근성 API는 무엇인가요?

SwiftUI의 accessibilityLinkedGroup 모디파이어예요. iOS 27부터, 같은 idnamespace로 여러 텍스트 요소를 연결하면 요소 간 텍스트 내비게이션이 부여되어, VoiceOver가 한 요소의 마지막 줄에서 다음 요소의 첫 줄로 이동해요. 이것은 iOS 18의 accessibilityNextTextNavigationElement/accessibilityPreviousTextNavigationElement API와 AppKit의 accessibilitySharedTextUIElements에 해당하는 SwiftUI 버전이에요1.

표준 텍스트 뷰를 사용한다면 UITextInput을 구현해야 하나요?

아니요. UITextView(UIKit), TextEditor, 선택을 활성화한 Text(SwiftUI), 그리고 NSTextView(AppKit)는 이미 UITextInput을 채택하고 있어서 줄·단어·문자 내비게이션과 선택을 기본으로 제공해요. UITextInput을 직접 채택하는 것은, 스캔한 페이지나 고급 타이포그래피처럼 커스텀 텍스트를 렌더링해 그러한 시스템 동작을 잃는 경우뿐이에요1.

커스텀 컨트롤은 언제 adjustable 트레잇을 쓰고 언제 커스텀 액션을 써야 하나요?

슬라이더처럼 증가와 감소가 의미를 갖는 단일 축 값에는 accessibilityAdjustableAction을 동반한 .adjustable 트레잇을 쓰세요. 2차원 패드처럼 하나의 축으로는 충분하지 않거나, VoiceOver가 이름으로 읽어 주는 개별 동작을 노출하고 싶을 때는 커스텀 액션(accessibilityAction 모디파이어)을 쓰세요. 세션의 이퀄라이저 패드가 네 개의 커스텀 액션(위/오른쪽/아래/왼쪽으로 이동)을 쓰는 것은 바로 adjustable 트레잇이 한 방향만 다루기 때문이에요2.

다이렉트 터치란 무엇이며 언제 써야 하나요?

다이렉트 터치(allowsDirectInteraction 트레잇, SwiftUI에서는 .accessibilityDirectTouch로 적용)는 터치가 VoiceOver에 처리되지 않고 곧장 컨트롤로 전달되도록 영역을 표시해, 사람들이 컨트롤이 지원하는 모든 제스처를 쓸 수 있게 해요. 패스스루 제스처가 잘 맞지 않는 제스처 중심 컨트롤에 쓰고, 실수로 작동하는 것을 막기 위해 .requiresActivation과 짝지으세요. 다이렉트 터치 제스처를 할 수 없는 사람들을 위해, 커스텀 액션 같은 제스처가 아닌 폴백을 항상 제공하세요2.

접근 가능한 텍스트는 VoiceOver 외의 기능에 어떻게 도움이 되나요?

같은 작업은 Speak Screen에서, 그리고 iOS 26부터는 Accessibility Reader에서 보상받아요. Accessibility Reader는 더 읽기 쉽도록 조정된 화면으로 앱의 콘텐츠를 열어요. 읽기 세션이 다루는 텍스트 내비게이션, 페이지 넘김, UITextInput 실천을 구현하면, 한 묶음의 변경으로 세 가지 경험 모두가 향상돼요1.

관련 읽을거리

참고 문헌


  1. Apple, WWDC26 세션 219, “Enhance the accessibility of your reading app.” developer.apple.com/videos/play/wwdc2026/219. UITextInput을 채택하는 시스템 텍스트 뷰(UITextView, TextEditor, 선택을 활성화한 Text, NSTextView), iOS 18의 accessibilityNextTextNavigationElement/accessibilityPreviousTextNavigationElement API와 iOS 27의 SwiftUI accessibilityLinkedGroup 모디파이어(AppKit: accessibilitySharedTextUIElements), accessibilityScroll을 동반한 causesPageTurn, 편집 카테고리를 사용한 accessibilityCustomActions를 통한 텍스트 선택 액션, 커스텀 텍스트를 위한 완전한 UITextInput 채택(selectionRects, textInRange, UITextInputStringTokenizer)과 선택 사항인 UITextInteraction, 그리고 iOS 26의 Accessibility Reader에 대한 출처. 

  2. Apple, WWDC26 세션 220, “Refine accessibility for custom controls.” developer.apple.com/videos/play/wwdc2026/220. 목적/값/동작/피드백 지침, accessibilityLabel, accessibilityValue, .adjustable 트레잇, accessibilityAdjustableAction을 사용한 커피 디스펜서 컨트롤, 스로틀링된 안내(값 변경과 0.3초 경과)를 동반한 accessibilityActivationPoint에서의 패스스루 제스처, 이퀄라이저 패드를 위한 accessibilityAction 모디파이어, 그리고 가상 고양이 컨트롤을 위한 .requiresActivation.silentOnTouch를 동반한 allowsDirectInteraction(.accessibilityDirectTouch)을 통한 다이렉트 터치, 더불어 제스처가 아닌 폴백 알림에 대한 출처. 

관련 게시물

iOS 27의 SwiftUI 성능과 상호 운용성

iOS 27 SwiftUI가 lazy-stack 스크롤, GPU 셰이더 효과, AppKit/UIKit 상호 운용을 어떻게 처리하는지, 세 개의 공식 WWDC26 UI Frameworks 세션에서 도출한 내용입니다.

15 분 소요

iOS 27의 SwiftUI 새 기능

iOS 27은 SwiftUI의 리스트, 문서, 툴바, 오류 처리를 재설계합니다. 드래그 재정렬, 읽기/쓰기 문서 모델, 툴바 오버플로, 항목 기반 알림이 추가됩니다.

18 분 소요

디자인 엔지니어의 에이전트 스택

디자인 엔지니어에게는 시각적 일관성, 타이포그래피 규율, 색상 준수, 그리고 감각을 강제하는 에이전트 인프라가 필요합니다. 여기 여섯 가지 구성 요소를 소개합니다.

9 분 소요