Multiple Cells for Comments and Reply in a Collect

2020-05-05 17:44发布

问题:

I have two different cells types one is for comments and the other one for replies. I'm trying to render them in the same collectionView and then maybe grouping them like so: each comment with a certain id to have under it its replies. However, with any attempt, I failed.

How would you go about it?

private var comments = [Comment]()
private var replies = [Reply]()
var items: [Any] = []


    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return items.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//        let item = items[indexPath.item]

        var item = items[indexPath.item]

        if item is Comment.Type  {

            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CommentCell.cellId, for: indexPath) as! CommentCell
            cell.comment = items[indexPath.item] as? Comment
            print(item)
            return cell


        } else {
                    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RepliesCell.cellId, for: indexPath) as! RepliesCell
            cell.reply = items[indexPath.item] as? Reply

                    return cell

        }



    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let item = items[indexPath.item]

        if item is CommentCell.Type {

            let dummyCell = CommentCell(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 50))
            dummyCell.comment = items[indexPath.item] as? Comment
            dummyCell.layoutIfNeeded()

            let targetSize = CGSize(width: view.frame.width, height: 250)
            let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
            let height = max(40 + 8 + 8, estimatedSize.height)

            return CGSize(width: view.frame.width, height: height)
        } else {
            let dummyCell = RepliesCell(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 50))
            dummyCell.reply = items[indexPath.item] as? Reply
            dummyCell.layoutIfNeeded()

            let targetSize = CGSize(width: view.frame.width, height: 250)
            let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
            let height = max(40 + 8 + 8, estimatedSize.height)

            return CGSize(width: view.frame.width, height: height)
        }
     }
}

回答1:

The best practice solution

Create a Reply model and Comment model which contains Replies object list

class Comment {
    var commentId: Int
    var commentText: String
    var replies: [Reply]

    init(commentId: Int, commentText: String, replies: [Reply]) {
        self.commentId = commentId
        self.commentText = commentText
        self.replies = replies
    }
}

class Reply {
    var replyId: Int
    var replyText: String

    init(replyId: Int, replyText: String) {
        self.replyId = replyId
        self.replyText = replyText
    }
}

Create a UICollectionReusableView for comment header

class CommentHeader: UICollectionReusableView {

    @IBOutlet weak var commentTextLabel: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    func configure(with comment: Comment) {
        commentTextLabel.text = comment.commentText
    }

}

Create a UICollectionViewCell for reply

class ReplyCell: UICollectionViewCell {

    @IBOutlet weak var replyTextLabel: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    func configure(with reply: Reply) {
        replyTextLabel.text = reply.replyText
    }

}

Create a CommentsViewController class which has a UICollectionView and comments data list

Attention that the header and cell are registered into the collection view in the viewDidLoad method

class CommentsViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    var comments: [Comment] = [Comment]()

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.register(UINib(nibName: "CommentHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "CommentHeaderIdentifier")
        collectionView.register(UINib(nibName: "ReplyCell", bundle: nil), forCellWithReuseIdentifier: "ReplyCellIdentifier")

        comments = getDummyComments(with: 3)
    }

    func getDummyComments(with count: Int) -> [Comment] {

        var comments = [Comment]()
        for i in 1...count {
            comments.append(Comment(commentId: i, commentText: "Comment \(i)", replies: getDummyReplies(with: i)))
        }
        return comments

    }

    func getDummyReplies(with count: Int) -> [Reply] {
        var replies = [Reply]()
        for i in 1...count {
            replies.append(Reply(replyId: i, replyText: "Reply \(i)"))
        }
        return replies
    }
}

And finally set UICollectionView datasource and delegate methods

extension CommentsViewController: UICollectionViewDataSource {

    // for cell
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return comments.count
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return comments[section].replies.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let replyCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReplyCellIdentifier", for: indexPath) as! ReplyCell
        replyCell.configure(with: comments[indexPath.section].replies[indexPath.row])
        return replyCell

    }

    // for header
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

        if kind == UICollectionElementKindSectionHeader {

            let commentHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "CommentHeaderIdentifier", for: indexPath) as! CommentHeader

            commentHeader.configure(with: comments[indexPath.section])
            return commentHeader
        }

        return UICollectionReusableView()
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: 100) // this height is used for the example, you can use self sizing for height
    }

}

extension CommentsViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        return CGSize(width: collectionView.frame.width, height: 100) // this height is used for the example, you can use self sizing for height

    }

}

Let's see the comments screen :)



回答2:

correct me if i am wrong, you are trying to make nested cells, and each has a header type thing which is comment just like Facebook, right!

so the thing is, the comment thing will be in the header of each section and the replies will be in the cells. so in this way, the comments will appear at the top of each section, and replies cells acts as the body. you will need numberOfSections delegate for comments and numberOfRows delegate for replies.

Ask me if you don't get it right.



回答3:

So what I basically did was creating a PostType Class so I can switch through comments and replies types when I had to render a certain cell type.

class PostType {
    enum CommentsTypes {
        case PostType
        case Reply
        case Comment
    }

    var type: CommentsTypes {
        get {
            return .PostType
        }
    }
}

Subclassed my Comments and Replies classes to PostType just like this:

class Comment: PostType {

   ....

    override var type: CommentsTypes {
        return .Comment
    }
}

class Reply: PostType {

    ....

    override var type: CommentsTypes {
        return .Reply
    }
}

Then I made an array of PostType to hold comments and replies after fetching them:

 var postsTypes: [PostType] = []

 //after fetching comments and replies 
self.postsTypes.append(contentsOf: comments)
self.postsTypes.append(contentsOf: replies) 

Finally set

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

        return postsTypes.count
    } 

and

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

let item = indexPath.item
let post = self.postsTypes[item]

switch post.type  {
case .Comment:
    let comment = post as! Comment
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CommentCell.cellId, for: indexPath) as! CommentCell
    cell.comment = comment
    return cell
case .Reply:
    let reply = post as! Reply
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RepliesCell.cellId, for: indexPath) as! RepliesCell

    cell.reply = reply

    return cell

default:
    return UICollectionViewCell()
}
}

Works like a charm! So glad I finally found the answer to my question. Thanks everyone for their suggestions.