• TextKit1: 텍스트 컨텐츠의 내용과 레이아웃을 관리하기 위한 프레임워크
    • Openstep부터 이어지고 있던 것
    • 레거시적인 부분이 많았다.
  • 그래서 TextKit2를 새로 만들었다.
    • macOS 11부터 들어가 있었다.
    • TextKit1과 공존한다.
    • 똑같이 Foundation, Quartz, Core Text위에 존재한다.
    • 구조도 MVC를 유지한다.
      • Model
        • NSTextContainer
        • NSTextStorage는 두개로 나뉘어 변경됨
          • NSTextContentManager
          • NSTextContentStorage
        • 추가 클래스 및 프로토콜
          • NSTextElement
          • NSTextParagraph
          • NSTextSelection
          • NSTextLocation
          • NSTextRange
      • View: UIKit, AppKit
      • Controller: NSLayoutManager → NSTextLayoutManager
        • 추가 클래스 및 프로토콜
        • NSTextLayoutFragment
        • NSTextLineFragment
        • NSTextViewportLayoutController
        • NSTextSelectionNavigation
    • 목표: 더 선언적으로 쓰기 위해서
  • 디자인 원칙
    • Correctness: Abstract away glyph handling
      • 다양한 언어를 핸들링 할때도 일관된 경험을 제공하기 위함
      • TextKit1은 이 부분에서 어려움이 있었다.
        • character와 glyph는 n대 n관계(1:1, 1:n, n:1 모두 가능) → n:1로 합쳐진 glyph를 합자( ligature) 라고 한다.
        • 서구권에서는 보통 1대1이고 합자가 없어도 의미가 달라지지 않는데 , 아랍 문자 등에서는 합자가 자주 나오고, 이게 없으면 의미가 잘못되는 경우도 많다.
        • TextKit1은 모든 API에 glyph range를 요구했는데, 이걸 구하는게 쉽지 않은 일이였다.
      • 그래서 TextKit2에서는 이를 직접 하지 않게했다.
        • 렌더링은 Core Text가 담당한다.
        • glyph range 대신 layout을 위한 고수준 객체들을 제공한다.
          • selection: NSTextSelection(읽기 전용의 값 객체 기반)
            • 선택 단위, 선택 방향, 선택 범위를 연속 혹은 불연속적으로 제공 → 이 값들이 read-only다
            • selection에 따른 액션은 NSTextSelectionNavigation으로 수행한다.
          • 텍스트의 위치: NSTextLocation(프로토콜) → UITextPostion과 유사. 서브클래싱이 프로토콜 채택으로 바뀐 정도
            • 보통은 기본 오브젝트를 쓸 것이다.
            • 단순 정수값이 아닌 이유는 document의 구조에 대한 다양한 정보를 담을 수 있기 때문이다.
          • 텍스트 범위: NSTextRange
    • Safety: Value Semantics
      • Value Semantics ≠ Value types
      • 값타입뿐 아니라 불변 클래스로도 Value Semantic을 구현할 수 있다.
      • 값을 바꾼 객체를 얻기 위해서는, 기존 객체의 값을 바꾸는 게 아니라 새로운 객체를 만들어야 한다.
      • TextKit1과의 비교
        • 기존 렌더링 과정
          • 텍스트 변경→ NSTextStorage → NSLayoutManager → Generate glyphs → Position Glyphs → Draw Glyphs in View

          • 이 방법으로는 커스텀 드로잉을 할 때 어디서 텍스트를 분리해야할 지 알기 어렵다. 예를 들어 아래 같은 걸 하고 싶다는 것이다.

            • 선언적(TextKit2의 지향점)
              • 텍스트를 엘리먼트 단위로 쪼갠다.
              • 이렇게 쪼개진 엘리멘트를 순서대로 위치를 잡는다
              • 엘리먼트 단위로 커스텀 드로잉을 한다.
            • 절차적(TextKit1으로 하려면)
              • 스토리지에서 캐릭터 인덱스 찾고
              • 캐릭터 인덱스를 글리프 인덱스로 바꾸고
              • 이게 grapheme cluster boundary인지 체크하고(잘못된 글리프면 안되니까)
              • UTF-16으로 바꿔줘야 되는지 체크하고
              • 필요하면 글리프 인덱스도 조정해줘야 되고
              • 특정 글리프 인덱스 이후로 라인 스페이싱 바꿔줘야 되고
              • 라인 프래그먼트 위치도 바꿔줘야 되고
              • 등등... 디테일이 너무 많이 들어간다.
          • 새로운 렌더링 과정

            • 앱에서는 NSTextLayoutFragments를 후킹해서 원하는 레이아웃을 만들 수 있다.
              • 이때 NSTextLayoutFragments는 Value Semantic이기 때문에, 새로운 객체를 만들어서 시스템에 돌려준다.

            • NSTextElement(읽기 전용의 값 객체 기반, 추상객체): 텍스트 컨텐츠의 일부를 표현

              • document에서 어느 범위인지를 표현하는 Range를 포함
              • document는 일련의 element들로 표현할 수 있다.
              • paragraph, attachment는 물론이고, 커스텀 element도 정의가 가능하다.
              • 커스텀을 위해서는 서브클래싱해야 한다.
              • 제공되는 구체 타입
                • NSTextParagraph: 단일 패러그래프를 NSAttributedString으로 표현
            • NSTextContentManager(추상 객체): 텍스트 컨텐츠로부터 element를 제공한다.

              • 실제 저장소의 wrapper 객체

              • 커스텀 저장소를 구현하기 위해서는 서브클래싱해야 한다.

              • 기본 구체 클래스는 NSTextContentStorage다. 기존 NSTextStorage를 저장소로 사용한다.

              • content를 변경하기 위해서는 Transaction 메소드로 변경 사항을 감싸야 한다. 이렇게 해야 TextKit2의 다른 부분들이 변경을 감지할 수 있다.

                class NSTextContentManager {
                	func performEditingTransaction(_ transaction: () -> Void)
                }
                
            • NSTextLayoutManager: 텍스트 레이아웃 프로세스를 조정한다.

              • Element를 받아서 layoutFragment를 만든다.
              • TextKit1의 NSLayoutManager와 유사하지만, Glyph를 다루지 않는다는 큰 차이가 있다.
            • NSTextLayoutFragment(읽기 전용의 값 객체 기반): 1개 이상의 TextElement의 레이아웃 정보

              • 주요 프로퍼티(get-only)
                • textLineFragments: 단일 라인에 대한 측정 정보. 개별 라인의 위상 정보, 전체 라인 갯수 등을 파악하는 데 좋다.
                • layoutFragmentFrame: 레이아웃 작업할 때, 텍스트 컨테이너에 쌓이는 프레임. 다른 뷰를 배치하거나, 텍스트의 전체 높이를 구하는데 좋다.
                  • 빈 라인도 개별적인 fragment frame을 가진다.
                • renderingSurfaceBounds: 텍스트를 그리기 위한 영역
              • 커스터마이징하기
    • Performance: Viewport-based layout and rendering
  • 데모
  • 모던화