Closed13
TextKit 2研究
このセッションを受けて、なんか使えないかなと
TextKit 1 vs TextKit 2
- TextKit 1は
NSLayoutManager
を中心とした古いAPI- グリフ(文字要素)が操作の最小単位
- TextKit 2は
NSTextLayoutManager
を中心としたWWDC21で発表されたAPI- 操作の最小単位は行。グリフは隠蔽されるようになった
- 行=
NSTextLineFragment
-
NSTextContentStorage
->NSTextLayoutFragment
->NSTextLineFragment
の順でアクセスすることになる - https://developer.apple.com/videos/play/wwdc2021/10061/
縦書きレイアウトに応用した例
- 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 が全部出てない
- 矩形の値これであってるのか
縦書きに使えるのはいいかと思ったんだけど、.verticalGlyphForm
が思いっきりdeprecatedになったてたんだよな
縦書き、普通にNSAttributedString
に指定すれば動くのでは?と思ったけど、これだと横書きのままだった
textView.attributedText = NSAttributedString(string: textView.text, attributes: [
.font: Font.body.font(),
.foregroundColor: Palette.Text.primary,
.verticalGlyphForm: true
])
こっちのサンプルが良さそう
このスクラップは2023/10/06にクローズされました