2018-08-17 02:39:16 +00:00
|
|
|
//
|
|
|
|
// StatusTableViewCell.swift
|
|
|
|
// Tusker
|
|
|
|
//
|
|
|
|
// Created by Shadowfacts on 8/16/18.
|
|
|
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
import MastodonKit
|
|
|
|
import SwiftSoup
|
|
|
|
|
|
|
|
class StatusTableViewCell: UITableViewCell {
|
|
|
|
|
|
|
|
@IBOutlet weak var displayNameLabel: UILabel!
|
|
|
|
@IBOutlet weak var usernameLabel: UILabel!
|
|
|
|
@IBOutlet weak var contentLabel: UILabel!
|
2018-08-21 23:23:27 +00:00
|
|
|
@IBOutlet weak var avatarImageView: UIImageView!
|
2018-08-17 02:39:16 +00:00
|
|
|
|
2018-08-18 03:09:59 +00:00
|
|
|
var status: Status!
|
|
|
|
|
2018-08-21 23:23:27 +00:00
|
|
|
var avatarURL: URL?
|
|
|
|
|
2018-08-18 03:09:59 +00:00
|
|
|
var layoutManager: NSLayoutManager!
|
|
|
|
var textContainer: NSTextContainer!
|
|
|
|
var textStorage: NSTextStorage!
|
|
|
|
|
|
|
|
var links: [NSRange: URL] = [:]
|
|
|
|
|
2018-08-17 02:39:16 +00:00
|
|
|
func updateUI(for status: Status) {
|
2018-08-18 03:09:59 +00:00
|
|
|
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)"
|
2018-08-21 23:23:27 +00:00
|
|
|
avatarImageView.layer.cornerRadius = 5
|
|
|
|
avatarImageView.layer.masksToBounds = true
|
|
|
|
avatarImageView.image = nil
|
|
|
|
if let url = URL(string: account.avatar) {
|
|
|
|
AvatarCache.shared.get(url) { image in
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.avatarImageView.image = image
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-17 02:39:16 +00:00
|
|
|
|
|
|
|
let doc = try! SwiftSoup.parse(status.content)
|
2018-08-18 03:09:59 +00:00
|
|
|
let body = doc.body()!
|
2018-08-18 20:07:34 +00:00
|
|
|
// print("---")
|
|
|
|
// print(status.content)
|
|
|
|
// print("---")
|
2018-08-18 03:09:59 +00:00
|
|
|
|
2018-08-22 01:43:43 +00:00
|
|
|
let (text, links) = attributedTextForNode(body)
|
|
|
|
self.links = links
|
2018-08-18 03:09:59 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-08-22 01:43:43 +00:00
|
|
|
func attributedTextForNode(_ node: Node) -> (NSAttributedString, [NSRange: URL]) {
|
2018-08-18 03:09:59 +00:00
|
|
|
switch node {
|
|
|
|
case let node as TextNode:
|
2018-08-22 01:43:43 +00:00
|
|
|
return (NSAttributedString(string: node.text()), [:])
|
2018-08-18 03:09:59 +00:00
|
|
|
case let node as Element:
|
2018-08-22 01:43:43 +00:00
|
|
|
var links = [NSRange: URL]()
|
2018-08-18 03:09:59 +00:00
|
|
|
let attributed = NSMutableAttributedString()
|
|
|
|
node.getChildNodes().forEach { child in
|
2018-08-22 01:43:43 +00:00
|
|
|
let (text, childLinks) = attributedTextForNode(child)
|
|
|
|
childLinks.forEach { range, url in
|
|
|
|
let newRange = NSRange(location: range.location + attributed.length, length: range.length)
|
|
|
|
links[newRange] = url
|
|
|
|
}
|
|
|
|
attributed.append(text)
|
2018-08-18 03:09:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch node.tagName() {
|
|
|
|
case "br":
|
|
|
|
attributed.append(NSAttributedString(string: "\n"))
|
|
|
|
case "a":
|
2018-08-18 20:07:34 +00:00
|
|
|
if let link = try? node.attr("href"),
|
|
|
|
let url = URL(string: link) {
|
|
|
|
let linkRange = NSRange(location: 0, length: attributed.length)
|
|
|
|
let linkAttributes: [NSAttributedString.Key: Any] = [
|
|
|
|
.foregroundColor: UIColor.blue
|
|
|
|
]
|
|
|
|
attributed.setAttributes(linkAttributes, range: linkRange)
|
|
|
|
|
|
|
|
links[linkRange] = url
|
|
|
|
}
|
2018-08-18 03:09:59 +00:00
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2018-08-22 01:43:43 +00:00
|
|
|
return (attributed, links)
|
2018-08-18 03:09:59 +00:00
|
|
|
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)
|
|
|
|
}
|
2018-08-17 02:39:16 +00:00
|
|
|
|
2018-08-18 03:09:59 +00:00
|
|
|
if let (_, url) = link {
|
|
|
|
print("Open URL: \(url)")
|
|
|
|
}
|
2018-08-17 02:39:16 +00:00
|
|
|
}
|
2018-08-21 23:23:27 +00:00
|
|
|
|
|
|
|
override func prepareForReuse() {
|
|
|
|
if let url = avatarURL {
|
|
|
|
AvatarCache.shared.cancel(url)
|
|
|
|
}
|
|
|
|
}
|
2018-08-17 02:39:16 +00:00
|
|
|
|
|
|
|
}
|