Closed Smiller193 closed 7 years ago
The GitHawk is a nice source of how you can leverage IGListKit in your applications. But at first sight it looks correct
Sent with GitHawk
@rnystrom I think it's more a question whether this is a 'best practice'-implementation
Yeah, it is more of a best practice question. However, I do have two issues that I want to bring to light So each one of my section controllers is, in essence, a collection view cell. In my previous implementation, I was able to interact with those cells via a Button that is present in each cell. The button is still there but It no longer responds to the tap gesture I have setup to it.
Secondly, how would I add my header back? The header contained the title text "Comment" and a button that lets you dismiss the controller.
If you want to reintroduce your headers, you need to implement ListSupplementaryViewSource
in your SectionController
so you add the supplementary view (here a header) back. An example of this is the FeedItemSectionController
in the Example-code.
Regarding getting the button working again, you can have a look at the guide https://github.com/Instagram/IGListKit/blob/master/Guides/Modeling%20and%20Binding.md which shows an example adding a like button.
Sent with GitHawk
Thank you, I will take a look at it
So I implemented the header similar to how it is implemented in the FeedItemSectionController however it seems to be adding a header for each and every comment cell. When in all reality I only need one. Below is my implementation. @weyert
func supportedElementKinds() -> [String] {
return [UICollectionElementKindSectionHeader]
}
func viewForSupplementaryElement(ofKind elementKind: String, at index: Int) -> UICollectionReusableView {
guard let view = collectionContext?.dequeueReusableSupplementaryView(ofKind: elementKind, for: self, class: CommentHeader.self, at: index) as? CommentHeader else{
fatalError()
}
view.handle = "Comments"
return view
}
func sizeForSupplementaryView(ofKind elementKind: String, at index: Int) -> CGSize {
return CGSize(width: collectionContext!.containerSize.width, height: 40)
}
It seems as though the header is attached to the cell and not the view
Yes, it's adding a section to every instance of the section controller. Check out the SupplementaryViewController
in the Example. I think that would be easiest way to resolve this issue :)
Sent with GitHawk
Thanks I will take a look into that and keep you updated
Um I don't get it
/** Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
The examples provided by Facebook are for non-commercial testing and evaluation purposes only. Facebook reserves all rights not expressly granted.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
import UIKit import IGListKit
final class SupplementaryViewController: UIViewController, ListAdapterDataSource {
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self)
}()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
let feedItems = [
FeedItem(pk: 1, user: User(pk: 100, name: "Jesse", handle: "jesse_squires"), comments: ["You rock!", "Hmm you sure about that?"]),
FeedItem(pk: 2, user: User(pk: 101, name: "Ryan", handle: "_ryannystrom"), comments: ["lgtm", "lol", "Let's try it!"]),
FeedItem(pk: 3, user: User(pk: 102, name: "Ann", handle: "abaum"), comments: ["Good luck!"]),
FeedItem(pk: 4, user: User(pk: 103, name: "Phil", handle: "phil"), comments: ["yoooooooo", "What's the eta?"])
]
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
adapter.collectionView = collectionView
adapter.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
// MARK: ListAdapterDataSource
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
return feedItems
}
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
return FeedItemSectionController()
}
func emptyView(for listAdapter: ListAdapter) -> UIView? { return nil }
} There seems to be nothing here about rendering headers
@rnystrom the implementation I followed in there added a header to each and every cell. Is there something there that I missed?
Sorry for all the questions
It sounds like you're trying to get a single header for everything? If that's the case, then I'd recommend using a separate section controller to represent the header. It'll be a UICollectionViewCell
in the end, but that's not really important (unless you need sticky headers).
Another option is to gather all of your data into a single section controller that has a supplementary view header.
I'd probably lean towards a separate section controller as the header. We do this a lot in Instagram and it works really, really well for us.
Sorry for all the questions
Np! That's what our issues section is for 😄
Thank you I should be able to do that easily with the knowledge gained from conversing with you guys
Sorry is there an example that just creates the header without attaching some sort of object or ListDiffable Model to it.
// // CommentsHeaderSectionController.swift // Eventful // // Created by Shawn Miller on 9/26/17. // Copyright © 2017 Make School. All rights reserved. //
import UIKit import IGListKit
class CommentsHeaderSectionController: ListSectionController { override init(){ super.init() } // MARK: IGListSectionController Overrides override func numberOfItems() -> Int { return 1 }
override func sizeForItem(at index: Int) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
let dummyCell = CommentHeader(frame: frame)
dummyCell.layoutIfNeeded()
let targetSize = CGSize(width: collectionContext!.containerSize.width, height: 55)
let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
let height = max(40+8+8, estimatedSize.height)
return CGSize(width: collectionContext!.containerSize.width, height: height)
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: CommentHeader.self, for: self, at: index) as? CommentHeader else {
fatalError()
}
cell.handle = "Comments"
return cell
}
override func didUpdate(to object: Any) {
}
override func didSelectItem(at index: Int){
}
}
This what I have but when I go to my main controller and try to add it in the objects(for: ) method I can't because the thing to add is not ListDiffable or there is not a listDiffable model for it to seemingly relate to.
I got the button thing figured out though :)
Would I just do a check for the object and then return the sectionController in this method
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) ->
ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
return CommentsSectionController()
}
@rnystrom
In my project I was using a private let addStore = "addStore" as ListDiffable
and then:
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
return [addStore] + feedItems
}
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) ->
ListSectionController {
if let object = object as? ListDiffable, object === addStore {
return AddStoreSectionController(delegate: self)
}
return CommentsSectionController()
}
Thanks that worked perfectly. Now I just have to go back and make the button work again @weyert
Sorry but can I maybe ask one more thing
So I understand how to make use of protocols to communicate that a button was pressed in another class. Now I have this function in my NewCommentsViewController that is supposed to handle all things related to that comment; both flagging and deleteing.
This function is listed below
func flagButtonTapped (from cell: CommentCell){
guard let indexPath = self.collectionView.indexPath(for: cell) else { return }
// 2
let comment = comments[indexPath.item]
_ = comment.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment, self.eventKey)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
self.adapter.performUpdates(animated: true)
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
present(alertController, animated: true, completion: nil)
}
I will also include my comment cell class for reference.
import Foundation
import UIKit
protocol CommentCellDelegate: class {
func optionsButtonTapped(cell: CommentCell)
}
class CommentCell: UICollectionViewCell {
weak var delegate: CommentCellDelegate? = nil
override var reuseIdentifier : String {
get {
return "cellID"
}
set {
// nothing, because only red is allowed
}
}
var didTapOptionsButtonForCell: ((CommentCell) -> Void)?
var comment: CommentGrabbed?{
didSet{
guard let comment = comment else{
return
}
// textLabel.text = comment.content
//shawn was also here
profileImageView.loadImage(urlString: comment.user.profilePic!)
// print(comment.user.username)
let attributedText = NSMutableAttributedString(string: comment.user.username!, attributes: [NSFontAttributeName: UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string: " " + (comment.content), attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 14)]))
textView.attributedText = attributedText
}
}
let textView: UITextView = {
let textView = UITextView()
textView.font = UIFont.systemFont(ofSize: 14)
textView.isScrollEnabled = false
textView.isEditable = false
// label.numberOfLines = 0
//label.backgroundColor = UIColor.lightGray
return textView
}()
let profileImageView: CustomImageView = {
let iv = CustomImageView()
iv.clipsToBounds = true
iv.contentMode = .scaleAspectFill
return iv
}()
lazy var flagButton: UIButton = {
let flagButton = UIButton(type: .system)
flagButton.setImage(#imageLiteral(resourceName: "icons8-Info-64"), for: .normal)
flagButton.addTarget(self, action: #selector(optionsButtonTapped), for: .touchUpInside)
return flagButton
}()
func optionsButtonTapped (){
didTapOptionsButtonForCell?(self)
}
func onOptionsTapped() {
delegate?.optionsButtonTapped(cell: self)
}
override init(frame: CGRect){
super.init(frame: frame)
// backgroundColor = .yellow
addSubview(textView)
addSubview(profileImageView)
addSubview(flagButton)
textView.anchor(top: topAnchor, left: profileImageView.rightAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 4, paddingLeft: 4, paddingBottom: 4, paddingRight: 4, width: 0, height: 0)
profileImageView.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 8, paddingLeft: 8, paddingBottom: 0, paddingRight: 0, width: 40, height: 40)
profileImageView.layer.cornerRadius = 40/2
flagButton.anchor(top: topAnchor, left: nil, bottom: nil, right: rightAnchor, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: 4, width: 40, height: 40)
flagButton.addTarget(self, action: #selector(CommentCell.onOptionsTapped), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
So my question is how would I call the first function from this class and still present the alert controller the same way? An additional question stems from the removal of the comment when the deletion task is done. Will the cell auto delete itself? Before I decided to implement IGListKit the only way the cell would disappear was if I left and came back I want it to seemingly delete itself in real time.
@rnystrom @weyert
I normally handle it all in the section controller but you can handle it in your VC if you want by just passing the delegate in the section controller e.g. as shown in GitHawk
repo: https://github.com/rnystrom/GitHawk/blob/f8d0cc7f12c6adebfff723ea6029feffa08ee9ac/Classes/Issues/IssuesViewController.swift#L410
If you want to handle it in the section controller you should be able to access the view controller instance in the section controller. See: https://github.com/Instagram/IGListKit/blob/master/Source/IGListSectionController.h#L119
So when I delete my comment it does not disappear from the section controller for some odd reason like it is gone from my database but my controller does not recognize this how would I simulate a real time deletion using this?
@weyert @rnystrom
Did you make sure the data source got updated and you call the update on the IGListKit's adapter?
Sent with GitHawk
Um am I able to call that via the section controller?
@weyert
//will fetch the comments from the database and append them to an array
fileprivate func fetchComments(){
messagesRef = Database.database().reference().child("Comments").child(eventKey)
print(eventKey)
print(comments.count)
messagesRef?.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let commentDictionary = snapshot.value as? [String: Any] else{
return
}
print(commentDictionary)
guard let uid = commentDictionary["uid"] as? String else{
return
}
UserService.show(forUID: uid, completion: { (user) in
if let user = user {
var commentFetched = CommentGrabbed(user: user, dictionary: commentDictionary)
commentFetched.commentID = snapshot.key
let filteredArr = self.comments.filter { (comment) -> Bool in
return comment.commentID == commentFetched.commentID
}
if filteredArr.count == 0 {
self.comments.append(commentFetched)
}
print(self.comments)
self.adapter.performUpdates(animated: true)
}
self.comments.sort(by: { (comment1, comment2) -> Bool in
return comment1.creationDate.compare(comment2.creationDate) == .orderedAscending
})
self.comments.forEach({ (comments) in
})
})
}, withCancel: { (error) in
print("Failed to observe comments")
})
//first lets fetch comments for current event
}
This method handles fetching the comments and the following method is the datasource for the Controller
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var items:[ListDiffable] = comments
print("comments = \(comments)")
return [addHeader] + items
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
if let object = object as? ListDiffable, object === addHeader {
return CommentsHeaderSectionController()
}
return CommentsSectionController()
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
Yes, I think via the collectionContext property of the section controller. If I were you would just go the easy way and go for the delegate pattern. Pass a delegate to your comments section controller and then when the Item needs to be deleted do it through this delegate and implement it in your VC so you can reuse your existing code.
If this doesn't make sense can you make a demo/example project so we can have a closer look what's happening?
Sent with GitHawk
So, for example,If I am understanding correctly I would add a protocol/delegate to this function. Which is the comments section controller
import UIKit
import IGListKit
import Foundation
class CommentsSectionController: ListSectionController,CommentCellDelegate {
var comment: CommentGrabbed?
var eventKey: String?
override init() {
super.init()
// supplementaryViewSource = self
//sets the spacing between items in a specfic section controller
inset = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0)
}
// MARK: IGListSectionController Overrides
override func numberOfItems() -> Int {
return 1
}
override func sizeForItem(at index: Int) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
let dummyCell = CommentCell(frame: frame)
dummyCell.comment = comment
dummyCell.layoutIfNeeded()
let targetSize = CGSize(width: collectionContext!.containerSize.width, height: 55)
let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
let height = max(40+8+8, estimatedSize.height)
return CGSize(width: collectionContext!.containerSize.width, height: height)
}
override var minimumLineSpacing: CGFloat {
get {
return 0.0
}
set {
self.minimumLineSpacing = 0.0
}
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: CommentCell.self, for: self, at: index) as? CommentCell else {
fatalError()
}
// print(comment)
cell.comment = comment
cell.delegate = self
return cell
}
override func didUpdate(to object: Any) {
comment = object as! CommentGrabbed
}
override func didSelectItem(at index: Int){
}
/*
func supportedElementKinds() -> [String] {
return [UICollectionElementKindSectionHeader]
}
func viewForSupplementaryElement(ofKind elementKind: String, at index: Int) -> UICollectionReusableView {
guard let view = collectionContext?.dequeueReusableSupplementaryView(ofKind: elementKind, for: self, class: CommentHeader.self, at: index) as? CommentHeader else{
fatalError()
}
view.handle = "Comments"
return view
}
*/
// func optionsButtonTapped(cell: CommentCell) {
// print("like")
//
//
// }
func optionsButtonTapped(cell: CommentCell){
print("like")
// guard let indexPath = self.collectionContext?.index(for: cell, sectionController: self)(for: cell) else { return }
guard let indexPath = self.collectionContext?.index(for: cell, sectionController: self) else{
return
}
// 2
let comment = self.comment
_ = comment?.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment?.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment!)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment!, (comment?.eventKey)!)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
self.viewController?.present(alertController, animated: true, completion: nil)
}
/*
func sizeForSupplementaryView(ofKind elementKind: String, at index: Int) -> CGSize {
return CGSize(width: collectionContext!.containerSize.width, height: 40)
}
*/
}
And conform to that delegate in this controller
import UIKit
import IGListKit
import Firebase
class NewCommentsViewController: UIViewController, UITextFieldDelegate {
//array of comments which will be loaded by a service function
var comments = [CommentGrabbed]()
var messagesRef: DatabaseReference?
var bottomConstraint: NSLayoutConstraint?
public let addHeader = "addHeader" as ListDiffable
public var eventKey = ""
//This creates a lazily-initialized variable for the IGListAdapter. The initializer requires three parameters:
//1 updater is an object conforming to IGListUpdatingDelegate, which handles row and section updates. IGListAdapterUpdater is a default implementation that is suitable for your usage.
//2 viewController is a UIViewController that houses the adapter. This view controller is later used for navigating to other view controllers.
//3 workingRangeSize is the size of the working range, which allows you to prepare content for sections just outside of the visible frame.
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self)
}()
// 1 IGListKit uses IGListCollectionView, which is a subclass of UICollectionView, which patches some functionality and prevents others.
let collectionView: UICollectionView = {
// 2 This starts with a zero-sized rect since the view isn’t created yet. It uses the UICollectionViewFlowLayout just as the ClassicFeedViewController did.
let view = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
// 3 The background color is set to white
view.backgroundColor = UIColor.white
return view
}()
//will fetch the comments from the database and append them to an array
fileprivate func fetchComments(){
messagesRef = Database.database().reference().child("Comments").child(eventKey)
print(eventKey)
print(comments.count)
messagesRef?.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let commentDictionary = snapshot.value as? [String: Any] else{
return
}
print(commentDictionary)
guard let uid = commentDictionary["uid"] as? String else{
return
}
UserService.show(forUID: uid, completion: { (user) in
if let user = user {
var commentFetched = CommentGrabbed(user: user, dictionary: commentDictionary)
commentFetched.commentID = snapshot.key
let filteredArr = self.comments.filter { (comment) -> Bool in
return comment.commentID == commentFetched.commentID
}
if filteredArr.count == 0 {
self.comments.append(commentFetched)
}
print(self.comments)
self.adapter.performUpdates(animated: true)
}
self.comments.sort(by: { (comment1, comment2) -> Bool in
return comment1.creationDate.compare(comment2.creationDate) == .orderedAscending
})
self.comments.forEach({ (comments) in
})
})
}, withCancel: { (error) in
print("Failed to observe comments")
})
//first lets fetch comments for current event
}
lazy var submitButton : UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
submitButton.isEnabled = false
return submitButton
}()
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .white
containerView.addSubview(self.submitButton)
self.submitButton.anchor(top: containerView.topAnchor, left: nil, bottom: containerView.bottomAnchor, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
containerView.addSubview(self.commentTextField)
self.commentTextField.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: containerView.bottomAnchor, right: self.submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
self.commentTextField.delegate = self
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
containerView.addSubview(lineSeparatorView)
lineSeparatorView.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: nil, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
return containerView
}()
lazy var commentTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Add a comment"
textField.delegate = self
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textField
}()
func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.characters.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
func handleSubmit(){
guard let comment = commentTextField.text, comment.characters.count > 0 else{
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!,eventKey: eventKey)
sendMessage(userText)
// will remove text after entered
self.commentTextField.text = nil
}
func flagButtonTapped (from cell: CommentCell){
guard let indexPath = self.collectionView.indexPath(for: cell) else { return }
// 2
let comment = comments[indexPath.item]
_ = comment.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment, self.eventKey)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
self.adapter.performUpdates(animated: true)
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
present(alertController, animated: true, completion: nil)
}
func handleKeyboardNotification(notification: NSNotification){
if let userinfo = notification.userInfo{
let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
self.bottomConstraint?.constant = -(keyboardFrame.height)
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -(keyboardFrame.height) : 0
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = IndexPath(item: self.comments.count-1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.addSubview(containerView)
collectionView.alwaysBounceVertical = true
containerView.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 40)
//view.addConstraintsWithFormatt("H:|[v0]|", views: containerView)
// view.addConstraintsWithFormatt("V:[v0(48)]", views: containerView)
bottomConstraint = NSLayoutConstraint(item: containerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint!)
adapter.collectionView = collectionView
adapter.dataSource = self
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
collectionView.register(CommentCell.self, forCellWithReuseIdentifier: "CommentCell")
collectionView.register(CommentHeader.self, forCellWithReuseIdentifier: "HeaderCell")
self.collectionView.contentInset = UIEdgeInsetsMake(20, 0, 0, 0)
fetchComments()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.isHidden = true
submitButton.isUserInteractionEnabled = true
}
//viewDidLayoutSubviews() is overridden, setting the collectionView frame to match the view bounds.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var items:[ListDiffable] = comments
print("comments = \(comments)")
return [addHeader] + items
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
if let object = object as? ListDiffable, object === addHeader {
return CommentsHeaderSectionController()
}
return CommentsSectionController()
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}
Calling some function that I create as the delegate function that it conforms to and call the performUpdates function somewhere in this VC?
Yes, correct which then will trigger your code for removing the comment from your data source and then doing a refresh of the IGListKit
by calling: self.adapter.performUpdates(animated: true)
:)
Okay cool thanks ill put that into practice and let you know how it goes
This is my section controller
import UIKit
import IGListKit
import Foundation
protocol CommentsSectionDelegate: class {
func CommentSectionUpdared(sectionController: CommentsSectionController)
}
class CommentsSectionController: ListSectionController,CommentCellDelegate {
weak var delegate: CommentsSectionDelegate? = nil
var comment: CommentGrabbed?
var eventKey: String?
override init() {
super.init()
// supplementaryViewSource = self
//sets the spacing between items in a specfic section controller
inset = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0)
}
// MARK: IGListSectionController Overrides
override func numberOfItems() -> Int {
return 1
}
override func sizeForItem(at index: Int) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
let dummyCell = CommentCell(frame: frame)
dummyCell.comment = comment
dummyCell.layoutIfNeeded()
let targetSize = CGSize(width: collectionContext!.containerSize.width, height: 55)
let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
let height = max(40+8+8, estimatedSize.height)
return CGSize(width: collectionContext!.containerSize.width, height: height)
}
override var minimumLineSpacing: CGFloat {
get {
return 0.0
}
set {
self.minimumLineSpacing = 0.0
}
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: CommentCell.self, for: self, at: index) as? CommentCell else {
fatalError()
}
// print(comment)
cell.comment = comment
cell.delegate = self
return cell
}
override func didUpdate(to object: Any) {
comment = object as! CommentGrabbed
}
override func didSelectItem(at index: Int){
}
/*
func supportedElementKinds() -> [String] {
return [UICollectionElementKindSectionHeader]
}
func viewForSupplementaryElement(ofKind elementKind: String, at index: Int) -> UICollectionReusableView {
guard let view = collectionContext?.dequeueReusableSupplementaryView(ofKind: elementKind, for: self, class: CommentHeader.self, at: index) as? CommentHeader else{
fatalError()
}
view.handle = "Comments"
return view
}
*/
// func optionsButtonTapped(cell: CommentCell) {
// print("like")
//
//
// }
func optionsButtonTapped(cell: CommentCell){
print("like")
// guard let indexPath = self.collectionContext?.index(for: cell, sectionController: self)(for: cell) else { return }
guard let indexPath = self.collectionContext?.index(for: cell, sectionController: self) else{
return
}
// 2
let comment = self.comment
_ = comment?.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment?.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment!)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment!, (comment?.eventKey)!)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
self.onItemDeleted()
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
self.viewController?.present(alertController, animated: true, completion: nil)
}
func onItemDeleted() {
delegate?.CommentSectionUpdared(sectionController: self)
}
/*
func sizeForSupplementaryView(ofKind elementKind: String, at index: Int) -> CGSize {
return CGSize(width: collectionContext!.containerSize.width, height: 40)
}
*/
}
Umm the delgate function function seems to not be getting called this is my VC
import UIKit
import IGListKit
import Firebase
class NewCommentsViewController: UIViewController, UITextFieldDelegate,CommentsSectionDelegate {
//array of comments which will be loaded by a service function
var comments = [CommentGrabbed]()
var messagesRef: DatabaseReference?
var bottomConstraint: NSLayoutConstraint?
public let addHeader = "addHeader" as ListDiffable
public var eventKey = ""
//This creates a lazily-initialized variable for the IGListAdapter. The initializer requires three parameters:
//1 updater is an object conforming to IGListUpdatingDelegate, which handles row and section updates. IGListAdapterUpdater is a default implementation that is suitable for your usage.
//2 viewController is a UIViewController that houses the adapter. This view controller is later used for navigating to other view controllers.
//3 workingRangeSize is the size of the working range, which allows you to prepare content for sections just outside of the visible frame.
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self)
}()
// 1 IGListKit uses IGListCollectionView, which is a subclass of UICollectionView, which patches some functionality and prevents others.
let collectionView: UICollectionView = {
// 2 This starts with a zero-sized rect since the view isn’t created yet. It uses the UICollectionViewFlowLayout just as the ClassicFeedViewController did.
let view = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
// 3 The background color is set to white
view.backgroundColor = UIColor.white
return view
}()
//will fetch the comments from the database and append them to an array
fileprivate func fetchComments(){
messagesRef = Database.database().reference().child("Comments").child(eventKey)
print(eventKey)
print(comments.count)
messagesRef?.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let commentDictionary = snapshot.value as? [String: Any] else{
return
}
print(commentDictionary)
guard let uid = commentDictionary["uid"] as? String else{
return
}
UserService.show(forUID: uid, completion: { (user) in
if let user = user {
var commentFetched = CommentGrabbed(user: user, dictionary: commentDictionary)
commentFetched.commentID = snapshot.key
let filteredArr = self.comments.filter { (comment) -> Bool in
return comment.commentID == commentFetched.commentID
}
if filteredArr.count == 0 {
self.comments.append(commentFetched)
}
print(self.comments)
self.adapter.performUpdates(animated: true)
}
self.comments.sort(by: { (comment1, comment2) -> Bool in
return comment1.creationDate.compare(comment2.creationDate) == .orderedAscending
})
self.comments.forEach({ (comments) in
})
})
}, withCancel: { (error) in
print("Failed to observe comments")
})
//first lets fetch comments for current event
}
lazy var submitButton : UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
submitButton.isEnabled = false
return submitButton
}()
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .white
containerView.addSubview(self.submitButton)
self.submitButton.anchor(top: containerView.topAnchor, left: nil, bottom: containerView.bottomAnchor, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
containerView.addSubview(self.commentTextField)
self.commentTextField.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: containerView.bottomAnchor, right: self.submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
self.commentTextField.delegate = self
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
containerView.addSubview(lineSeparatorView)
lineSeparatorView.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: nil, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
return containerView
}()
lazy var commentTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Add a comment"
textField.delegate = self
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textField
}()
func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.characters.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
func handleSubmit(){
guard let comment = commentTextField.text, comment.characters.count > 0 else{
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!,eventKey: eventKey)
sendMessage(userText)
// will remove text after entered
self.commentTextField.text = nil
}
func flagButtonTapped (from cell: CommentCell){
guard let indexPath = self.collectionView.indexPath(for: cell) else { return }
// 2
let comment = comments[indexPath.item]
_ = comment.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment, self.eventKey)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
self.adapter.performUpdates(animated: true)
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
present(alertController, animated: true, completion: nil)
}
func handleKeyboardNotification(notification: NSNotification){
if let userinfo = notification.userInfo{
let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
self.bottomConstraint?.constant = -(keyboardFrame.height)
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -(keyboardFrame.height) : 0
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = IndexPath(item: self.comments.count-1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.addSubview(containerView)
collectionView.alwaysBounceVertical = true
containerView.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 40)
//view.addConstraintsWithFormatt("H:|[v0]|", views: containerView)
// view.addConstraintsWithFormatt("V:[v0(48)]", views: containerView)
bottomConstraint = NSLayoutConstraint(item: containerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint!)
adapter.collectionView = collectionView
adapter.dataSource = self
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
collectionView.register(CommentCell.self, forCellWithReuseIdentifier: "CommentCell")
collectionView.register(CommentHeader.self, forCellWithReuseIdentifier: "HeaderCell")
self.collectionView.contentInset = UIEdgeInsetsMake(20, 0, 0, 0)
fetchComments()
// Do any additional setup after loading the view.
}
//look here
func CommentSectionUpdared(sectionController: CommentsSectionController){
print("like")
self.adapter.performUpdates(animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.isHidden = true
submitButton.isUserInteractionEnabled = true
}
//viewDidLayoutSubviews() is overridden, setting the collectionView frame to match the view bounds.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var items:[ListDiffable] = comments
print("comments = \(comments)")
return [addHeader] + items
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
if let object = object as? ListDiffable, object === addHeader {
return CommentsHeaderSectionController()
}
return CommentsSectionController()
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}
Notice any problems
Yes, you aren't setting the delegate
-property when creating the instance of CommentsSectionController
try:
let sectionController = CommentsSectionController()
sectionController.delegate = self
return sectionController
You could also change the code a bit so that this section controller accepts a delegate
-parameter in it's initialiser so the code would become: return CommentsSectionController(delegate: self)
.
Where would this code be inside of? would creating a new instance everytime not create a memory leak?
oh inside the listadapter function
So the updated version should look like this
import UIKit
import IGListKit
import Foundation
protocol CommentsSectionDelegate: class {
func CommentSectionUpdared(sectionController: CommentsSectionController)
}
class CommentsSectionController: ListSectionController,CommentCellDelegate {
weak var delegate: CommentsSectionDelegate? = nil
var comment: CommentGrabbed?
var eventKey: String?
override init() {
super.init()
// supplementaryViewSource = self
//sets the spacing between items in a specfic section controller
inset = UIEdgeInsets(top: 5, left: 0, bottom: 0, right: 0)
}
// MARK: IGListSectionController Overrides
override func numberOfItems() -> Int {
return 1
}
override func sizeForItem(at index: Int) -> CGSize {
let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
let dummyCell = CommentCell(frame: frame)
dummyCell.comment = comment
dummyCell.layoutIfNeeded()
let targetSize = CGSize(width: collectionContext!.containerSize.width, height: 55)
let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
let height = max(40+8+8, estimatedSize.height)
return CGSize(width: collectionContext!.containerSize.width, height: height)
}
override var minimumLineSpacing: CGFloat {
get {
return 0.0
}
set {
self.minimumLineSpacing = 0.0
}
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: CommentCell.self, for: self, at: index) as? CommentCell else {
fatalError()
}
// print(comment)
cell.comment = comment
cell.delegate = self
return cell
}
override func didUpdate(to object: Any) {
comment = object as! CommentGrabbed
}
override func didSelectItem(at index: Int){
}
/*
func supportedElementKinds() -> [String] {
return [UICollectionElementKindSectionHeader]
}
func viewForSupplementaryElement(ofKind elementKind: String, at index: Int) -> UICollectionReusableView {
guard let view = collectionContext?.dequeueReusableSupplementaryView(ofKind: elementKind, for: self, class: CommentHeader.self, at: index) as? CommentHeader else{
fatalError()
}
view.handle = "Comments"
return view
}
*/
// func optionsButtonTapped(cell: CommentCell) {
// print("like")
//
//
// }
func optionsButtonTapped(cell: CommentCell){
print("like")
// guard let indexPath = self.collectionContext?.index(for: cell, sectionController: self)(for: cell) else { return }
guard let indexPath = self.collectionContext?.index(for: cell, sectionController: self) else{
return
}
// 2
let comment = self.comment
_ = comment?.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment?.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment!)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment!, (comment?.eventKey)!)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.viewController?.present(okAlert, animated: true, completion: nil)
self.onItemDeleted()
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
self.viewController?.present(alertController, animated: true, completion: nil)
}
func onItemDeleted() {
delegate?.CommentSectionUpdared(sectionController: self)
}
/*
func sizeForSupplementaryView(ofKind elementKind: String, at index: Int) -> CGSize {
return CGSize(width: collectionContext!.containerSize.width, height: 40)
}
*/
}
And then the VC
import UIKit
import IGListKit
import Firebase
class NewCommentsViewController: UIViewController, UITextFieldDelegate,CommentsSectionDelegate {
//array of comments which will be loaded by a service function
var comments = [CommentGrabbed]()
var messagesRef: DatabaseReference?
var bottomConstraint: NSLayoutConstraint?
public let addHeader = "addHeader" as ListDiffable
public var eventKey = ""
//This creates a lazily-initialized variable for the IGListAdapter. The initializer requires three parameters:
//1 updater is an object conforming to IGListUpdatingDelegate, which handles row and section updates. IGListAdapterUpdater is a default implementation that is suitable for your usage.
//2 viewController is a UIViewController that houses the adapter. This view controller is later used for navigating to other view controllers.
//3 workingRangeSize is the size of the working range, which allows you to prepare content for sections just outside of the visible frame.
lazy var adapter: ListAdapter = {
return ListAdapter(updater: ListAdapterUpdater(), viewController: self)
}()
// 1 IGListKit uses IGListCollectionView, which is a subclass of UICollectionView, which patches some functionality and prevents others.
let collectionView: UICollectionView = {
// 2 This starts with a zero-sized rect since the view isn’t created yet. It uses the UICollectionViewFlowLayout just as the ClassicFeedViewController did.
let view = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
// 3 The background color is set to white
view.backgroundColor = UIColor.white
return view
}()
//will fetch the comments from the database and append them to an array
fileprivate func fetchComments(){
messagesRef = Database.database().reference().child("Comments").child(eventKey)
print(eventKey)
print(comments.count)
messagesRef?.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let commentDictionary = snapshot.value as? [String: Any] else{
return
}
print(commentDictionary)
guard let uid = commentDictionary["uid"] as? String else{
return
}
UserService.show(forUID: uid, completion: { (user) in
if let user = user {
var commentFetched = CommentGrabbed(user: user, dictionary: commentDictionary)
commentFetched.commentID = snapshot.key
let filteredArr = self.comments.filter { (comment) -> Bool in
return comment.commentID == commentFetched.commentID
}
if filteredArr.count == 0 {
self.comments.append(commentFetched)
}
print(self.comments)
self.adapter.performUpdates(animated: true)
}
self.comments.sort(by: { (comment1, comment2) -> Bool in
return comment1.creationDate.compare(comment2.creationDate) == .orderedAscending
})
self.comments.forEach({ (comments) in
})
})
}, withCancel: { (error) in
print("Failed to observe comments")
})
//first lets fetch comments for current event
}
lazy var submitButton : UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
submitButton.isEnabled = false
return submitButton
}()
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .white
containerView.addSubview(self.submitButton)
self.submitButton.anchor(top: containerView.topAnchor, left: nil, bottom: containerView.bottomAnchor, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
containerView.addSubview(self.commentTextField)
self.commentTextField.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: containerView.bottomAnchor, right: self.submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
self.commentTextField.delegate = self
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
containerView.addSubview(lineSeparatorView)
lineSeparatorView.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: nil, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
return containerView
}()
lazy var commentTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Add a comment"
textField.delegate = self
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textField
}()
func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.characters.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
func handleSubmit(){
guard let comment = commentTextField.text, comment.characters.count > 0 else{
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!,eventKey: eventKey)
sendMessage(userText)
// will remove text after entered
self.commentTextField.text = nil
}
func flagButtonTapped (from cell: CommentCell){
guard let indexPath = self.collectionView.indexPath(for: cell) else { return }
// 2
let comment = comments[indexPath.item]
_ = comment.uid
// 3
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
// 4
if comment.uid != User.current.uid {
let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
ChatService.flag(comment)
let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(flagAction)
}else{
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
ChatService.deleteComment(comment, self.eventKey)
let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
self.present(okAlert, animated: true)
self.adapter.performUpdates(animated: true)
})
alertController.addAction(cancelAction)
alertController.addAction(deleteAction)
}
present(alertController, animated: true, completion: nil)
}
func handleKeyboardNotification(notification: NSNotification){
if let userinfo = notification.userInfo{
let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
self.bottomConstraint?.constant = -(keyboardFrame.height)
let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
self.bottomConstraint?.constant = isKeyboardShowing ? -(keyboardFrame.height) : 0
UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = IndexPath(item: self.comments.count-1, section: 0)
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.addSubview(containerView)
collectionView.alwaysBounceVertical = true
containerView.anchor(top: nil, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 40)
//view.addConstraintsWithFormatt("H:|[v0]|", views: containerView)
// view.addConstraintsWithFormatt("V:[v0(48)]", views: containerView)
bottomConstraint = NSLayoutConstraint(item: containerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
view.addConstraint(bottomConstraint!)
adapter.collectionView = collectionView
adapter.dataSource = self
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
collectionView.register(CommentCell.self, forCellWithReuseIdentifier: "CommentCell")
collectionView.register(CommentHeader.self, forCellWithReuseIdentifier: "HeaderCell")
self.collectionView.contentInset = UIEdgeInsetsMake(20, 0, 0, 0)
fetchComments()
// Do any additional setup after loading the view.
}
//look here
func CommentSectionUpdared(sectionController: CommentsSectionController){
print("like")
self.adapter.performUpdates(animated: true)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.isHidden = true
submitButton.isUserInteractionEnabled = true
}
//viewDidLayoutSubviews() is overridden, setting the collectionView frame to match the view bounds.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NewCommentsViewController: ListAdapterDataSource {
// 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
var items:[ListDiffable] = comments
print("comments = \(comments)")
return [addHeader] + items
}
// 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
//the comment section controller will be placed here but we don't have it yet so this will be a placeholder
if let object = object as? ListDiffable, object === addHeader {
return CommentsHeaderSectionController()
}
let sectionController = CommentsSectionController()
sectionController.delegate = self
return sectionController
}
// 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}
The comment still seems to be there how would I update screen and datasource
Can you please make a reproducible version of your problem in a git repo so we can have a look at it?
Those two files are in there
@Smiller193 Looks like you are only calling CommentSectionUpdared
in the section controller but not actually deleting the comment anywhere? You probably need to call fetchComments
again or just filter out the deleted comment from the comments
-variable.
If this array doesn't change it won't disappear from the list when you call self.adapter.performUpdates(animated: true)
. As IGListKit
is smart enough to avoid refreshing the collection view when no data has changed. I haven't used Firebase personally yet but I assume you somehow can get notified when the comments have changed and ensure to fetch the new comments or all comments. You could go for the optimistic route and just filter out the comment from the existing comment.
The following might solve your issue quickly, I haven't tested it as I don't have login credentials for your project:
func CommentSectionUpdared(sectionController: CommentsSectionController){
print("like")
self.fetchComments()
}
well I know what the problem may be it seems to not even be deleting it from firebase
Got it to work
[So I have been attempting to implement IGListKit For a couple days now I believe my implementation is correct. I'm using it currently to load comments when a user clicks on a post. I'm not 100% sure if I have implemented it right so this is less of an issue and more of a verification in the hopes that my code has no issue.
This is my comments controller
}
}
extension NewCommentsViewController { func sendMessage(_ message: Comments) { ChatService.sendMessage(message, eventKey: eventKey)
}
This is my section controller
} ](url) Thanks in advance
](url)