Closed13

TextKit 2研究

蔀

TextKit 1 vs TextKit 2

  • TextKit 1は NSLayoutManager を中心とした古いAPI
    • グリフ(文字要素)が操作の最小単位
  • TextKit 2は NSTextLayoutManager を中心としたWWDC21で発表されたAPI
蔀
  • WWDCのサンプルコード→ビルドできず
  • 縦書きレイアウト→縦書きの方が表示されず

だったので、ミニマムなサンプルプロジェクトつくる

蔀

とりあえず表示したものがこちら

import UIKit

class ViewController: UIViewController {
    let textContentStorage = NSTextContentStorage()

    override func viewDidLoad() {
        super.viewDidLoad()
        let textView = UITextView(usingTextLayoutManager: true)
        textContentStorage.addTextLayoutManager(textView.textLayoutManager!)
        let attributedString = NSMutableAttributedString(string: """
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non mollis lorem, quis mollis nunc. Pellentesque purus massa, porttitor eu augue in, semper laoreet est. Fusce vitae vestibulum felis, quis pulvinar elit. Donec sagittis euismod magna, ac dapibus nisi porta et. Phasellus venenatis at diam at tempor. Duis posuere volutpat sem iaculis mollis. Aliquam gravida quam eget tristique viverra. Aenean euismod condimentum risus. Nullam posuere purus eros, in lobortis massa ultricies ac. Cras convallis tellus mauris, eu blandit dui viverra ac. Etiam egestas ultrices sapien, congue pharetra ligula.

        Nulla rhoncus nibh sed nunc placerat, nec aliquet libero imperdiet. Suspendisse ut quam ac purus tristique fringilla. Vestibulum dignissim hendrerit dui et luctus. Praesent vehicula justo eu elit congue mattis ac eu massa. Donec commodo congue orci, ut elementum quam finibus sit amet. Cras vestibulum condimentum tellus id facilisis. Aliquam vitae malesuada diam. Maecenas dapibus massa urna, at cursus nisl scelerisque quis. Cras tincidunt nisl mauris, ut rhoncus felis congue a. Donec non nulla quis leo ornare blandit pellentesque a ligula. Morbi fringilla lectus et consectetur fringilla.

        Sed feugiat nulla vel ipsum vulputate pellentesque. Aenean quis magna felis. Sed sagittis porta ullamcorper. Mauris iaculis diam non purus bibendum, sed pretium quam sollicitudin. Proin sit amet mauris magna. Aliquam sit amet diam magna. Integer convallis dolor tellus, nec egestas lacus mollis placerat. Aenean posuere elit ac enim finibus aliquet. Vestibulum sit amet tristique ipsum, at maximus ipsum. Nullam consectetur elit ac bibendum vulputate. Nulla facilisi. Integer finibus rutrum ligula et tincidunt. Phasellus blandit elit libero, sit amet accumsan ligula efficitur vitae. Suspendisse malesuada sit amet nisi sit amet rutrum. Suspendisse ac ultrices odio. Nam consequat turpis et urna malesuada tempor.

        Quisque ultricies ultrices pharetra. Nunc consequat dignissim volutpat. Donec vel urna in mi laoreet mattis at eu ligula. Cras enim lacus, interdum at efficitur et, volutpat eu nulla. Praesent tincidunt congue urna. Duis id ante vulputate, pretium diam nec, lobortis ante. Nam finibus nisi eget sollicitudin imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Integer scelerisque felis velit, a luctus justo accumsan sed. Nunc ultrices euismod aliquet. Aliquam consequat molestie elit, at laoreet lacus rhoncus a. Curabitur pulvinar rhoncus pharetra.

        Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean lectus enim, tempus tempus pretium eget, tincidunt rhoncus arcu. Phasellus eu laoreet tortor, quis pharetra neque. Sed feugiat odio et urna malesuada, sed luctus ex laoreet. In mollis urna lectus, a ornare felis sodales in. Nullam pellentesque nulla turpis, et venenatis arcu faucibus in. Nam mi urna, ornare vitae elit ut, tincidunt molestie urna. Sed blandit maximus augue.
        """)
        textContentStorage.attributedString = attributedString
        view.addSubview(textView)

        textView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            textView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            view.safeAreaLayoutGuide.trailingAnchor.constraint(equalTo: textView.trailingAnchor),
            view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: textView.bottomAnchor)
        ])
    }
}


蔀

NSTextContainer ってなんのために存在するんだ?
NSTextLayoutManager を外出ししたときの中継役?
あとUITextView のプロパティtextLayoutManagerはget onlyだった

蔀

書きなおしたもの。こっちの方が良さそうかな

textContentStorage.addTextLayoutManager(textLayoutManager)
textContentStorage.attributedString = attributedString
let textContainer = NSTextContainer()
textLayoutManager.textContainer = textContainer
let textView = UITextView(frame: .zero, textContainer: textContainer)
view.addSubview(textView)
蔀
private func showTextLayoutFragment() {
    textLayoutManager.enumerateTextLayoutFragments(from: textContentStorage.documentRange.location) {
        print($0.textLineFragments)
        return true
    }
}

こんな感じで行要素を吐き出してみた

[<NSTextLineFragment: 0x114711ba0 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non mollis lorem, quis mollis nunc. Pellentesque purus massa, porttitor eu augue in, semper laoreet est. Fusce vitae vestibulum felis, quis pulvinar elit. Donec sagittis euismod magna, ac dapibus nisi porta et. Phasellus venenatis at diam at tempor. Duis posuere volutpat sem iaculis mollis. Aliquam gravida quam eget tristique viverra. Aenean euismod condimentum risus. Nullam posuere purus eros, in lobortis massa ultricies ac. Cras convallis tellus mauris, eu blandit dui viverra ac. Etiam egestas ultrices sapien, congue pharetra ligula.
">]
[]
[]
[]
[]
[]
[]
[]
[<NSTextLineFragment: 0x1147099d0 "Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean lectus enim, tempus tempus pretium eget, tincidunt rhoncus arcu. Phasellus eu laoreet tortor, quis pharetra neque. Sed feugiat odio et urna malesuada, sed luctus ex laoreet. In mollis urna lectus, a ornare felis sodales in. Nullam pellentesque nulla turpis, et venenatis arcu faucibus in. Nam mi urna, ornare vitae elit ut, tincidunt molestie urna. Sed blandit maximus augue.">]

途中が抜けてる気がするが謎

蔀
private func showTextLayoutFragment() {
    textLayoutManager.enumerateTextLayoutFragments(from: textContentStorage.documentRange.location) {
        print("layoutFragmentFrame")
        print($0.layoutFragmentFrame)
        $0.textLineFragments.forEach {
            print("textLineFragments")
            print($0.typographicBounds)
        }
        return true
    }
}

こんな値。

layoutFragmentFrame
(5.0, 0.0, 3177.556640625, 13.8)
textLineFragments
(0.0, 0.0, 3177.556640625, 13.8)
layoutFragmentFrame
(0.0, 13.8, 4.998046875, 13.8)
layoutFragmentFrame
(0.0, 27.6, 2968.83984375, 13.8)
layoutFragmentFrame
(0.0, 41.400000000000006, 4.998046875, 13.8)
layoutFragmentFrame
(0.0, 55.2, 3573.603515625, 13.8)
layoutFragmentFrame
(0.0, 69.0, 4.998046875, 13.8)
layoutFragmentFrame
(0.0, 82.8, 3098.7890625, 13.8)
layoutFragmentFrame
(0.0, 96.6, 4.998046875, 13.8)
layoutFragmentFrame
(5.0, 110.39999999999999, 2343.287109375, 13.8)
textLineFragments
(0.0, 0.0, 2343.287109375, 13.8)
蔀
  • 文字列→矩形(CGRect)が取れるので、色々できることがありそう
  • WWDCのセッションによると、パフォーマンスも向上する?とのこと
  • ただ全体的に扱いづらい
  • 所々挙動が信頼できない
    • textLineFragments が全部出てない
    • 矩形の値これであってるのか
蔀

縦書き、普通にNSAttributedStringに指定すれば動くのでは?と思ったけど、これだと横書きのままだった

textView.attributedText = NSAttributedString(string: textView.text, attributes: [
    .font: Font.body.font(),
    .foregroundColor: Palette.Text.primary,
    .verticalGlyphForm: true
])
このスクラップは2023/10/06にクローズされました