diff --git a/Vorleser-iOS/BookTextView.swift b/Vorleser-iOS/BookTextView.swift new file mode 100644 index 0000000..e7cf48c --- /dev/null +++ b/Vorleser-iOS/BookTextView.swift @@ -0,0 +1,75 @@ +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) + } + } + } +}