import SwiftUI import UIKit import VorleserKit struct BookTextView: UIViewRepresentable { let attributedText: NSAttributedString let highlightRange: Range? let onTapCharacter: (CharacterOffset) -> Void var scrollToOffset: CharacterOffset? func makeUIView(context: Context) -> UITextView { let textView = UITextView() textView.isEditable = false textView.isSelectable = false textView.textContainerInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) let tap = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap(_:))) textView.addGestureRecognizer(tap) return textView } func updateUIView(_ textView: UITextView, context: Context) { let highlighted = NSMutableAttributedString(attributedString: attributedText) if let range = highlightRange, range.lowerBound >= 0, range.upperBound <= attributedText.length { let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound) highlighted.addAttribute(.backgroundColor, value: UIColor.systemYellow.withAlphaComponent(0.3), range: nsRange) } if textView.attributedText != highlighted { textView.attributedText = highlighted } if let range = highlightRange, range.lowerBound >= 0, range.upperBound <= attributedText.length { let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound) textView.scrollRangeToVisible(nsRange) } if let offset = scrollToOffset, offset >= 0, offset < attributedText.length { let nsRange = NSRange(location: offset, length: 1) textView.scrollRangeToVisible(nsRange) } } func makeCoordinator() -> Coordinator { Coordinator(onTapCharacter: onTapCharacter) } class Coordinator: NSObject { let onTapCharacter: (CharacterOffset) -> Void init(onTapCharacter: @escaping (CharacterOffset) -> Void) { self.onTapCharacter = onTapCharacter } @objc func handleTap(_ gesture: UITapGestureRecognizer) { guard let textView = gesture.view as? UITextView else { return } let point = gesture.location(in: textView) let characterIndex = textView.offset( from: textView.beginningOfDocument, to: textView.closestPosition(to: point) ?? textView.beginningOfDocument ) if characterIndex < textView.attributedText.length { onTapCharacter(characterIndex) } } } }