Search code examples
swiftuicollectionviewcellnsattributedstringtttattributedlabelasyncdisplaykit

How can I use ASTextNode with TTTAttributedLabel


I have built a ASCellNode and it works perfectly. However when I used a traditional UICollectionViewCell I used an TTTAttributedLabel with links.

I don't know how should I replicate this with AsyncDisplayKit

I can assign the attriubtedText from the TTTAttributedLabel to an ASTextNode but of course it doesn't keep the links. How could I efficiently do this. Bellow the example of my ASCellNode.

protocol EmailSentDelegator : class {
    func callSegueFromCell(data object: JSON)
}

class EmailCellNode: ASCellNode, TTTAttributedLabelDelegate {

    let cardHeaderNode: ASTextNode
    var frameSetOrNil: FrameSet?

    init(mailData: JSON) {
        // Create Nodes
        cardHeaderNode = ASTextNode()

        super.init()

        // Set Up Nodes

        cardHeaderNode.attributedString = createAttributedLabel(mailData, self).attributedText

        // Build Hierarchy
        addSubnode(cardHeaderNode)
    }

    override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize {
        var cardSize = CGSizeZero
        cardSize.width = UIScreen.mainScreen().bounds.size.width - 16

        // Measure subnodes
        let cardheaderSize = cardHeaderNode.measure(CGSizeMake(cardSize.width - 56, constrainedSize.height))
        cardSize.height = max(cardheaderSize.height,40) + subjectLabelSize.height + timeStampSize.height + emailAbstractSize.height  + 30

        // Calculate frames
        frameSetOrNil = FrameSet(node: self, calculatedSize: cardSize)
        return cardSize
    }

    override func layout() {
        if let frames = frameSetOrNil {
            cardHeaderNode.frame = frames.cardHeaderFrame
        }
    }

    func attributedLabel(label: TTTAttributedLabel!, didSelectLinkWithTransitInformation components: [NSObject : AnyObject]!) {
            self.delegate.callSegueFromCell(data: mailData)
    }

    func createAttributedLabel(mailData: JSON, cell: EmailCellNode) -> TTTAttributedLabel{
        let senderName = mailData["From"]["Name"].string!
        var recipients:[String] = []

        for (key: String, subJson: JSON) in mailData["To"] {
            if let recipientName = subJson["Name"].string {
                recipients.append(recipientName)
            }
        }
        var cardHeader = TTTAttributedLabel()
        cardHeader.setText("")
        cardHeader.delegate = cell
        cardHeader.userInteractionEnabled = true

        // Add sender to attributed string and save range

        var attString = NSMutableAttributedString(string: "\(senderName) to")
        let senderDictionary:[String:String] = ["sender": senderName]
        let rangeSender : NSRange = (attString.string as NSString).rangeOfString(senderName)

        // Check if recipients is nil and add undisclosed recipients
        if recipients.count == 0 {
            attString.appendAttributedString(NSAttributedString(string: " undisclosed recipients"))
            let rangeUndisclosed : NSRange = (attString.string as NSString).rangeOfString("undisclosed recipients")
            attString.addAttribute(NSFontAttributeName, value: UIFont(name: "SourceSansPro-Semibold", size: 14)!, range: rangeUndisclosed)
            attString.addAttribute(NSForegroundColorAttributeName, value: UIColor.grayColor(), range: rangeUndisclosed)
        } else {

            // Add recipients (first 5) to attributed string and save ranges for each

            var index = 0
            for recipient in recipients {
                if (index == 0) {
                    attString.appendAttributedString(NSAttributedString(string: " \(recipient)"))
                } else if (index == 5){
                    attString.appendAttributedString(NSAttributedString(string: ", and \(recipients.count - index) other"))
                    break
                } else {
                    attString.appendAttributedString(NSAttributedString(string: ", \(recipient)"))
                }
                index = index + 1
            }
        }
        cardHeader.attributedText = attString

        // Adding recipients and sender links with recipient object to TTTAttributedLabel
        cardHeader.addLinkToTransitInformation(senderDictionary, withRange: rangeSender)

        if recipients.count != 0 {
            var index = 0
            var position = senderName.length + 2
            for recipient in recipients {
                let recipientDictionary:[String: AnyObject] = ["recipient": recipient,"index": index ]
                let rangeRecipient : NSRange = (attString.string as NSString).rangeOfString(recipient, options: nil, range: NSMakeRange(position, attString.length-position))
                cardHeader.addLinkToTransitInformation(recipientDictionary, withRange: rangeRecipient)
                index = index + 1
                if (index == 5) {
                    let recipientsDictionary:[String: AnyObject] = ["recipients": recipients]
                    let rangeRecipients : NSRange = (attString.string as NSString).rangeOfString("and \(recipients.count - index) other")
                    cardHeader.addLinkToTransitInformation(recipientsDictionary, withRange: rangeRecipients)
                }
                position = position + rangeRecipient.length
            }
        }
        return cardHeader
    }
}

extension EmailCellNode {
    class FrameSet {
        let cardHeaderFrame: CGRect
        init(node: EmailCellNode, calculatedSize: CGSize) {
            var calculatedcardHeaderFrame = CGRect(origin: CGPointMake(senderPhotoFrame.maxX + 8, senderPhotoFrame.minY) , size: node.cardHeaderNode.calculatedSize)
            cardHeaderFrame = calculatedcardHeaderFrame.integerRect.integerRect
        }
    }
}

Solution

  • I ended up using solely ASTextNode it doesn't have as many features at TTTAttributedLabel but enough for my need. Further more since it's a heavy ASCollectionView it was better to go fully Async. Bellow the an exemple of how I did this in a ASCellNode with the creation of the complexe ASTextNode.

    here is the final result with clickable name passing JSON data through a segue.

    enter image description here

    here is a simplified version of building the NSAttributedString

    func createAttributedLabel(mailData: JSON) -> NSAttributedString{ var recipients:[String] = []

    for (key: String, subJson: JSON) in mailData["To"] {
        if let recipientName = subJson["Name"].string {
            recipients.append(recipientName)
        }
    }
    // Add recipients to attributed string and save ranges for each
        var index = 0
        var position = senderName.length + 2
        for recipient in recipients {
                let recipientDictionary:[String: AnyObject] = ["recipient": recipient,"index": index ]
                let recipientJSON = mailData["To"][index]
                attString.appendAttributedString(NSAttributedString(string: ", \(recipient)"))
                let rangeRecipient : NSRange = (attString.string as NSString).rangeOfString(recipient, options: nil, range: NSMakeRange(position, attString.length-position))
                attString.addAttributes([
                    kLinkAttributeName: recipientJSON.rawValue,
                    NSForegroundColorAttributeName: UIColor.blackColor(),
                    NSFontAttributeName:  UIFont(name: "SourceSansPro-Semibold", size: 14)!,
                    NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleNone.rawValue],
                    range: rangeRecipient)
        }
        index = index + 1
    return attString
    

    }

    end then to detect link taps. I had to transform my JSON in raw value to pass the data across.

    func textNode(textNode: ASTextNode!, tappedLinkAttribute attribute: String!, value: AnyObject!, atPoint point: CGPoint, textRange: NSRange) {
    
    //    The node tapped a link; open it if it's a valid URL
        if  (value.isKindOfClass(NSDictionary)) {
            var jsonTransferred = JSON(rawValue: value as! NSDictionary)
            self.delegate.callSegueFromCellUser(data: jsonTransferred!)
        } else {
            var jsonTransferred = JSON(rawValue: value as! NSArray)
            self.delegate.callSegueFromCellRecipients(data: jsonTransferred!)
        }
    }