Skip to content

Commit

Permalink
Merge pull request #1523 from wordpress-mobile/issue/interact-with-ur…
Browse files Browse the repository at this point in the history
…l-crash

[Aztec iOS]: `shouldInteractWithURL` will return always false to avoid crashes
  • Loading branch information
etoledom authored Nov 4, 2019
2 parents 5c1d5a2 + d5bd76f commit 87bfbab
Showing 1 changed file with 50 additions and 62 deletions.
112 changes: 50 additions & 62 deletions react-native-aztec/ios/RNTAztecView/RCTAztecView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,23 +93,23 @@ class RCTAztecView: Aztec.TextView {
/// This helps to avoid propagating that unwanted empty string to RN. (Solving #606)
/// on `textViewDidChange` and `textViewDidChangeSelection`
private var isInsertingDictationResult = false

// MARK: - Font

/// Font family for all contents Once this is set, it will always override the font family for all of its
/// contents, regardless of what HTML is provided to Aztec.
private var fontFamily: String? = nil

/// Font size for all contents. Once this is set, it will always override the font size for all of its
/// contents, regardless of what HTML is provided to Aztec.
private var fontSize: CGFloat? = nil

/// Font weight for all contents. Once this is set, it will always override the font weight for all of its
/// contents, regardless of what HTML is provided to Aztec.
private var fontWeight: String? = nil

// MARK: - Formats

private let formatStringMap: [FormattingIdentifier: String] = [
.bold: "bold",
.italic: "italic",
Expand All @@ -132,7 +132,7 @@ class RCTAztecView: Aztec.TextView {
textContainerInset = .zero
contentInset = .zero
addPlaceholder()
textDragInteraction?.isEnabled = false
textDragInteraction?.isEnabled = false
storage.htmlConverter.characterToReplaceLastEmptyLine = Character(.zeroWidthSpace)
shouldNotifyOfNonUserChanges = false
}
Expand Down Expand Up @@ -165,18 +165,18 @@ class RCTAztecView: Aztec.TextView {

func updateContentSizeInRN() {
let newSize = sizeThatFits(frame.size)

guard previousContentSize != newSize,
let onContentSizeChange = onContentSizeChange else {
return
}

previousContentSize = newSize

let body = packForRN(newSize, withName: "contentSize")
onContentSizeChange(body)
}

// MARK: - Paste handling
private func read(from pasteboard: UIPasteboard, uti: CFString, documentType: DocumentType) -> String? {
guard let data = pasteboard.data(forPasteboardType: uti as String),
Expand Down Expand Up @@ -210,7 +210,7 @@ class RCTAztecView: Aztec.TextView {

return nil
}

private func readText(from pasteboard: UIPasteboard) -> String? {
var text = pasteboard.string
// Text that comes from Aztec will have paragraphSeparator instead of line feed AKA as \n. The paste methods in GB are expecting \n so this line will fix that.
Expand Down Expand Up @@ -241,7 +241,7 @@ class RCTAztecView: Aztec.TextView {
let imagesURLs = images.compactMap({ saveToDisk(image: $0)?.absoluteString })
return imagesURLs
}

override func paste(_ sender: Any?) {
let pasteboard = UIPasteboard.general
let text = readText(from: pasteboard) ?? ""
Expand All @@ -268,9 +268,9 @@ class RCTAztecView: Aztec.TextView {
"pastedHtml": html,
"files": imagesURLs] )
}

// MARK: - Edits

open override func insertText(_ text: String) {
guard !interceptEnter(text) else {
return
Expand All @@ -284,7 +284,7 @@ class RCTAztecView: Aztec.TextView {
guard !interceptBackspace() else {
return
}

super.deleteBackward()
updatePlaceholderVisibility()
}
Expand All @@ -302,7 +302,7 @@ class RCTAztecView: Aztec.TextView {
}

// MARK: - Custom Edit Intercepts

private func interceptEnter(_ text: String) -> Bool {
if text == "\t" {
return true
Expand All @@ -317,13 +317,13 @@ class RCTAztecView: Aztec.TextView {
onEnter(caretData)
return true
}

private func interceptBackspace() -> Bool {
guard (isNewLineBeforeSelectionAndNotEndOfContent() && selectedRange.length == 0) || (selectedRange.location == 0 && selectedRange.length == 0),
let onBackspace = onBackspace else {
return false
}

let caretData = packCaretDataForRN()
onBackspace(caretData)
return true
Expand All @@ -347,32 +347,32 @@ class RCTAztecView: Aztec.TextView {
let html = getHTML(prettify: false).replacingOccurrences(of: String(.paragraphSeparator), with: String(.lineFeed)).replacingOccurrences(of: String(.zeroWidthSpace), with: "")
return html
}

func packForRN(_ text: String, withName name: String) -> [AnyHashable: Any] {
return [name: text,
"eventCount": 1]
}

func packForRN(_ size: CGSize, withName name: String) -> [AnyHashable: Any] {

let size = ["width": size.width,
"height": size.height]

return [name: size]
}

func packCaretDataForRN() -> [AnyHashable: Any] {
var start = selectedRange.location
var end = selectedRange.location + selectedRange.length
if selectionAffinity == .backward {
(start, end) = (end, start)
}

var result: [AnyHashable : Any] = packForRN(cleanHTML(), withName: "text")

result["selectionStart"] = start
result["selectionEnd"] = end

if let selectedTextRange = selectedTextRange {
let caretEndRect = caretRect(for: selectedTextRange.end)
// Sergio Estevao: Sometimes the carectRect can be invalid so we need to check before sending this to JS.
Expand All @@ -386,7 +386,7 @@ class RCTAztecView: Aztec.TextView {
}

// MARK: - RN Properties

@objc
func setContents(_ contents: NSDictionary) {
guard contents["eventCount"] == nil else {
Expand Down Expand Up @@ -461,97 +461,97 @@ class RCTAztecView: Aztec.TextView {
selectedTextRange = textRange(from: startPosition, to: endPosition)
}
}

func updatePlaceholderVisibility() {
placeholderLabel.isHidden = !self.text.replacingOccurrences(of: String(.zeroWidthSpace), with: "").isEmpty
}

// MARK: - Font Setters

@objc func setFontFamily(_ family: String) {
fontFamily = family
refreshFont()
}

@objc func setFontSize(_ size: CGFloat) {
fontSize = size
refreshFont()
}

@objc func setFontWeight(_ weight: String) {
fontWeight = weight
refreshFont()
}

// MARK: - Font Refreshing

/// Applies the family, size and weight constraints to the provided font.
///
private func applyFontConstraints(to baseFont: UIFont) -> UIFont {
let oldDescriptor = baseFont.fontDescriptor
let newFontSize: CGFloat

if let fontSize = fontSize {
newFontSize = fontSize
} else {
newFontSize = baseFont.pointSize
}

var newTraits = oldDescriptor.symbolicTraits

if let fontWeight = fontWeight {
if (fontWeight == "bold") {
newTraits.update(with: .traitBold)
}
}

var newDescriptor: UIFontDescriptor

if let fontFamily = fontFamily {
newDescriptor = UIFontDescriptor(name: fontFamily, size: newFontSize)
newDescriptor = newDescriptor.withSymbolicTraits(newTraits) ?? newDescriptor
} else {
newDescriptor = oldDescriptor
}

return UIFont(descriptor: newDescriptor, size: newFontSize)
}

/// Returns the font from the specified attributes, or the default font if no specific one is set.
///
private func font(from attributes: [NSAttributedString.Key: Any]) -> UIFont {
return attributes[.font] as? UIFont ?? defaultFont
}

/// This method refreshes the font for the whole view if the font-family, the font-size or the font-weight
/// were ever set.
///
private func refreshFont() {
guard fontFamily != nil || fontSize != nil || fontWeight != nil else {
return
}

let fullRange = NSRange(location: 0, length: textStorage.length)

textStorage.beginEditing()
textStorage.enumerateAttributes(in: fullRange, options: []) { (attributes, subrange, stop) in
let oldFont = font(from: attributes)
let newFont = applyFontConstraints(to: oldFont)

textStorage.addAttribute(.font, value: newFont, range: subrange)
}
textStorage.endEditing()

refreshTypingAttributesAndPlaceholderFont()
}

/// This method refreshes the font for the palceholder field and typing attributes.
/// This method should not be called directly. Call `refreshFont()` instead.
///
private func refreshTypingAttributesAndPlaceholderFont() {
let oldFont = font(from: typingAttributes)
let newFont = applyFontConstraints(to: oldFont)

typingAttributes[.font] = newFont
placeholderLabel.font = newFont
}
Expand All @@ -573,9 +573,9 @@ class RCTAztecView: Aztec.TextView {
formatHandler.forceTypingFormat(on: self)
}
}

// MARK: - Event Propagation

func propagateContentChanges() {
if let onChange = onChange {
let text = packForRN(cleanHTML(), withName: "text")
Expand All @@ -595,7 +595,7 @@ class RCTAztecView: Aztec.TextView {
// MARK: UITextView Delegate Methods
extension RCTAztecView: UITextViewDelegate {

func textViewDidChangeSelection(_ textView: UITextView) {
func textViewDidChangeSelection(_ textView: UITextView) {
guard isFirstResponder, isInsertingDictationResult == false else {
return
}
Expand Down Expand Up @@ -627,18 +627,6 @@ extension RCTAztecView: UITextViewDelegate {
}

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if #available(iOS 13.1, *) {
return false
} else if #available(iOS 13.0.0, *) {
// Sergio Estevao: This shouldn't happen in an editable textView, but it looks we have a system bug in iOS13 so we need this workaround
let position = characterRange.location
textView.selectedRange = NSRange(location: position, length: 0)
textView.typingAttributes = textView.attributedText.attributes(at: position, effectiveRange: nil)
textView.delegate?.textViewDidChangeSelection?(textView)
} else {
return false
}

return false
}
}

0 comments on commit 87bfbab

Please sign in to comment.