So I'm building an app similar to Tinder where there is a card "deck" (referred to in this code as cardsDeckView) filled with UIViews (referred to in this code as cardView). Each of these "cards" display user information such as profile images (which you can cycle through), name, age, and profession. They also have a button on them that, when pressed, go to a user info screen where more information about that user is shown. Here is where I'm having trouble. I figured I could pass each user's id to each respective "card" when users are loaded on the deck, and pass this data through the button target when pressed, but I haven't found anything on Stack Overflow regarding passing parameters into button selectors in Swift. Here is my code, that essentially loads existing users with some filters, and creates cardViews with each user's info:
import UIKit
import SDWebImage
import SLCarouselView
import JGProgressHUD
class DeckVC: UIViewController {
let headerView = UIView()
let cardsDeckView = SLCarouselView(coder: NSCoder.empty())
let menuView = BottomNavigationStackView()
var users: [User] = []
var userId: String?
let hud = JGProgressHUD(style: .extraLight)
override func viewDidLoad() {
super.viewDidLoad()
hud.textLabel.text = "Loading nearby users..."
hud.layer.zPosition = 50
hud.show(in: view)
headerView.heightAnchor.constraint(equalToConstant: 70).isActive = true
menuView.heightAnchor.constraint(equalToConstant: 70).isActive = true
let stackView = UIStackView(arrangedSubviews: [headerView, cardsDeckView!, menuView])
stackView.axis = .vertical
view.addSubview(stackView)
stackView.frame = .init(x: 0, y: 0, width: 300, height: 200)
stackView.fillSuperview()
stackView.isLayoutMarginsRelativeArrangement = true
stackView.layoutMargins = .init(top: 0, left: 12, bottom: 0, right: 12)
stackView.bringSubviewToFront(cardsDeckView!)
menuView.settingsButton.addTarget(self, action: #selector(handleSettings), for: .touchUpInside)
menuView.messagesButton.addTarget(self, action: #selector(handleMessages), for: .touchUpInside)
setupUI()
}
func setupUI() {
observeUsers { (user) in
API.User.observeCurrentUser(completion: { (currentUser) in
if (user.id != API.User.CURRENT_USER?.uid) && (currentUser.preferedGender == user.gender) && (currentUser.minAge!...currentUser.maxAge! ~= user.age!) {
self.users.append(user)
DispatchQueue.main.async {
self.setupCards()
}
} else if (user.id != API.User.CURRENT_USER?.uid) && (currentUser.preferedGender == "Both") && (currentUser.minAge!...currentUser.maxAge! ~= user.age!) {
self.users.append(user)
DispatchQueue.main.async {
self.setupCards()
}
}
})
}
}
func observeUsers(completion: #escaping (User) -> Void) {
API.User.REF_USERS.observe(.childAdded) { (snapshot) in
if let dict = snapshot.value as? [String : Any] {
let user = User.transformUser(dict: dict, key: snapshot.key)
completion(user)
}
}
}
#objc func handleSettings() {
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromLeft
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let profileVC = storyboard.instantiateViewController(withIdentifier: "ProfileVC")
self.present(profileVC, animated: true, completion: nil)
}
#objc func handleMessages() {
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let messagesVC = storyboard.instantiateViewController(withIdentifier: "MessagesVC")
self.present(messagesVC, animated: true, completion: nil)
}
#objc func moreInfoTapped() {
let userDetailsController = UserDetailsVC()
userDetailsController.userId = userId
present(userDetailsController, animated: true, completion: nil)
}
#objc func messageUserTapped() {
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromRight
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
view.window!.layer.add(transition, forKey: kCATransition)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let messagesVC = storyboard.instantiateViewController(withIdentifier: "MessagesVC")
let m = MessagesVC()
m.userId = userId
self.present(messagesVC, animated: true, completion: nil)
// go to specific user chat after this transition
}
func setupCards() {
for user in users {
let gradientView = GlympsGradientView()
let barsStackView = UIStackView()
let moreInfoButton = UIButton(type: .system)
moreInfoButton.setImage(#imageLiteral(resourceName: "info_icon").withRenderingMode(.alwaysOriginal), for: .normal)
moreInfoButton.isUserInteractionEnabled = true
moreInfoButton.addTarget(self, action: #selector(moreInfoTapped), for: .touchUpInside)
let messageUserButton = UIButton(type: .system)
messageUserButton.setImage(#imageLiteral(resourceName: "message-icon2").withRenderingMode(.alwaysOriginal), for: .normal)
messageUserButton.isUserInteractionEnabled = true
messageUserButton.addTarget(self, action: #selector(messageUserTapped), for: .touchUpInside)
gradientView.layer.opacity = 0.5
let cardView = CardView(frame: .zero)
cardView.userId = user.id
userId = user.id
cardView.images = user.profileImages
if let photoUrlString = user.profileImages {
let photoUrl = URL(string: photoUrlString[0])
cardView.imageView.sd_setImage(with: photoUrl)
}
(0..<user.profileImages!.count).forEach { (_) in
let barView = UIView()
barView.backgroundColor = UIColor(white: 0, alpha: 0.1)
barView.layer.cornerRadius = barView.frame.size.height / 2
barsStackView.addArrangedSubview(barView)
barsStackView.arrangedSubviews.first?.backgroundColor = .white
}
let nametraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.semibold]
var nameFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.family: "Avenir Next"])
nameFontDescriptor = nameFontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.traits: nametraits])
let agetraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.light]
var ageFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.family: "Avenir Next"])
ageFontDescriptor = ageFontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.traits: agetraits])
let jobtraits = [UIFontDescriptor.TraitKey.weight: UIFont.Weight.light]
var jobFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.family: "Avenir Next"])
jobFontDescriptor = jobFontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.traits: jobtraits])
let attributedText = NSMutableAttributedString(string: user.name!, attributes: [.font: UIFont(descriptor: nameFontDescriptor, size: 30)])
attributedText.append(NSAttributedString(string: " \(user.age!)", attributes: [.font: UIFont(descriptor: ageFontDescriptor, size: 24)]))
if user.profession != "" && user.company != "" {
attributedText.append(NSAttributedString(string: "\n\(user.profession!) # \(user.company!)", attributes: [.font: UIFont(descriptor: jobFontDescriptor, size: 20)]))
}
cardView.informationLabel.attributedText = attributedText
// cardsDeckView.addSubview(cardView)
cardView.addSubview(gradientView)
cardView.addSubview(barsStackView)
cardView.addSubview(moreInfoButton)
cardView.addSubview(messageUserButton)
cardView.moreInfoButton = moreInfoButton
cardView.messageUserButton = messageUserButton
cardView.stackView = barsStackView
moreInfoButton.anchor(top: nil, leading: nil, bottom: cardView.bottomAnchor, trailing: cardView.trailingAnchor, padding: .init(top: 0, left: 0, bottom: 20, right: 20), size: .init(width: 50, height: 50))
messageUserButton.anchor(top: cardView.topAnchor, leading: nil, bottom: nil, trailing: cardView.trailingAnchor, padding: .init(top: 25, left: 0, bottom: 0, right: 25), size: .init(width: 44, height: 44))
barsStackView.anchor(top: cardView.topAnchor, leading: cardView.leadingAnchor, bottom: nil, trailing: cardView.trailingAnchor, padding: .init(top: 8, left: 8, bottom: 0, right: 8), size: .init(width: 0, height: 4))
barsStackView.spacing = 4
barsStackView.distribution = .fillEqually
cardView.fillSuperview()
gradientView.fillSuperview()
hud.textLabel.text = "All done! \u{1F389}"
hud.dismiss(afterDelay: 0.0)
self.cardsDeckView?.appendContent(view: cardView)
}
}
}
extension NSCoder {
class func empty() -> NSCoder {
let data = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.finishEncoding()
return NSKeyedUnarchiver(forReadingWith: data as Data)
}
}
extension Array {
public mutating func appendDistinct<S>(contentsOf newElements: S, where condition:#escaping (Element, Element) -> Bool) where S : Sequence, Element == S.Element {
newElements.forEach { (item) in
if !(self.contains(where: { (selfItem) -> Bool in
return !condition(selfItem, item)
})) {
self.append(item)
}
}
}
}
See setupUsers(), and see how the cardView is being created with buttons. How do I get these userIds from the cardViews and pass them to the UserDetails ViewController once the moreInfo button is pressed? Could I add target/selector to these buttons in the cardView? Any suggestions would help! Thanks!
This will definitely works!
In setupCards() function, use below code as follows:
Button layer's name is of type String which will contain your user id and catch it further using it's layer's name.
moreInfoButton.layer.name = user.id
moreInfoButton.addTarget(self, action: #selector(moreInfoTapped(_:)), for: .touchUpInside)
In moreInfoTapped selector method, add parameters as mentioned below and pass it further to desired controller.
#objc func moreInfoTapped(_ sender: UIButton) {
let userDetailsController = UserDetailsVC()
userDetailsController.userId = sender.layer.name
present(userDetailsController, animated: true, completion: nil)
}
There are two problems.
First, the thing you want to do is impossible. You cannot pass extra data into an target/action button event call, because it is not your call. It is Cocoa's call.
Second, your action method signature is defective:
#objc func moreInfoTapped() {
The correct signature is:
#objc func moreInfoTapped(_ sender:Any) {
If you write the action method that way, you can retrieve the sender — the particular button that was tapped. Now you can work out what button this is and where it is, and so forth, and figure out what data you want to pass in response.
What you can do is Create a Custom class of UIButton. Create your required variables in that custom button class.
Pass your userId, add addTarget.
moreInfoButton.userId = user.id
moreInfoButton.addTarget(self, action: #selector(moreInfoTapped(_:)), for: .touchUpInside)
class CustomButton: UIButton {
var userId: String?
}
#objc func moreInfoTapped(_ sender: CustomButton) {
print(sender.userId)
}
You don't send parameters to button selectors. There is a fixed method signature for IBAction methods.
The IBAction is a method of the target (usually the view controller). The target should hold the additional state data you need to decide what to do.
You posted a lot of code without much explanation and I don't have time to go through that code and figure it out.
I gather you have a button action on one view controller that needs to link to another view controller. The first view controller should know the userIDs that it need to send to the other view controller. The first view controller should have instance variables that give it access to that information. Your IBAction methods have access to the instance variables of the object that implements those IBActions.
Related
I'm a noob in Swift and XCode and it may be a silly question, but I can't figure it out on my own :(
This is how my LoginViewController looks like. Image1
And I have the following function that triggers when I press the submit button.
#objc func loginButtontapped(){
if emailTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" ||
passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
customAlert.showAlert(with: "Error", message: "Fill in the fields.", on: self)
} else {
let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
Auth.auth().signIn(withEmail: email, password: password) { (result, err) in
if err != nil {
self.customAlert.showAlert(with: "Error", message: "Invalid email or password.", on: self)
}else{
let homepageVC = HomepageViewController()
homepageVC.modalPresentationStyle = .fullScreen
let transition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromLeft
transition.timingFunction = CAMediaTimingFunction(name:CAMediaTimingFunctionName.easeInEaseOut)
self.view.window!.layer.add(transition, forKey: kCATransition)
self.present(homepageVC, animated: false, completion: nil)
}}}}
Basically it verifies if the TextFields are empty and after that it shows a view with a message.
The first time after I run the project and I click Submit with the empty fields, I get the desired result, as can be seen here. Screenshot here
The problem appears the second time i press the submit button, after i complete the fields with some text as it shows the views overlapped.
Screenshot here
What I'm trying to do is to show one "alert" at a time, depending on the situation.
Below is how my CustomAlert is build.
class CustomAlert{
struct Constants {
static let backgroundAlphaTo: CGFloat = 0.6
}
private let backgroundView: UIView = {
let backgroundView = UIView()
backgroundView.backgroundColor = .black
backgroundView.alpha = 0
return backgroundView
}()
private let alertView: UIView = {
let alertView = UIView()
alertView.backgroundColor = UIColor(rgb: 0x363636, alphaVal: 1)
alertView.layer.masksToBounds = true
alertView.layer.cornerRadius = 12
return alertView
}()
private var myTargetView: UIView?
func showAlert(with title: String, message: String, on ViewController: UIViewController){
guard let targetView = ViewController.view else{
return
}
myTargetView = targetView
backgroundView.frame = targetView.bounds
targetView.addSubview(backgroundView)
targetView.addSubview(alertView)
alertView.frame = CGRect(x: 40,
y: -300,
width: targetView.frame.size.width-80,
height: 180)
let titleLabel = UILabel(frame: CGRect(x: 20,
y: 10,
width: alertView.frame.size.width,
height: 40))
titleLabel.text = title
titleLabel.textAlignment = .left
titleLabel.font = UIFont(name: "Roboto-Regular", size: 20)
titleLabel.textColor = .white
alertView.addSubview(titleLabel)
let messageLabel = UILabel(frame: CGRect(x: 20,
y: 40,
width: alertView.frame.size.width-30,
height: 60))
messageLabel.text = message
messageLabel.font = UIFont(name: "Roboto-Thin", size: 18)
messageLabel.textColor = .white
messageLabel.numberOfLines = 0
messageLabel.textAlignment = .left
alertView.addSubview(messageLabel)
let button = UIButton (frame: CGRect(x: alertView.frame.size.width-130,
y: alertView.frame.size.height-70,
width: (alertView.frame.midX)/2,
height: 50))
button.setTitle("Okay", for: .normal)
button.applyGradient(colors: [Helper.UIColorFromRGB(0x694BCE).cgColor,Helper.UIColorFromRGB(0x7D3297).cgColor])
button.addTarget(self, action: #selector(dismissAlert), for: .touchUpInside)
alertView.addSubview(button)
UIView.animate(withDuration: 0.25, animations: {
self.backgroundView.alpha = Constants.backgroundAlphaTo},
completion: {done in
if done{
UIView.animate(withDuration: 0.25, animations: {
self.alertView.center = targetView.center
})
}
})
}
#objc func dismissAlert(){
guard let targetView = myTargetView else{
return
}
UIView.animate(withDuration: 0.25, animations: {
self.alertView.frame = CGRect(x: 40,
y: targetView.frame.size.height,
width: targetView.frame.size.width-80,
height: 300)
},
completion: { [weak self] done in
if done{
UIView.animate(withDuration: 0.25, animations: {
self?.backgroundView.alpha = 0
}, completion: { done in
if done {
self?.alertView.removeFromSuperview()
self?.backgroundView.removeFromSuperview()
}
})
}
})
}
}
It looks like you're adding a new UILabel each time you call showAlert function. You should add this label as child of alertView and just change it's text.
i managed to fix it by adding in the dismissAlert() func, the following 2 lines of code:
myTitleLabel?.text = ""
myMessageLabel?.text = ""
I also made those 2 global.
I have a View Controller A with a collection view with list of items. I have to push to another View controller B.
When I select an item in Collection View I want this push to occur. But the push happens after a slight delay which I want to avoid.
I have had few lines of code after didSelect Item method and also NavigationController's navigation Bar setup and CollectionView setup in the View Did load and View Will Appear methods of the View Controller B.
I tried removing the codes after CollectionView Did Select just to check if this was causing the delay but the animation is still slow.
View Controller A Code.
DispatchQueue.global(qos: .background).async {
DispatchQueue.main.async {
//write your code here
var dict = projectsArray[indexPath.row - 1] as! [String:Any]
guard let id = dict["_id"] as? NSNumber else{return }
var canvasIds = NSArray()
if let idsAll = projectCanvasIds.object(forKey: id.stringValue) as? NSArray{
canvasIds = idsAll
}
dict["CanvasIds"] = canvasIds
let dict2 = dict as NSDictionary
IndexContentsModel.projectSlectedCanvasIds = dict2.mutableCopy() as! NSMutableDictionary
let vc = IndexCanvasViewController(nibName: "IndexCanvasViewController", bundle: nil)
self.navigationController?.pushViewController(vc, animated: true)
}
}
View Controller B Code
View Did Load has CollectionViewInit() and SetUI()
func collectionViewinit(){
let nib = UINib(nibName: "TopCanvasesCollectionViewCell", bundle: nil)
self.collectionView.register(nib, forCellWithReuseIdentifier: "topCanvasCell")
collectionView.register(UINib(nibName: "CanvasViewCollectionReusableView3", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "canvasHeaderView")
}
func setUI(){
self.navigationController?.navigationItem.hidesBackButton = true
self.navigationController?.navigationBar.clipsToBounds = true
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController?.navigationBar.shadowImage = UIImage()
let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
let hostStr = host
let userinfo = UserInfo.shared()
if let photo = userinfo?.dicJSON.object(forKey: "photo") as? String{
let profile = host + photo
imgView.sd_setImage(with: URL(string: profile), completed: nil)
}else{
imgView.image = UIImage(named: "test_Profile")
}
let button = UIButton()
button.addTarget(self, action:#selector(profileViewButtonPressed(_:)), for: UIControlEvents.touchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 36, height: 36)// CGRectMake(0, 0, 36, 36)
button.layer.cornerRadius = button.frame.width / 2
button.layer.masksToBounds = true
button.setImage(imgView.image, for: UIControlState.normal)
// let profileButton = UIBarButtonItem(customView: button)
let searchImage = UIImage(named: "Search_Icon3")!
let notificationImage = UIImage(named: "Notification_Icon3")!
let profileImage = UIImage(named: "test_Profile")!
let backImage = UIImage(named: "Back_Icon3")!
let searchButton = UIBarButtonItem(image: searchImage, style: .plain, target: self, action: #selector(searchViewButtonPressed(_:)))
let notificationButton = UIBarButtonItem(image: notificationImage, style: .plain, target: self, action: #selector(notificationViewButtonPressed(_:)))
let profileButton = UIBarButtonItem(image: profileImage, style: .plain, target: self, action: #selector(profileViewButtonPressed(_:)))
let backButton = UIBarButtonItem(image: backImage, style: .plain, target: self, action: #selector(backButtonPressed(_:)))
self.navigationItem.rightBarButtonItems = [profileButton,notificationButton,searchButton]
self.navigationController?.navigationBar.tintColor = UIColor(hexString: "#373839")
self.navigationItem.leftBarButtonItem = backButton
self.setValues()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
if #available(iOS 11.0, *) {
self.navigationController?.navigationBar.prefersLargeTitles = false
self.navigationController?.navigationItem.largeTitleDisplayMode = .never
self.navigationController!.navigationBar.backgroundColor = UIColor.white
} else {
// Fallback on earlier versions
}
if (self.isBeingPresented || self.isMovingToParentViewController) {
self.collectionView.animateViews(animations: animationsCanvas, reversed: false, initialAlpha: 0, finalAlpha: 1, delay: 0, duration: 0.07, animationInterval: 0.1, completion: nil)
}
}
So I have this snippet of code that is supposed to handle making my content scroll to the bottom upon toggling of the keyboard. However, all it does is crash every time that I try to do it.
Any insight?
Here is my code:
#objc 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)
}
})
}
}
I am currently using IGLitkit to populate my view controller. The items are controlled and loaded through this view 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")
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)
}
*/
}
Also note the data source
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
}
}
Controller which contains keyboard function that causes crash
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(){
comments.removeAll()
messagesRef = Database.database().reference().child("Comments").child(eventKey)
print(eventKey)
// print(comments.count)
var query = messagesRef?.queryOrderedByKey()
query?.observe(.value, with: { (snapshot) in
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else {
return
}
print(snapshot)
allObjects.forEach({ (snapshot) in
guard let commentDictionary = snapshot.value as? [String: Any] else{
return
}
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)
}
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
}()
#objc func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.characters.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
#objc 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
}
#objc func handleKeyboardNotification(notification: NSNotification){
if let userinfo = notification.userInfo {
if 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.fetchComments()
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)
}
}
This Code Seems to be crashing my code.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'attempt to scroll to invalid
index path: {length = 2, path = 0 - 3}'
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 = NSIndexPath(item: self.comments.count - 1, section: 0)
self.collectionView.scrollToItem(at: indexPath as IndexPath, at: .top, animated: true)
}
})
}
}
I am also using IGListKit to populate the collection view that this is contained in. Below is my comments 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 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, workingRangeSize: 0)
}()
// 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
})
})
}, 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: 180, 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!)
sendMessage(userText)
// will remove text after entered
self.commentTextField.text = nil
}
func flagButtonTapped (from cell: CommentCell) {
guard let indexPath = collectionView.indexPath(for: cell) else { return }
// 2
let comment = comments[indexPath.item]
// 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)
})
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: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completion) in
if self.comments.count > 0 && isKeyboardShowing {
let indexPath = NSIndexPath(item: self.comments.count - 1, section: 0)
self.collectionView.scrollToItem(at: indexPath as IndexPath, at: .top, animated: true)
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.addSubview(containerView)
collectionView.alwaysBounceVertical = true
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")
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
}
}
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] {
print("comments = \(comments)")
return comments
}
// 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
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)
}
}
Also this is my section controller. Now it is loading the data but as soon as I try to add someething to test if it is working properly using the keyboard it crashes.
import UIKit
import IGListKit
import Foundation
class CommentsSectionController: ListSectionController {
var comment: CommentGrabbed?
override init() {
super.init()
inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
}
// MARK: IGListSectionController Overrides
override func numberOfItems() -> Int {
print(numberOfItems)
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
return cell
}
override func didUpdate(to object: Any) {
comment = object as! CommentGrabbed
}
override func didSelectItem(at index: Int) {
}
}
This specific method is responsbile for returning cells
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()
}
Any insight would be very helpful
This is happening because you set the number of items for your collection to be equal to 1, but you are trying to access a cell that has an index equal to IndexPath(section: 0, item: self:comments.count-1), and comments.count-1 is equal to 3 in this case as you can guess from the error log, which is why you get this error.
The solution here would be returning the correct amount of comments (comments.count-1) for your collection view data source.
UPDATE:
In that case, you should not use NSIndexPath. Instead, use IndexPath. Also, after this change you no longer need to cast your index path as IndexPath in your scrollToItem call. The updated version would look like this:
func handleKeyboardNotification(notification: NSNotification) {
// ...
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)
}
})
}
}
I've got a SidebarMenueController which is my entry point after starting the app.
Within there i've got a NavigationController embedded with a sidebar (slide out) menue. With sideBarDidSelectButtonAtIndex i catch which view, which should be loaded inside the NavigationController. When selecting an entry in the sidebar menue i want to push to another view.
Via printing (into the console) the selected menue entry, i can confirm that the selection is definitely working. But the pushViewController function is not loading the desired view.
I'm using no storyboard!
Any suggestions on how to fix this?
import UIKit
class SideBarMenueController: UIViewController, SideBarDelegate {
var sideBar:SideBar = SideBar()
lazy var menueButton: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
button.backgroundColor = UIColor.clear
button.setImage(#imageLiteral(resourceName: "MenueIcon"), for: UIControlState.normal)
button.addTarget(self, action: #selector(didSelectMenueButton), for: UIControlEvents.touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
let sideBarMenue = UINavigationController(rootViewController:FirstViewController(collectionViewLayout: layout))
self.addChildViewController(sideBarMenue)
sideBarMenue.view.frame = self.view.bounds
self.view.addSubview(sideBarMenue.view)
UINavigationBar.appearance().barTintColor = UIColor(red: 70/255, green: 174/255, blue: 253/255, alpha: 1)
let titleAttributes = [
NSFontAttributeName: UIFont.systemFont(ofSize: 20, weight: UIFontWeightBold),
NSForegroundColorAttributeName: UIColor.white
]
UINavigationBar.appearance().titleTextAttributes = titleAttributes
UINavigationBar.appearance().tintColor = UIColor.white
let NavigationMenueButton = UIBarButtonItem(customView: menueButton)
navigationItem.leftBarButtonItem = NavigationMenueButton
sideBar = SideBar(sourceView: self.view, menuItems: ["feed now", "feed times", "cats", "device status", "device setup", "about app"])
sideBar.delegate = self
}
func didSelectMenueButton() {
let layout = UICollectionViewFlowLayout()
let controller = FeedTimesController(collectionViewLayout: layout)
navigationController?.pushViewController(controller, animated: true)
}
func sideBarDidSelectButtonAtIndex(_ index: Int) {
if index == 0{
let controller = FirstViewController()
navigationController?.pushViewController(controller, animated: true)
print("touched index 0")
} else if index == 1{
let layout = UICollectionViewFlowLayout()
let controller = SecondViewController(collectionViewLayout: layout)
navigationController?.pushViewController(controller, animated: true)
print("touched index 1")
} else if index == 2{
let layout = UICollectionViewFlowLayout()
let controller = ThirdViewController(collectionViewLayout: layout)
navigationController?.pushViewController(controller, animated: true)
print("touched index 2")
} else if index == 3{
print("touched index 3")
} else if index == 4{
print("touched index 4")
} else if index == 5{
print("touched index 5")
}
sideBar.showSideBar(false)
}
}
It looks like you are creating a NavigationController with:
let sideBarMenue = UINavigationController(rootViewController:FirstViewController(collectionViewLayout: layout))
Then you are adding that controller's view as a subview to self.view:
self.view.addSubview(sideBarMenue.view)
But then later, in didSelect, you are trying to access (implied) self.navigationController:
navigationController?.pushViewController(controller, animated: true)
Not sure, but it looks like you want to access the nav controller via:
sideBar.navigationController
Without seeing the code / library you're using for SideBar, this may or may not resolve your issue.