-
Eleve a experiência de texto do seu app com o TextKit
Descubra como combinar a conveniência das visualizações de texto integradas com o controle do TextKit. Vamos mostrar como as novas APIs facilitam a extensão do UITextView e do NSTextView com comportamentos personalizados, como números de linha e seções recolhíveis. Também vamos explorar a arquitetura do TextKit e analisar as novas políticas de armazenamento em cache e reutilização para anexos de texto. Para aproveitar ao máximo esta sessão, assista às sessões "Conheça o TextKit 2" da WWDC21 e "Novidades no TextKit e nas visualizações de texto" da WWDC22.
Capítulos
- 0:00 - Introdução
- 3:09 - Arquitetura do TextKit
- 9:17 - Novidades do TextKit
- 11:27 - Estendendo as visualizações de texto do framework
- 12:58 - Exemplo: Editor de código com números de linha
- 17:52 - Exemplo: Seções de receitas colapsáveis
- 19:56 - Anexos de texto e reutilização do provedor de visualização
- 23:00 - Próximas etapas
Recursos
Vídeos relacionados
WWDC26
WWDC22
WWDC21
-
Buscar neste vídeo...
-
-
9:47 - NSTextViewportRenderingSurface conformance
class MyView: UIView, NSTextViewportRenderingSurface {} -
10:25 - NSTextViewportRenderingSurfaceKey and NSMapTable
class MyView: UIView, NSTextViewportRenderingSurface {} var cache: NSMapTable<NSTextLayoutFragment, MyView> -
12:39 - UITextView/NSTextView in SwiftUI via ViewRepresentable
// Using a TextView in SwiftUI import SwiftUI struct MyTextView: View { var body: some View { TextViewRepresentable() } } #if os(macOS) struct TextViewRepresentable: NSViewRepresentable { func makeNSView(context: Context) -> NSTextView { NSTextView() } func updateNSView(_ nsView: NSTextView, context: Context) { } } #else struct TextViewRepresentable: UIViewRepresentable { func makeUIView(context: Context) -> UITextView { UITextView() } func updateUIView(_ uiView: UITextView, context: Context) { } } #endif -
13:33 - ContainerView with TextView and line number view
// Create a text view subclass for a code editor import UIKit class TextView: UITextView {} class ContainerView: UIView { let textView = TextView() let lineNumberView = UIView() textView.font = UIFont.monospacedSystemFont } -
14:42 - Three NSTextViewportLayoutControllerDelegate overrides
// Override viewport controller delegate methods class TextView: UITextView { // Set up override func textViewportLayoutControllerWillLayout(_ textViewportLayoutController: NSTextViewportLayoutController) { super.textViewportLayoutControllerWillLayout(textViewportLayoutController) //... } // Get paragraph bounds override func textViewportLayoutController (_ textViewportLayoutController: NSTextViewportLayoutController, configureRenderingSurfaceFor textLayoutFragment: NSTextLayoutFragment) { super.textViewportLayoutController(textViewportLayoutController, configureRenderingSurfaceFor: textLayoutFragment) //... } // Share accumulated info back to ContainerView override func textViewportLayoutControllerDidLayout (_ textViewportLayoutController: NSTextViewportLayoutController) { super.textViewportLayoutControllerDidLayout(textViewportLayoutController) //... } } -
15:59 - startingLineNumber(for:) using enumerateTextElements
func startingLineNumber(for viewportRange: NSTextRange?) -> Int { guard let viewportRange, let storage = textLayoutManager?.textContentManager as? NSTextContentStorage else { return 0 } let startLocation = storage.documentRange.location var count = 1 storage.enumerateTextElements(from: startLocation) { element in guard let range = element.elementRange else { return true } if range.location.compare(viewportRange.location) != .orderedAscending { return false } count += 1 return true } return count } -
17:02 - DidLayout: convert frames to viewport coordinates
// Override viewport controller delegate methods class TextView: UITextView { private var lines: [CGRect] = [] private var startingLineNumber = 0 var onDidLayout: ((Int, [CGRect]) -> Void)? // Share accumulated info back to ContainerView override func textViewportLayoutControllerDidLayout (_ textViewportLayoutController: NSTextViewportLayoutController) { super.textViewportLayoutControllerDidLayout(controller) let origin = controller.viewportBounds.origin onDidLayout?(startingLineNumber, lines.map {$0.offsetBy(dx: 0, dy: -origin.y) }) } } -
17:16 - Draw line numbers in ContainerView closure
// Draw line numbers in the ContainerView class ContainerView: UIView { let textView = TextView() let lineNumberView = UIView() func setup() { textView.onDidLayout = {startingLineNumber, lines in let attributes: [NSAttributedString.Key: Any] = [ .font: UIFont.monospacedSystemFont(ofSize: 11, weight: .regular), .foregroundColor: UIColor.secondaryLabel ] for (i, frame) in lines.enumerated() { let number = "\(startingLineNumber + i)" as NSString number.draw(at: CGPoint(x: 8, y: frame.minY), withAttributes: attributes) } } } } -
19:22 - Collapsible sections: full TextView class
// Add collapsible sections to your text view class TextView: UITextView, NSTextContentStorageDelegate { var collapsedSections: Set<Int> = [] // Set up override func textViewportLayoutControllerWillLayout(_ textViewportLayoutController: NSTextViewportLayoutController) { super.textViewportLayoutControllerWillLayout(textViewportLayoutController) //... } // Get paragraph bounds override func textViewportLayoutController (_ textViewportLayoutController: NSTextViewportLayoutController, configureRenderingSurfaceFor textLayoutFragment: NSTextLayoutFragment) { super.textViewportLayoutController(textViewportLayoutController, configureRenderingSurfaceFor: textLayoutFragment) //... } // Share accumulated info back to ContainerView override func textViewportLayoutControllerDidLayout (_ textViewportLayoutController: NSTextViewportLayoutController) { super.textViewportLayoutControllerDidLayout(textViewportLayoutController) //... } // Skip layout for paragraphs marked as collapsed func textContentManager(shouldEnumerate textElement: NSTextElement, options: NSTextContentManager.EnumerationOptions) -> Bool { //... } // Handle section collapse toggling func toggleSection(headerOffset: Int) { if collapsedSections.contains(headerOffset) { collapsedSections.remove(headerOffset) } else { collapsedSections.insert(headerOffset) } guard let textLayoutManager = textLayoutManager else { return } let textViewportLayoutController = textLayoutManager.textViewportLayoutController textViewportLayoutController.delegate?.textViewportLayoutControllerReceivedSetNeedsLayout?(textViewportLayoutController) } } -
22:06 - Text attachment view provider reuse policy
// Cache text attachment view providers import UIKit class ViewController: UIViewController { var textView: UITextView func setupTextView() { textView = UITextView() textView.register( [.onEditingInlineParagraphs], forTextAttachmentViewProviderType: AnimatedAttachmentViewProvider.self ) } }
-