Skip to content

Instantly share code, notes, and snippets.

@jfuellert
Last active August 8, 2022 04:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jfuellert/764cc16b2d19b5c6f696d7acd894d3f2 to your computer and use it in GitHub Desktop.
Save jfuellert/764cc16b2d19b5c6f696d7acd894d3f2 to your computer and use it in GitHub Desktop.
SwiftUI HTML string parsing Text component
struct DynamicAttributedTextView: UIViewRepresentable {
// MARK: - Constants
private static let attributes: [NSAttributedString.Key : Any] = [.font: CustomFont().UIKitFont()!,
.foregroundColor: UIColor.appPrimaryText,
.paragraphStyle: DynamicAttributedTextView.paragraphStyle]
private static let types: NSTextCheckingResult.CheckingType = [.link, .address, .phoneNumber]
private static let paragraphStyle: NSParagraphStyle = {
let paragraphStyle: NSMutableParagraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 4
return paragraphStyle
}()
// MARK: - Properties
private var text: String
private let width: CGFloat
@Binding private var height: CGFloat
// MARK: - Coordinator
class Coordinator: NSObject, UITextViewDelegate {
// MARK: - Properties
let rep: DynamicAttributedTextView
// MARK: - Init
init(_ rep: DynamicAttributedTextView) {
self.rep = rep
}
// MARK: - Updates
func textViewDidChange(_ textView: UITextView) {
textView.delegate = self
}
func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return true
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if interaction != .preview {
DeepLinkRouter.open(URL)
return false
}
return true
}
}
// MARK: - Init
init(_ text: String, width: CGFloat, height: Binding<CGFloat>) {
self.width = width
self._height = height
self.text = text
}
// MARK: - Updates
func makeUIView(context: Context) -> UITextView {
let view: UITextView = UITextView()
view.backgroundColor = .clear
view.delegate = context.coordinator
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.dataDetectorTypes = [.link, .address, .phoneNumber, .shipmentTrackingNumber]
view.showsVerticalScrollIndicator = false
view.isEditable = false
view.isSelectable = true
view.isUserInteractionEnabled = false
view.tintColor = .black
view.textColor = .black
view.linkTextAttributes = [.underlineStyle: NSUnderlineStyle.single.rawValue,
.foregroundColor: UIColor.appPrimaryText]
view.removeGestureRecognizer(view.panGestureRecognizer)
if UIDevice.current.userInterfaceIdiom == .pad {
DispatchQueue.main.async {
view.attributedText = self.text.HTMLAttributedString(DynamicAttributedTextView.attributes)
}
} else {
view.attributedText = self.text.HTMLAttributedString(DynamicAttributedTextView.attributes)
}
let size: CGSize = view.systemLayoutSizeFitting(.init(width: self.width, height: .infinity))
view.contentSize = size
view.bounds = .init(origin: .zero, size: size)
DispatchQueue.main.async {
self.height = size.height
}
return view
}
func updateUIView(_ view: UITextView, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
extension String {
func HTMLAttributedString(_ attributes: [NSAttributedString.Key : Any]? = nil) -> NSAttributedString {
guard let data = self.replacingOccurrences(of: "\n", with: "<br>").data(using: .utf8) else {
return NSAttributedString(string: self, attributes: attributes)
}
if let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) {
if let attributes = attributes {
attributedString.addAttributes(attributes, range: NSRange(location: 0, length: attributedString.length))
}
return attributedString
}
return NSAttributedString(string: self, attributes: attributes)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment