// // Copyright 2023 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import SwiftUI import UIKit final class MessageTextView: UITextView { // This prevents the magnifying glass from showing up override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { gestureRecognizer as? UILongPressGestureRecognizer == nil } } struct MessageText: UIViewRepresentable { @Environment(\.openURL) private var openURLAction: OpenURLAction let attributedString: AttributedString func makeUIView(context: Context) -> MessageTextView { let textView = MessageTextView() textView.isEditable = false textView.isScrollEnabled = false textView.adjustsFontForContentSizeCategory = true // Required to allow tapping links // We disable selection at delegate level textView.isSelectable = true textView.isUserInteractionEnabled = true // Otherwise links can be dragged and dropped when long pressed textView.textDragInteraction?.isEnabled = false textView.contentInset = .zero textView.contentInsetAdjustmentBehavior = .never textView.textContainerInset = .zero textView.textContainer.lineFragmentPadding = 0 textView.layoutManager.usesFontLeading = false textView.backgroundColor = .clear textView.attributedText = NSAttributedString(attributedString) textView.delegate = context.coordinator return textView } func updateUIView(_ uiView: MessageTextView, context: Context) { uiView.attributedText = NSAttributedString(attributedString) context.coordinator.openURLAction = openURLAction } func sizeThatFits(_ proposal: ProposedViewSize, uiView: MessageTextView, context: Context) -> CGSize? { uiView.sizeThatFits(CGSize(width: proposal.width ?? UIView.layoutFittingExpandedSize.width, height: UIView.layoutFittingCompressedSize.height)) } func makeCoordinator() -> Coordinator { Coordinator(openURLAction: openURLAction) } final class Coordinator: NSObject, UITextViewDelegate { var openURLAction: OpenURLAction init(openURLAction: OpenURLAction) { self.openURLAction = openURLAction } func textViewDidChangeSelection(_ textView: UITextView) { textView.selectedTextRange = nil } func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { if interaction == .invokeDefaultAction { openURLAction.callAsFunction(URL) } return false } } } // MARK: - Previews struct MessageText_Previews: PreviewProvider { private static let defaultFontContainer: AttributeContainer = { var container = AttributeContainer() container.font = UIFont.preferredFont(forTextStyle: .body) return container }() private static let attributedString = AttributedString("Hello World! Hello world! Hello world! Hello world! Hello World! Hellooooooooooooooooooooooo Woooooooooooooooooooooorld", attributes: defaultFontContainer) private static let attributedStringWithAttachment = "Hello " + AttributedString(NSAttributedString(attachment: PillTextAttachment(data: Data(), ofType: InfoPlistReader.main.pillsUTType))) + " World!" private static let htmlStringWithQuote = """
A blockquote that is long and goes onto multiple lines as the first item in the message

Then another line of text here to reply to the blockquote, which is also a multiline component.

""" private static let htmlStringWithList = "

This is a list

\n\n" private static let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL) static var previews: some View { MessageText(attributedString: attributedString) .border(Color.purple) .previewDisplayName("Custom Text") // For comparison Text(attributedString) .border(Color.purple) .previewDisplayName("SwiftUI Default Text") MessageText(attributedString: attributedStringWithAttachment) .border(Color.purple) .previewDisplayName("Custom Attachment") if let attributedString = attributedStringBuilder.fromHTML(htmlStringWithQuote) { MessageText(attributedString: attributedString) .border(Color.purple) .previewDisplayName("With block quote") } if let attributedString = attributedStringBuilder.fromHTML(htmlStringWithList) { MessageText(attributedString: attributedString) .border(Color.purple) .previewDisplayName("With list") } } }