eBay / NMessenger

A fast, lightweight messenger component built on AsyncDisplaykit and written in Swift
Other
2.42k stars 271 forks source link

[wish] images and text within a single content node out of box #143

Open dodikk opened 7 years ago

dodikk commented 7 years ago

Often there is a case when a message bubble should contain both

So far, each user is forced to implement that as a CustomNode. Having UICollectionView inside an AsyncDisplayKit node might hit the performance. On the other hand, AsyncDisplayKit might be hard to deal with for some users.

It would be nice to have such node out of box in the NMessenger. Or a snippet in the README, at least.

dodikk commented 7 years ago

I've managed to subclass CollectionViewContentNode and add some views by reusing super.layoutSpecThatFits. I don't like this approach but I'm not sure how to apply composition properly in this case.

A pull request will follow but here is the code :

import Foundation
import AsyncDisplayKit

open class CollectionViewWithTextContentNode: CollectionViewContentNode
{
    public var textMessageNode: ASTextNode?
    public var timestampNode  : ASTextNode?

    override open func layoutSpecThatFits(
        _ constrainedSize: ASSizeRange)
    -> ASLayoutSpec
    {
        guard let textMessageNodeUnwrap = self.textMessageNode,
              let timestampNodeUnwrap   = self.timestampNode
        else
        {
            return super.layoutSpecThatFits(constrainedSize)
        }

        // ====
        //
        let cellsLayout = super.layoutSpecThatFits(constrainedSize)

        let imagesPaddingInsets =
            UIEdgeInsets(
                top: 4,
                left: 15,
                bottom: 15,
                right: 4)
        let imagesPadding =
            ASInsetLayoutSpec(
                insets: imagesPaddingInsets,
                child: cellsLayout)

        // ==== timestampNode
        //
        let timespampPaddingInsets =
            UIEdgeInsets(
                top: 10,
                left: 15,
                bottom: 0,
                right: 0)
        let timestampPadding =
            ASInsetLayoutSpec(
                insets: timespampPaddingInsets,
                child: timestampNodeUnwrap)

        // ==== textMessageNode
        //
        let labelPaddingInsets =
            UIEdgeInsets(
                top: 5,
                left: 15,
                bottom: 0,
                right: 0)
        let labelPadding =
            ASInsetLayoutSpec(
                insets: labelPaddingInsets,
                child: textMessageNodeUnwrap)

        // ====
        //
        let result =
            ASStackLayoutSpec(
                direction: .vertical,
                spacing: 5,
                justifyContent: .start,
                alignItems: .start,
                children: [timestampPadding, labelPadding, imagesPadding])

        return result
    }
}
dodikk commented 7 years ago

P.S. It works :

simulator screen shot jul 20 2017 12 35 37 pm

dodikk commented 7 years ago

Implemented in https://github.com/eBay/NMessenger/pull/146

ignotusverum commented 7 years ago

hi @dodikk, I see that you're using tabBarController with NMessenger. I have question regarding that: Everything works fine on initial load, but chatView disappears when i switch back and forward between controllers from sim/device, and when i use debugView it's actually there, so i'm confused what's happening (i see that contentOffset is changing for some reason, still investigating why). Do you experienced same behavior with your implementation by any chance ?

dodikk commented 7 years ago

@ignotusverum , unfortunately, I have not reproduced the described issue.

and when i use debugView it's actually there, so i'm confused what's happening (i see that contentOffset is changing for some reason, still investigating why)

Please check the z-order of your view hierarchy to see if chatView was not overlapped by some other view.

P.S. I believe, you should create a separate github issue and continue the discussion there. Some screenshots or code of your chat view setup would be helpful (but again, not in this github issue).

dodikk commented 7 years ago

A better implementation to avoid ugly inheritance https://github.com/eBay/NMessenger/issues/170

class VHMessageContentNode: ContentNode {

    public var textNode: ASTextNode?
    public var timestampNode: ASTextNode?
    public var attachmentsNode: CollectionViewContentNode?

    override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {

        let children: [ASLayoutElement?] = [self.timestampNode,
                                            self.textNode,
                                            self.attachmentsNode]

        let stackSpec = ASStackLayoutSpec(direction: .vertical,
                                          spacing: 5,
                                          justifyContent: .start,
                                          alignItems: .start,
                                          children: children.flatMap { $0 })

        let insets = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)

        let insetSpec = ASInsetLayoutSpec(insets: insets,
                                          child: stackSpec)
        return insetSpec
    }
}
poonamdhomane commented 7 years ago

@dodikk can you share how to use this VHMessageContentNode in example app provided in this lib.

dodikk commented 7 years ago

@dodikk can you share how to use this VHMessageContentNode in example app provided in this lib.

@iOSUser110 , you should take the steps below :

  1. Create a node
  2. Pass it to func NMessenger. addMessages() https://github.com/eBay/NMessenger/blob/master/nMessenger/Source/Messenger/Components/NMessenger.swift#L172
// create an instance
//
let contentNode = VHMessageContentNode()

// setup the fields
//
contentNode.textNode = ...;
contentNode.timestampNode = ...;

// create a message node with the content node 
//
let messageNode = VHMessageNode(content: contentNode)

// put the node to NMessenger instance
//
nmessengerView. addMessages([messageNode], scrollsToMessage: false)
dodikk commented 7 years ago

@iOSUser110 , actually, the steps are the same as for the built-in ImageContentNode. However, the NMessengerViewController wraps them into the func sendText() and func sendImage() convenience methods.

dodikk commented 7 years ago

@iOSUser110 , please keep in mind that VHMessageContentNode is not a part of NMessenger framework yet (as my PullRequest has not been merged yet).

So, unfortunately, you'll have to copy-paste this code to your app codebase.

P.S. Not sure if the class will ever make it to the codebase as not all users might need a timestamp. So I left the example code snippet in this ticket.