diff options
Diffstat (limited to 'modules/react-native-ui-text-view/ios/RNUITextView.swift')
-rw-r--r-- | modules/react-native-ui-text-view/ios/RNUITextView.swift | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/modules/react-native-ui-text-view/ios/RNUITextView.swift b/modules/react-native-ui-text-view/ios/RNUITextView.swift new file mode 100644 index 000000000..9c21d45b5 --- /dev/null +++ b/modules/react-native-ui-text-view/ios/RNUITextView.swift @@ -0,0 +1,141 @@ +class RNUITextView: UIView { + var textView: UITextView + + @objc var numberOfLines: Int = 0 { + didSet { + textView.textContainer.maximumNumberOfLines = numberOfLines + } + } + @objc var selectable: Bool = true { + didSet { + textView.isSelectable = selectable + } + } + @objc var ellipsizeMode: String = "tail" { + didSet { + textView.textContainer.lineBreakMode = self.getLineBreakMode() + } + } + @objc var onTextLayout: RCTDirectEventBlock? + + override init(frame: CGRect) { + if #available(iOS 16.0, *) { + textView = UITextView(usingTextLayoutManager: false) + } else { + textView = UITextView() + } + + // Disable scrolling + textView.isScrollEnabled = false + // Remove all the padding + textView.textContainerInset = .zero + textView.textContainer.lineFragmentPadding = 0 + + // Remove other properties + textView.isEditable = false + textView.backgroundColor = .clear + + // Init + super.init(frame: frame) + self.clipsToBounds = true + + // Add the view + addSubview(textView) + + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(callOnPress(_:))) + tapGestureRecognizer.isEnabled = true + textView.addGestureRecognizer(tapGestureRecognizer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // Resolves some animation issues + override func reactSetFrame(_ frame: CGRect) { + UIView.performWithoutAnimation { + super.reactSetFrame(frame) + } + } + + func setText(string: NSAttributedString, size: CGSize, numberOfLines: Int) -> Void { + self.textView.frame.size = size + self.textView.textContainer.maximumNumberOfLines = numberOfLines + self.textView.attributedText = string + self.textView.selectedTextRange = nil + + if let onTextLayout = self.onTextLayout { + var lines: [String] = [] + textView.layoutManager.enumerateLineFragments( + forGlyphRange: NSRange(location: 0, length: textView.attributedText.length)) + { (rect, usedRect, textContainer, glyphRange, stop) in + let characterRange = self.textView.layoutManager.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil) + let line = (self.textView.text as NSString).substring(with: characterRange) + lines.append(line) + } + + onTextLayout([ + "lines": lines + ]) + } + } + + @IBAction func callOnPress(_ sender: UITapGestureRecognizer) -> Void { + // If we find a child, then call onPress + if let child = getPressed(sender) { + if textView.selectedTextRange == nil, let onPress = child.onPress { + onPress(["": ""]) + } else { + // Clear the selected text range if we are not pressing on a link + textView.selectedTextRange = nil + } + } + } + + // Try to get the pressed segment + func getPressed(_ sender: UITapGestureRecognizer) -> RNUITextViewChild? { + let layoutManager = textView.layoutManager + var location = sender.location(in: textView) + + // Remove the padding + location.x -= textView.textContainerInset.left + location.y -= textView.textContainerInset.top + + // Get the index of the char + let charIndex = layoutManager.characterIndex( + for: location, + in: textView.textContainer, + fractionOfDistanceBetweenInsertionPoints: nil + ) + + for child in self.reactSubviews() { + if let child = child as? RNUITextViewChild, let childText = child.text { + let fullText = self.textView.attributedText.string + let range = fullText.range(of: childText) + + if let lowerBound = range?.lowerBound, let upperBound = range?.upperBound { + if charIndex >= lowerBound.utf16Offset(in: fullText) && charIndex <= upperBound.utf16Offset(in: fullText) { + return child + } + } + } + } + + return nil + } + + func getLineBreakMode() -> NSLineBreakMode { + switch self.ellipsizeMode { + case "head": + return .byTruncatingHead + case "middle": + return .byTruncatingMiddle + case "tail": + return .byTruncatingTail + case "clip": + return .byClipping + default: + return .byTruncatingTail + } + } +} |