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)
}
}
}
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 :)
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.
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.