RichTextView.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. //
  2. // RichTextView.swift
  3. // RichTextView
  4. //
  5. // Created by Ahmed Elkady on 2018-11-08.
  6. // Copyright © 2018 Top Hat. All rights reserved.
  7. //
  8. import SnapKit
  9. import WebKit
  10. public class RichTextView: UIView {
  11. // MARK: - Properties
  12. public private(set) var input: String
  13. private(set) var richTextParser: RichTextParser
  14. private(set) var textColor: UIColor
  15. private(set) var isSelectable: Bool
  16. private(set) var isEditable: Bool
  17. public weak var textViewDelegate: RichTextViewDelegate?
  18. private(set) var errors: [ParsingError]?
  19. // MARK: - Constants
  20. private enum VideoProperties {
  21. static let defaultAspectRatio = 9.0/16.0
  22. }
  23. // MARK: - Init
  24. public init(input: String = "",
  25. latexParser: LatexParserProtocol = LatexParser(),
  26. font: UIFont = UIFont.systemFont(ofSize: UIFont.systemFontSize),
  27. textColor: UIColor = UIColor.black,
  28. isSelectable: Bool = true,
  29. isEditable: Bool = false,
  30. latexTextBaselineOffset: CGFloat = 0,
  31. interactiveTextColor: UIColor = UIColor.blue,
  32. textViewDelegate: RichTextViewDelegate? = nil,
  33. customAdditionalAttributes: [String: [NSAttributedString.Key: Any]]? = nil,
  34. frame: CGRect,
  35. completion: (([ParsingError]?) -> Void)? = nil) {
  36. self.input = input
  37. self.isSelectable = isSelectable
  38. self.isEditable = isEditable
  39. self.richTextParser = RichTextParser(
  40. latexParser: latexParser,
  41. font: font,
  42. textColor: textColor,
  43. latexTextBaselineOffset: latexTextBaselineOffset,
  44. interactiveTextColor: interactiveTextColor,
  45. customAdditionalAttributes: customAdditionalAttributes
  46. )
  47. self.textColor = textColor
  48. self.textViewDelegate = textViewDelegate
  49. super.init(frame: frame)
  50. self.setupSubviews()
  51. completion?(self.errors)
  52. }
  53. required init?(coder aDecoder: NSCoder) {
  54. self.input = ""
  55. self.richTextParser = RichTextParser()
  56. self.textColor = UIColor.black
  57. self.isSelectable = true
  58. self.isEditable = false
  59. super.init(coder: aDecoder)
  60. self.setupSubviews()
  61. }
  62. // MARK: - Helpers
  63. public func update(input: String? = nil,
  64. latexParser: LatexParserProtocol? = nil,
  65. font: UIFont? = nil,
  66. textColor: UIColor? = nil,
  67. latexTextBaselineOffset: CGFloat? = nil,
  68. interactiveTextColor: UIColor? = nil,
  69. customAdditionalAttributes: [String: [NSAttributedString.Key: Any]]? = nil,
  70. completion: (([ParsingError]?) -> Void)? = nil) {
  71. self.input = input ?? self.input
  72. self.richTextParser = RichTextParser(
  73. latexParser: latexParser ?? self.richTextParser.latexParser,
  74. font: font ?? self.richTextParser.font,
  75. textColor: textColor ?? self.textColor,
  76. latexTextBaselineOffset: latexTextBaselineOffset ?? self.richTextParser.latexTextBaselineOffset,
  77. interactiveTextColor: interactiveTextColor ?? self.richTextParser.interactiveTextColor,
  78. customAdditionalAttributes: customAdditionalAttributes ?? self.richTextParser.customAdditionalAttributes
  79. )
  80. self.textColor = textColor ?? self.textColor
  81. self.subviews.forEach { $0.removeFromSuperview() }
  82. self.setupSubviews()
  83. completion?(self.errors)
  84. }
  85. private func setupSubviews() {
  86. let subviews = self.generateViews()
  87. for (index, subview) in subviews.enumerated() {
  88. self.addSubview(subview)
  89. subview.snp.makeConstraints { make in
  90. if index == 0 {
  91. make.top.equalTo(self)
  92. } else {
  93. make.top.equalTo(subviews[index - 1].snp.bottom)
  94. }
  95. make.width.equalTo(self)
  96. make.centerX.equalTo(self)
  97. if subview is WKWebView {
  98. make.height.equalTo(self.snp.width).multipliedBy(VideoProperties.defaultAspectRatio)
  99. }
  100. if index == subviews.count - 1 {
  101. make.bottom.equalTo(self)
  102. }
  103. }
  104. subview.backgroundColor = UIColor.clear
  105. }
  106. self.enableAccessibility()
  107. }
  108. func generateViews() -> [UIView] {
  109. return self.richTextParser.getRichDataTypes(from: self.input).compactMap { (richDataType: RichDataType) -> UIView? in
  110. switch richDataType {
  111. case .video(let tag, let error):
  112. self.appendError(error)
  113. let webView = WKWebViewGenerator.getWebView(from: tag)
  114. if webView == nil {
  115. self.appendError(ParsingError.webViewGeneration(link: tag))
  116. }
  117. return webView
  118. case .text(let richText, let font, let errors):
  119. self.appendErrors(errors)
  120. return UITextViewGenerator.getTextView(
  121. from: richText,
  122. font: font,
  123. textColor: self.textColor,
  124. isSelectable: self.isSelectable,
  125. isEditable: self.isEditable,
  126. textViewDelegate: self.textViewDelegate
  127. )
  128. }
  129. }
  130. }
  131. private func appendErrors(_ errors: [ParsingError]?) {
  132. if let errors = errors {
  133. if self.errors == nil {
  134. self.errors = [ParsingError]()
  135. }
  136. self.errors?.append(contentsOf: errors)
  137. }
  138. }
  139. private func appendError(_ error: ParsingError?) {
  140. if let error = error {
  141. if self.errors == nil {
  142. self.errors = [ParsingError]()
  143. }
  144. self.errors?.append(error)
  145. }
  146. }
  147. private func enableAccessibility() {
  148. self.isAccessibilityElement = true
  149. self.accessibilityValue = nil
  150. for view in self.subviews {
  151. if let accessibilityValue = view.accessibilityValue, !accessibilityValue.isEmpty {
  152. if self.accessibilityValue == nil {
  153. self.accessibilityValue = accessibilityValue
  154. } else {
  155. self.accessibilityValue?.append(accessibilityValue)
  156. }
  157. }
  158. }
  159. }
  160. }