스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
TextKit 및 텍스트 보기의 새로운 기능
UI 프레임워크의 TextKit 및 텍스트 보기에 대한 최신 업데이트를 확인하세요. 레이아웃 개선 및 API 개선 사항을 살펴보고, 여러 OS 버전 사이에서 호환성을 유지할 수 있는 방법을 알아보고, TextKit 2로 앱을 현대화하는 방법을 확인하세요. 이 세션을 최대한 활용하려면 WWDC21의 ‘Meet TextKit 2(TextKit 2 소개)'를 시청하시기 바랍니다.
리소스
관련 비디오
WWDC22
WWDC21
-
다운로드
'TextKit와 텍스트 뷰 변경 사항' 세션입니다 저는 Donna Tom이고 TextKit 엔지니어죠
iOS 15와 macOS Monterey에 TextKit 2를 도입했는데 개선된 성능과 정확도, 안전성을 갖춘 강력하고 새로운 텍스트 엔진이죠
TextKit 2의 뷰포트 기반의 레이아웃 구조는 텍스트 레이아웃의 성능을 한껏 높여 주며 내용이 많은 문서라면 특히 그렇습니다
TextKit 2는 해외 사용자에게 더 나은 텍스트 경험을 제공하는데 글리프 등 불필요한 복잡도를 제거하고 최신 폰트 기술인 OpenType와 Variable Fonts를 완벽하게 지원하죠
TextKit 2는 더 높은 수준의 객체를 작업할 수 있도록 하여 텍스트 레이아웃을 조정할 수 있고 더 쉽게 텍스트 레이아웃을 커스텀화할 수 있습니다 더 멋진 콘텐츠를 더 적은 코드로 만들 수 있죠
앞으로도 TextKit 2 엔진은 모든 Apple 플랫폼의 렌더링과 텍스트 레이아웃의 기초가 됩니다
앞으로 있을 성능 개선 및 업데이트, 개선 사항은 TextKit 2 엔진에 집중될 예정이죠 TextKit 2로 업데이트하면 여러분의 앱도 개선 사항을 바로 적용할 수 있습니다 TextKit 2에 관한 자세한 소개 자료는 'TextKit 2 만나 보기' 영상에서 살펴보십시오 이 영상에서는 TextKit 2를 이용하여 직접 텍스트 레이아웃 요소를 만드는 방법을 다룹니다 한편, 이 영상은 TextKit 2의 최신 개선 사항을 다루며 TextKit 2 기반의 텍스트 뷰들을 최대한 활용하는 법을 공유하죠 맞습니다, 텍스트 뷰들이라고 복수형을 썼는데요 iOS 16과 macOS Ventura부터 UIKit와 AppKit의 모든 텍스트 컨트롤은 TextKit 2를 쓰며 UITextView도 포함합니다 시스템의 레이아웃과 렌더링에 TextKit 2를 사용하는 거죠 모든 앱을 TextKit 2로 빠르게 전환하는 게 중요합니다 전환이 쉽도록 여러 가지 도구를 추가했죠 많은 앱은 코드 없이 전환할 수 있습니다 텍스트 뷰를 특별히 수정하지 않은 모든 앱에 해당하죠 그건 나중에 더 설명하겠습니다
먼저 TextKit 2의 새로운 기능부터 살펴보죠 방금 언급한 도구도 포함됩니다
그 후에는 텍스트 뷰의 TextKit 1 호환 모드를 자세히 알아보고
TextKit 2로 코드 전환을 준비하기 위한 현대화 전략을 논의하죠
먼저 TextKit 2의 새로운 기능부터 알아보겠습니다
TextKit 2는 iOS 15의 UIKit에 처음 도입됐고 UITextField를 업그레이드하여 사용할 수 있었죠 iOS 16에서는 UIKit의 TextKit 2 전환이 완료됐고 텍스트 컨트롤과 UITextView는 TextKit 2를 기본으로 사용합니다 대부분의 텍스트 뷰는 자동으로 TextKit 2를 채택하여 여러분의 작업이 요구되지 않죠 텍스트 뷰가 채택되지 않는 몇 가지 상황이 있는데 이는 영상의 호환 부분에서 다루겠습니다
AppKit 쪽도 비슷하죠 TextKit 2는 macOS Big Sur의 AppKit에 처음 도입됐고 macOS Monterey에서 NSTextField가 기본으로 사용하게 업그레이드했죠 NSTextView에서의 채택도 가능했습니다
macOS Ventura의 텍스트 컨트롤은 TextKit 2를 기본으로 사용하죠 UITextView처럼 NSTextView 대부분 TextKit 2를 자동으로 채택하며 여러분은 작업하지 않아도 됩니다
NSTextView를 둘러싼 얇은 막인 TextEdit는 macOS Ventura 모든 곳에서 TextKit 2를 사용하죠 TextEdit는 macOS Big Sur부터 일반 텍스트에서 TextKit 2를 썼고 macOS Ventura부터 리치 텍스트에도 사용했습니다
TextKit 2가 새로운 표준이 되면서 UITextView와 NSTextView에 편리한 생성자를 추가했죠 초기화 시점에 이 생성자를 사용하여 어떤 텍스트 엔진을 사용할지 정하십시오
TextKit 2를 사용하는 텍스트 뷰를 만들려면 새로운 생성자를 사용하고 UsingTextLayoutManager를 true로 설정하십시오 호환을 위해 TextKit 1을 쓰려면 false로 설정하세요
인터페이스 빌더의 텍스트 뷰에 텍스트 레이아웃 기능이 생겼죠 이 기능으로 인스턴스마다 어떤 레이아웃 체계를 쓸지 제어할 수 있습니다 기본 설정은 시스템 기본값인 TextKit 2입니다
아니면 명시적으로 TextKit 2 또는 TextKit 1을 고를 수 있죠
TextKit 2는 간단하지 않은 텍스트 컨테이너를 지원합니다 간단하지 않은 텍스트 컨테이너는 구멍이나 틈이 있을 수 있죠 이를 통해 텍스트가 이미지나 다른 콘텐츠를 감쌀 수 있죠
간단하지 않은 텍스트 컨테이너를 만들려면 NSTextContainer의 exclusionPaths 속성을 사용하여 텍스트가 나타나면 안 되는 영역을 정의하세요 TextKitAndTextView 샘플 코드에 적용 예시가 있으며 이 영상과 관련된 리소스에서 찾을 수 있습니다 관련 예시는 '제외 경로' 탭에서 찾을 수 있죠
TextKit 2에서 줄 바꿈 엔진도 개선하여 양쪽 정렬 문단의 줄 바꿈을 일정하게 설정할 수 있습니다 긴 문단의 텍스트에서 쉽게 볼 수 있는 미묘한 변화죠
두 버전의 같은 텍스트를 같은 곳에 배열했습니다
전통적인 줄 바꿈을 적용하면 줄이 늘어나고 단어 사이의 간격이 크죠
일정한 줄 바꿈에서는 이러한 현상이 줄어듭니다 이를 통해 가독성이 높아지며 TextKit 2를 통해 무료로 적용할 수 있죠 특별한 적용은 필요 없습니다
또한, 모든 플랫폼의 TextKit 2에 텍스트 목록 지원을 추가했죠 텍스트 목록을 통해 프로그램적으로 텍스트 뷰에서 숫자나 기호를 붙인 목록을 만들 수 있습니다 TextKit 2는 NSTextList를 사용해 텍스트 목록을 나타내죠 NSTextList는 AppKit에만 사용할 수 있었는데 iOS 16부터 UIKit에도 적용됩니다
NSmutableParagraphStyle과 NSTextList를 함께 사용하여 텍스트 스토리지의 문단을 목록 형식으로 나타내십시오 텍스트 뷰는 텍스트 스토리지에서 이러한 속성을 감지하여 문단이 목록 형태로 보이도록 양식을 바꿉니다
NSTextList는 원래 있었지만 TextKit 2에 추가된 사항이 있죠 목록에 다른 목록이 포함될 수 있으므로 나무 구조로 나타내는 것이 자연스럽습니다 TextKit 2는 NSTextElement를 개선하여 나무 구조를 지원하려고 자식과 부모 요소에 접근하는 속성이 있죠
NSTextListElement라는 하위 클래스도 추가했습니다 콘텐츠 매니저가 텍스트의 NSTextList를 발견하면 NSTextListElements를 생성하여 목록의 항목을 나타내죠
텍스트 목록 생성 방법과 항목 추가에 관한 자세한 사항은 TextKitAndTextView 샘플 코드를 참조하십시오 목록 탭에서 관련 예시를 찾을 수 있습니다
샘플 코드를 살펴보시면서 텍스트 첨부 예시도 확인하십시오 TextKit 2의 텍스트 첨부 뷰 제공자 API 사용법이 있죠
이 API는 UI나 NSView를 텍스트 첨부로 사용하게 해 주며 첨부 뷰를 통해 이벤트를 직접 처리할 수 있습니다 텍스트 첨부의 이벤트 처리가 훨씬 쉬워지는데 TextKit 2에서만 쓸 수 있는 기능이죠 지금까지 TextKit 2의 새로운 사항을 알아봤습니다 이제 TextKit 1 호환 모드를 자세히 살펴보죠 TextKit 2가 TextKit 1에 비해 디자인이 크게 바뀐 만큼 TextKit 1 구조를 많이 사용하는 앱의 경우 TextKit 2를 채택하는 시간이 오래 걸릴 수 있다는 걸 압니다 전환 기간에도 앱이 잘 작동할 수 있도록 UITextView와 NSTextView를 위한 TextKit 1 호환 모드를 추가했죠 NSLayoutManager API를 명시적으로 호출했을 때 텍스트 뷰가 NSTextLayoutManager를 NSLayoutManager로 대체하며 TextKit 1 사용으로 재설정하죠 텍스트 뷰가 TextKit 2에서 지원하지 않는 속성을 만나면 비슷한 일이 발생할 수 있는데 표나 인쇄에서 그럴 수 있습니다
UITextView에서 TextKit 1로 전환하는 현상을 발견하셨다면 로그에서 전환에 관한 경고 메시지를 확인하세요 _UITextViewEnablingCompatibility Mode의 브레이크포인트를 설정하여 스택 추적이나 다른 디버그 정보를 확보하십시오
NSTextView의 런타임 전환 정보를 얻으려면 willSwitch 또는 didSwitchToNSL LayoutManagerNotifications를 등록하시면 됩니다
TextKit 1로 돌아가야 한다면 초기화 시점에 프로그램적으로 초기화한 텍스트 뷰로 선택할 수 있죠 TextKit 1 레이아웃 매니저와 전용 텍스트 컨테이너를 쓰세요
아니면 새로운 편의성 생성자를 사용하여 TextKit 1 텍스트 뷰를 초기화하고 매개 변수는 false로 설정하십시오 그러면 텍스트 뷰가 TextKit 1을 사용합니다
세 번째 방법은 인터페이스 빌더를 사용하여 텍스트 뷰의 텍스트 레이아웃을 TextKit 1로 설정하는 거죠
유의할 사항입니다 텍스트 컨테이너의 레이아웃 매니저를 초기화 중간이나 이후에 교체한다면 텍스트 뷰가 TextKit 1로 설정되죠 초기화 때 TextKit 2 객체를 생성한 뒤 잠시 후에 삭제하는 건 비효율적입니다 타이밍에 따라 사용자에게 부작용이 나타날 수도 있죠 입력 중에 이런 일이 발생하면 텍스트 뷰가 포커스를 잃고 입력이 중단돼 텍스트 뷰를 다시 선택해야 계속할 수 있습니다 초기화 때 텍스트 뷰를 선택하지 않으면 피할 수 있죠 이제 호환 모드에 관해 모두 알아보셨으니 앱을 현대화하여 모든 문제를 피하고 TextKit 2를 채택하는 법을 살펴보죠 그전에 한 가지 중요한 사항을 알아두십시오
텍스트 뷰는 하나의 레이아웃 매니저를 가질 수 있죠 텍스트 뷰는 NSTextLayoutManager와 NSLayoutManager를 동시에 가질 수 없습니다
텍스트 뷰가 TextKit 1로 전환하면 자동으로 돌아갈 수 없죠 레이아웃 체계를 전환하는 건 비용이 많이 드는 절차이고 전환 시점에 표시했던 모든 UI 상태를 잃습니다 최적의 성능과 사용성을 위해 시스템은 TextKit 1에서 2로 텍스트 뷰를 재전환하지 않습니다 일방통행의 작업이죠
호환 모드를 피하는 것이 정말 중요하다는 의미입니다 텍스트 뷰가 호환 모드에 진입하는 여러 가지 이유가 있죠 텍스트 뷰가 호환 모드에 진입하는 가장 큰 이유는 텍스트 뷰의 layoutManager 속성에 접근할 때입니다 다른 이유는 발생 빈도가 드물죠
따라서 가장 중요한 전략은 텍스트 뷰 layoutManager 속성의 접근을 피하는 겁니다 텍스트 뷰의 텍스트 컨테이너의 layoutManager 접근도 피하세요 이러한 속성을 사용하는지 코드를 검토하고 TextKit 2에 상응하는 속성으로 바꾸거나 삭제하십시오
만약 TextKit 2가 없는 옛 OS에 앱을 배포한다면 layoutManager 코드를 완벽히 제거할 수 없을 겁니다
그때는 먼저 텍스트 뷰의 NSTextLayoutManager를 확인하세요
TextKit 2 코드를 if 문에 넣고 TextKit 1 코드는 layoutManager 접근과 함께 else 문에 넣으십시오 이러면 TextKit 2가 없을 때만 TextKit 1 코드를 실행하죠 layoutManager를 탐색할 때도 TextKit 1로 전환하지 않습니다
모든 지침을 따랐음에도 시스템에 의해 예상치 못하게 TextKit 1로 전환한다면 우리가 해결할 문제이므로 피드백 담당자에게 보고해 주세요 현상이 일어났을 때의 스택 추적 캡처를 보내주십시오 캡처를 얻으려면 UIKit의 _UITextViewEnabling CompatibilityMode나 AppKit의 willSwitchToNSLayout ManagerNotification을 사용하면 됩니다
이제 TextKit 1의 코드를 업데이트하는 방법을 알아볼 건데 NSLayoutManager부터 시작하죠 코드에 NSLayoutManager가 있는지 검토하셨다면 NSTextLayoutManager에서 TextKit 2 대체 항목을 알아내세요
일부 레이아웃 매니저 API는 TextKit 1과 2의 이름이 비슷하며 대체 항목이 직관적입니다 예를 들어 보죠 TextKit 1의 NSLayoutManager는 usedRect(for:)를 호출하여 텍스트 컨테이너 내 텍스트에 사각형을 둘렀습니다 TextKit 2에는 전용 속성이 usageBoundsForTextContainer이며 NSTextLayoutManager에 있습니다
TextKit 1에서는 '임시 속성'이라는 용어를 써서 렌더링에만 영향을 주는 속성을 가리켰지만 TextKit 2에서는 더 정확하게 '렌더링 속성'이라고 부릅니다
하지만 일부 TextKit 1의 API는 TextKit 2에 상응하는 API가 없죠 그 이유를 이해하려면 인도 칸나다어와 같은 글자의 많은 단어가 글리프와 정확하게 매핑되지 않는다는 걸 알아야 합니다 이러한 언어는 글리프를 분리하거나 재배열, 재결합 삭제할 수도 있죠
NSLayoutManager의 글리프 기반 API는 인접한 글자와 인접한 글리프가 직접 연관돼 있다고 가정하는데 모든 활자가 그런 건 아닙니다 이러한 API를 사용하면 칸나다어로 쓴 활자의 레이아웃과 렌더링이 망가질 수 있죠 그래서 TextKit 2에는 글리프 API가 전혀 없습니다 TextKit 1의 글리프 API를 2에서 한 번에 대체할 수 없죠 이런 API를 대체하려면 다르게 접근해야 합니다
글리프 기반 코드의 업데이트 방법을 알려 드리죠 먼저 어떤 글리프 API를 사용하는지 알아보십시오 다음은 해당 API를 어떻게 사용하는지 보고 더 높은 수준에서의 목적을 생각해 보세요 글리프 기반의 코드는 수준이 낮으며 높은 수준의 과업과 관련 없는 정보가 많습니다
높은 수준의 과업을 정의했다면 TextKit 2에서 사용할 수 있는 구조를 확인해 보십시오 레이아웃과 라인 프래그먼트 텍스트 선택 등이 있습니다 이를 통해 과업을 달성할 수 있죠 이 TextKit 1 코드를 살펴보십시오 글리프 API가 2개 사용됐는데 numberOfGlyphs와 lineFragmentRect (forGlyphAt: index)가 사용됐죠 TextKit 1 코드는 문서의 글리프를 모두 반복하며 라인 프래그먼트 렉트를 셉니다 상위 수준의 과업은 텍스트 뷰에서 래핑된 텍스트가 있는 라인을 세는 거죠 라인 프래그먼트 렉트를 사용하는 코드이므로 TextKit 2의 NSTextLineFragment와 NSTextLayoutFragment를 사용하면 됩니다 TextKit 2를 사용하기 위해 다시 작성한 코드죠 글리프를 반복하지 않고 문서의 텍스트 레이아웃 프래그먼트를 열거하고 모든 텍스트 라인 프래그먼트의 수를 센 클로저를 각 레이아웃 프래그먼트에 공급합니다
여러분의 코드를 TextKit 2로 업데이트할 때 기억하십시오 이제 NSRange를 기반으로 하는 코드의 업데이트를 논하겠습니다
TextKit 1은 NSRange를 사용하여 텍스트 내용을 인덱싱하고 NSRange는 스트링 내 선형 인덱스죠 "Hello TextKit 2!"와 같은 텍스트에서 NSRange로 "TextKit 2!" 부분을 나타내려면 위치는 6, 길이는 10입니다 6번째 글자에서 시작하며 길이가 10이기 때문이죠 선형 모형은 쉽게 이해할 수 있고 스트링으로 인덱싱하기 좋습니다
하지만 선형 모형은 스트링보다 구조가 복잡할 때 인덱 하기 어렵죠 예를 보여 드리겠습니다 HTML 문서는 나무 구조로 나타낼 수 있는데 각 태그가 나무의 가지로 표현되죠 "Hello TextKit 2!" 텍스트가 HTML 문서의 일부라면 NSRange로는 텍스트가 3단계나 묻혀 있는 스팬 태그에 있다는 걸 알 수 없습니다 선형 모형으로는 그런 정보까지 저장할 수 없죠 이런 식의 중첩 구조는 인덱싱할 수 없습니다 TextKit 2는 텍스트 콘텐츠의 범위를 나타내는 유형을 추가했죠 NSTextLocation은 텍스트 콘텐츠 내의 단일 위치를 표현하는 객체입니다 NSTextRange는 시작과 종료 위치로 구성되며 종료 위치는 범위에서 제외되죠 새로운 유형으로 HTML 문서의 중첩 구조를 표현할 수 있습니다 DOM 노드와 캐릭터 오프셋으로 위치를 정의하는 거죠
NSTextLocation은 프로토콜이므로 모든 커스텀 객체는 NSTextLocation 프로토콜 메소드를 적용하면 위치가 될 수 있습니다 이는 다양한 유형의 보조 공간에 유용한 데이터 구조로 구조적인 데이터와 그 모형을 지원하죠
하지만 텍스트 뷰는 이런 구조가 없는 NSAttributedString 보조 공간에 구축되어 있어 여러분의 앱을 포함한 많은 앱의 구조를 깨야만 바꿀 수 있습니다 텍스트 뷰 API인 selectedRange나 scrollRangeToVisible을 사용할 땐 NSRange를 계속 사용하시고 TextKit 2의 레이아웃이나 콘텐츠 매니저를 호출할 때는 NSRange와 NSTextRange 사이를 전환하십시오
텍스트 뷰의 NSRange를 NSTextRange로 변환하려면 참조하는 스트링의 정수 인덱스로 위치를 정의하세요
NSRange 위치를 NSTextRange의 시작 위치로 사용하시고
NSRange의 위치에 길이를 더해 NSTextRange 종료 위치로 쓰십시오 개념적으로 NSRange에서 NSTextRange로 매핑하는 방법이죠
실제로는 코드가 조금 다르게 보일 겁니다 NSTextLocations가 객체이기 때문이죠
콘텐츠 매니저를 통해야 위치를 계산할 수 있습니다
시작 위치를 계산하려면 콘텐츠 매니저에 문서 시작 위치를 물어본 뒤 NSRange의 위치만큼 오프셋 하십시오 시작 위치를 NSRange의 길이로 오프셋 하여 종료 위치를 구하세요
반대 방향으로 작업하려면 텍스트 콘텐츠 매니저를 통해 2개의 서로 다른 오프셋을 구하십시오
NSRange의 위치는 문서 시작 위치와 NSTextRange의 위치를 오프셋 한 값입니다 NSRange의 길이는 NSTextRange 시작 및 종료 위치의 오프셋이죠
UITextViews와 UITextFields는 UITextInput 프로토콜에 맞췄으며 UITextPosition과 범위를 사용합니다 UITextView나 UITextField를 사용할 때는 UITextRange를 NSTextRange로 직접 변환하지 않아도 되는 경우가 대부분이죠 만약 변환해야 하면 정수 오프셋 값을 두 범위 유형의 중개 값으로 사용하십시오
한편, UITextInput이 있는 커스텀 뷰를 사용한다면 UITextPosition과 UITextRange의 하위 클래스를 여러분의 뷰를 사용하여 직접 제어할 수 있습니다 UITextPosition의 하위 클래스를 NSTextLocation에 맞춰 필요한 메서드를 적용하고 하위 클래스를 사용하여 NSTextRanges를 직접 만드세요
양쪽 뷰의 콘텐츠가 비슷하더라도 서로 다른 뷰에 걸쳐 UITextPosition 객체를 재사용하지 마십시오 UITextPosition는 이를 만든 뷰에서만 유효합니다
이제 코드를 현대화하는 전략을 많이 배우셨을 거예요 이 전략을 적용하면 여러분의 앱은 TextKit 2의 이점을 누릴 수 있습니다
지금까지 TextKit와 텍스트 뷰의 새로운 소식을 알아봤죠 TextKit 2의 개선 사항을 많이 다뤘고 여러분의 앱을 업데이트하는 전략을 공유했으며 옛 OS 버전의 호환성을 유지하는 법도 알려 드렸습니다 여러분 앱에 TextKit 2를 적용하여 개선 사항을 최대한 활용하십시오 여러분의 텍스트 뷰가 TextKit 1로 의도치 않게 전환하는지 확인하세요 마지막으로 현대화 전략을 채택하여 여러분의 앱에서 TextKit 2를 사용하십시오 여러분이 만든 TextKit 2와 텍스트 뷰를 빨리 읽고 싶습니다 시청해 주셔서 감사합니다
-
-
13:21 - Check for NSTextLayoutManager first
if let textLayoutManager = textView.textLayoutManager { // TextKit 2 code goes here } else { let layoutManager = textView.layoutManager // TextKit 1 code goes here }
-
17:41 - Counting number of lines of wrapped text in a text view with TextKit 2
// Example: Updating glyph-based code var numberOfLines = 0 let textLayoutManager = textView.textLayoutManager textLayoutManager.enumerateTextLayoutFragments(from: textLayoutManager.documentRange.location, options: [.ensuresLayout]) { layoutFragment in numberOfLines += layoutFragment.textLineFragments.count }
-
21:10 - Convert NSRange to NSTextRange
let textContentManager = textLayoutManager.textContentManager let startLocation = textContentManager.location(textContentManager.documentRange.location, offsetBy: nsRange.location)! let endLocation = textContentManager.location(startLocation, offsetBy: nsRange.length) let nsTextRange = NSTextRange(location: startLocation, end: endLocation)
-
21:40 - Convert NSTextRange to NSRange
let textContentManager = textLayoutManager.textContentManager let location = textContentManager.offset(from: textContentManager.documentRange.location, to: nsTextRange!.location) let length = textContentManager.offset(from: nsTextRange!.location, to: nsTextRange!.endLocation) let nsRange = NSRange(location: location, length: length)
-
22:02 - Convert UITextRange to NSTextRange
let offset = textView.offset(from: textview.beginningOfDocument, to: uiTextRange.start) let startLocation = textContentManager.location(textContentManager.documentRange.location, offsetBy: offset)! let nsTextRange = NSTextRange(location: startLocation)
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.