From 8b3f1ba30ddaa7cdf5ef700cfe40fb9ebe759d97 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Fri, 17 Aug 2018 23:09:59 -0400 Subject: [PATCH] Initial pass at message content rendering --- Tusker/Views/StatusTableViewCell.swift | 94 ++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/Tusker/Views/StatusTableViewCell.swift b/Tusker/Views/StatusTableViewCell.swift index 640090c5..f754b915 100644 --- a/Tusker/Views/StatusTableViewCell.swift +++ b/Tusker/Views/StatusTableViewCell.swift @@ -16,14 +16,100 @@ class StatusTableViewCell: UITableViewCell { @IBOutlet weak var usernameLabel: UILabel! @IBOutlet weak var contentLabel: UILabel! + var status: Status! + + var layoutManager: NSLayoutManager! + var textContainer: NSTextContainer! + var textStorage: NSTextStorage! + + var links: [NSRange: URL] = [:] + func updateUI(for status: Status) { - displayNameLabel.text = status.account.displayName - usernameLabel.text = "@\(status.account.acct)" + self.status = status + + let account: Account + if let reblog = status.reblog { + account = reblog.account + } else { + account = status.account + } + displayNameLabel.text = account.displayName + usernameLabel.text = "@\(account.acct)" let doc = try! SwiftSoup.parse(status.content) - let text = (try! doc.body()?.text())! + let body = doc.body()! + print("---") + print(status.content) + print("---") - contentLabel.text = text + let text = attributedTextForNode(body) + contentLabel.attributedText = text + contentLabel.isUserInteractionEnabled = true + contentLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapOnContentLabel(_:)))) + + // https://stackoverflow.com/a/28519273/4731558 + layoutManager = NSLayoutManager() + textContainer = NSTextContainer(size: .zero) + textStorage = NSTextStorage(attributedString: text) + + layoutManager.addTextContainer(textContainer) + textStorage.addLayoutManager(layoutManager) + + textContainer.lineFragmentPadding = 0 + textContainer.lineBreakMode = contentLabel.lineBreakMode + textContainer.maximumNumberOfLines = contentLabel.numberOfLines + } + + func attributedTextForNode(_ node: Node) -> NSAttributedString { + switch node { + case let node as TextNode: + return NSAttributedString(string: node.text()) + case let node as Element: + let attributed = NSMutableAttributedString() + node.getChildNodes().forEach { child in + attributed.append(attributedTextForNode(child)) + } + + switch node.tagName() { + case "br": + attributed.append(NSAttributedString(string: "\n")) + case "a": + let linkRange = NSRange(location: 0, length: attributed.length) + let linkAttributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: UIColor.blue + ] + attributed.setAttributes(linkAttributes, range: linkRange) + let link = try! node.attr("href") + links[linkRange] = URL(string: link)! + default: + break + } + + return attributed + default: + fatalError("Unexpected node type: \(type(of: node))") + } + } + + @objc func handleTapOnContentLabel(_ tapGesture: UITapGestureRecognizer) { + guard let view = tapGesture.view else { fatalError() } + + let locationOfTouchInLabel = tapGesture.location(in: view) + let labelSize = view.bounds.size + let textBoundingBox = layoutManager.usedRect(for: textContainer) + let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, + y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y) + let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, + y: locationOfTouchInLabel.y - textContainerOffset.y) + let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) + + let link = links.first { (range, _) -> Bool in + range.contains(indexOfCharacter) + } + + if let (_, url) = link { + print("Open URL: \(url)") + } } }