When I add multiple test messages, only two of them are displaying on screen. The other messages are clearly there because I can copy them, but the colors are simply not showing. When I scroll around, a new two texts appear, but it is still only two. Below show some examples.
First screenshot shows when the screen first loads. second shows when I move it around. Third shows that the other messages do exist but are not visible. Any ideas on how to fix this? Also, how do I make the names appear? Is there a good guide to doing this in Swift?
Here is the code I used:
var messages = [JSQMessage]()
var incomingBubbleImageView = JSQMessagesBubbleImageFactory.incomingMessageBubbleImageViewWithColor(UIColor.jsq_messageBubbleLightGrayColor())
var outgoingBubbleImageView = JSQMessagesBubbleImageFactory.outgoingMessageBubbleImageViewWithColor(UIColor.jsq_messageBubbleGreenColor())
override func viewDidLoad() {
super.viewDidLoad()
self.sender = UIDevice.currentDevice().identifierForVendor?.UUIDString
messages += [JSQMessage(text: "hello", sender: self.sender)]
messages += [JSQMessage(text: "hello", sender: "other")]
messages += [JSQMessage(text: "hello", sender: self.sender)]
messages += [JSQMessage(text: "hello", sender: "other")]
messages += [JSQMessage(text: "hello", sender: self.sender)]
messages += [JSQMessage(text: "hello", sender: "other")]
reloadMessagesView()
}
func reloadMessagesView() {
self.collectionView?.reloadData()
}
And here is the extension code for the delegate methods:
extension TestJSQ {
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(self.messages.count)
return self.messages.count
}
override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
let data = self.messages[indexPath.row]
return data
}
// override func collectionView(collectionView: JSQMessagesCollectionView!, didDeleteMessageAtIndexPath indexPath: NSIndexPath!) {
// self.messages.removeAtIndex(indexPath.row)
override func collectionView(collectionView: JSQMessagesCollectionView, bubbleImageViewForItemAtIndexPath indexPath: NSIndexPath) -> UIImageView {
let data = messages[indexPath.row]
switch(data.sender) {
case self.sender:
return self.outgoingBubbleImageView
default:
return self.incomingBubbleImageView
}
}
override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageViewForItemAtIndexPath indexPath: NSIndexPath!) -> UIImageView! {
return nil
}
}
Any help would be much appreciated!!!
You need to call self.finishReceivingMessage() after you add the message(s) to the data source (which will then invalidate layout BEFORE call collectionView.reloadData() for you)
Related
I am developing an app and set up a UICollectionView. Below is the code for the view controller for where the UICollectionView is located in:
import UIKit
import Firebase
import FirebaseFirestoreSwift
import FirebaseFirestore
class scrollCollectionViewController: UICollectionViewController{
var tournaments = [String]()
#IBOutlet weak var collectionview: UICollectionView!
override func viewDidLoad() {
fetchTourneys()
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
// Do any additional setup after loading the view.
}
func fetchTourneys() {
let db = Firestore.firestore()
db.collection("Tournaments").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.tournaments.append(document.documentID)
}
}
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return self.tournaments.count
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tourneyIdentifier", for: indexPath) as! ScrollCollectionViewCell
cell.tournamentTitle.text = tournaments[indexPath.row]
print(cell.tournamentTitle.text)
// Configure the cell
return cell
}
// MARK: UICollectionViewDelegate
/*
// Uncomment this method to specify if the specified item should be highlighted during tracking
override func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
return true
}
*/
/*
// Uncomment this method to specify if the specified item should be selected
override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return true
}
*/
/*
// Uncomment these methods to specify if an action menu should be displayed for the specified item, and react to actions performed on the item
override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
return false
}
override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
return false
}
override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
}
*/
}
The cells just dont end up showing up. After including some print statements, I noticed none of the override funcs for numberOfSections or the collection views seem to be running. What could be the issue for why these are not running, and why the cells are not showing up?
you need to return self.tournaments.count in numberOfItemsInSection
func fetchTourneys() {
let db = Firestore.firestore()
db.collection("Tournaments").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.tournaments.append(document.documentID)
}
self.collectionview.reloadData()
}
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return self.tournaments.count
}
Please move the fetchTourneys() after super.viewDidLoad(). Also, you need to ensure the cell identifier is set up correctly and registered with your collectionView
private let reuseIdentifier = "tourneyIdentifier"
class scrollCollectionViewController: UICollectionViewController {
var tournaments = [String]()
#IBOutlet weak var collectionview: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// Register cell classes
self.collectionview!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
fetchTourneys()
}
then, when the cells are being created, re-use the reuseIdentifier
.dequeueReusableCell(withReuseIdentifier: reuseIdentifier
Also, within your Firebase function, ensure you tell the collectionView to update after you've populated the dataSource
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.tournaments.append(document.documentID)
}
self.collectionview.reloadData()
}
Also you said
I noticed none of the override funcs for numberOfSections or the
collection views seem to be running
That would indicate your UICollectionView doesn't know this code is it's viewController. Ensure you've set that up in XCode Inspector. Generally speaking, Classes and Structs should start with a capital letter, vars are lowercased
You have to call reloadData on collectionview once the fetchTourneys is complete.
func fetchTourneys() {
let db = Firestore.firestore()
db.collection("Tournaments").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
self.tournaments.append(document.documentID)
}
self.collectionview.reloadData()
}
}
}
You need to set collectionview datasource and delegate to self in viewDidLoad
put delegate = self and dataSource = self in viewDidLoad
Everyone's answers pointed out errors in the code which moved it in the right direction. But it still did not end up showing the cells. I printed each cell, and noticed that there was a parameter that made them all hidden. I have no idea what caused that. But I added the following code:
cell.isHidden = false
And it worked out great!
I am working with JSQMessagesViewController and Firebase to implement a chat feature in my app. I had everything working well in my ChatViewController. But now I have moved my ChatViewController into a container view that is within a parent view controller and now the "send" button does not work when the keyboard is expanded.
In other words, in order to send a chat message, I must call view.endEditing(true) on the parent view controller that itself is within a UITabBarController, and then the send button will work. But as long as the keyboard is expanded, the send button doesn't respond. below is my ChatViewController code...
import Foundation
import UIKit
import FirebaseDatabase
import JSQMessagesViewController
final class ChatViewController: JSQMessagesViewController {
var outgoingBubbleImageView: JSQMessagesBubbleImage!
var incomingBubbleImageView: JSQMessagesBubbleImage!
var fireRootRef: FIRDatabaseReference!
var chatMessages = [JSQMessage]()
var messagesRefHandle: UInt!
var chatChannelId: String!
override func viewDidLoad(){
super.viewDidLoad()
self.inputToolbar.contentView.textView.backgroundColor = UIColor.clear
inputToolbar.alpha = 0.7
...
}
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool){
super.viewDidAppear(animated)
}
func setupView(){
...
}
override func viewWillDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
removeChatObserver()
}
func removeChatObserver(){
...
}
private func setupMessageBubbles() {
...
}
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return chatMessages.count
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = chatMessages[indexPath.item]
if message.senderId == senderId {
return outgoingBubbleImageView
} else {
return incomingBubbleImageView
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
let message = chatMessages[indexPath.item]
if message.senderId == senderId {
cell.textView!.textColor = UIColor.white
} else {
cell.textView!.textColor = UIColor.black
}
return cell
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, layout collectionViewLayout: JSQMessagesCollectionViewFlowLayout!, heightForMessageBubbleTopLabelAt indexPath: IndexPath!) -> CGFloat {
let message = chatMessages[indexPath.item]
if message.senderId == self.senderId {
return 0
}
if indexPath.item > 0 {
let previousMessage = chatMessages[indexPath.item - 1]
if previousMessage.senderId == message.senderId {
return 0
}
}
return kJSQMessagesCollectionViewCellLabelHeightDefault
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, attributedTextForMessageBubbleTopLabelAt indexPath: IndexPath!) -> NSAttributedString! {
let message = chatMessages[indexPath.item]
switch message.senderId {
case senderId:
return nil
default:
guard let senderDisplayName = message.senderDisplayName else {
assertionFailure()
return nil
}
return NSAttributedString(string: senderDisplayName)
}
}
//no avatar images
override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
print("DID PRESS SEND")
let fireMessagesRef = fireRootRef.child("messages").child(chatChannelId)
let itemRef = fireMessagesRef.childByAutoId()
let messageItem = [
"text": text,
K.MessageKeys.senderIdKey: senderId,
"displayName": senderDisplayName,
]
itemRef.setValue(messageItem)
JSQSystemSoundPlayer.jsq_playMessageSentSound()
finishSendingMessage()
}
override func didPressAccessoryButton(_ sender: UIButton!) {
//
}
private func observeMessages() {
...
}
func addMessage(id: String, text: String, name: String) {
...
}
}
I would like to fix the send button so the user can tap send when the keyboard is expanded. It is interesting that in order to dismiss the keyboard I have to call view.endEditing(true) on the parent view controller and not on the child view itself. This made me think that I need to configure the button action on the parent view however i haven't had any success. Thanks for your help
What I guess is jsq collection view cover the input view, so you are pressing on collection view, not the send button. Put a breakpoint on - (void)jsq_didReceiveKeyboardWillChangeFrameNotification:(NSNotification *)notification in JSQMessagesViewController, check whether CGRectGetHeight(keyboardEndFrame) and insets.bottom for setting collection view bottom is sufficient space to show the input view in your container. A problem is that the jsq controller use autolayout to adjust subviews, the collection view is align with its topLayoutGuide and bottomLayoutGuide which is the view controller thing, when you put a view controller inside another view controller, that may cause confusion.
Take iPhone 6(s)/7 plus for example, the keyboard height is 271, the inputtoolbar is 44 height in controller, so the total height is 315. Therefore CGRectGetHeight(keyboardEndFrame) + insets.bottom should be 315. Put a breakpoint on that line, check whether the sum is 315, if not, that means something calculated wrong.
Update for solution
If the cause is indeed mentioned above, try this to solve the problem
self.additionalContentInset = UIEdgeInsets(top: 0, left: 0, bottom: 44, right: 0)
This will add a bottom inset to the collection view. Add this after viewDidLoad
I am new here and to coding and I need some help with a project that I'm currently stuck on.
I have built an app containing UITableView that first displays body areas (Gastro, cardio, respiratory, etc), the second displays more detailed features on the first (in gastro the table will display stomach, small intestine, colon, etc) and then the the UIViewController will have the more detailed info on the stomach etc.
I have this working all ok but one problem has arose. The UIViewController, regardless of which option is selected in the first table, displays the same info, i.e I get results for gastro even after selecting cardio etc.
The UIviewcotrollers have the storyboards identifiers G1, G2 etc as shown in 'Subchapters' under identities.
I want G1 and the rest of the G series to only display gastro info and then create new view controllers for cardio, C1, C2 etc and so on for the rest of the chapters.
Below is my code. I hope someone could help with this. I would really appreciate it!
import UIKit
class Chapters: UITableViewController {
var ChaptersArray = [String]()
var SubchaptersArray = [secondTable]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// chapter names
ChaptersArray = ["Gastrointestinal","Cardiovascular","Respiratory","Central Nervous System", "Infections"]
// subcharpter names
SubchaptersArray = [secondTable(SecondTitle: ["Gas1", "Gas2", "Gas3", "Gas4"]),
secondTable(SecondTitle: ["Card1", "Card2", "Card3", "Card4"]),
secondTable(SecondTitle: ["Res1", "Res2", "Res3", "Res4"])]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(tableview: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell!
cell.textLabel?.text = ChaptersArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ChaptersArray.count
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow!
let destViewController = segue.destinationViewController as! Subchapters
var secondTableArrayTwo : secondTable
secondTableArrayTwo = SubchaptersArray[indexPath.row]
destViewController.SubChaptersArray = secondTableArrayTwo.SecondTitle
}
}
import UIKit
class Subchapters: UITableViewController {
var SubChaptersArray = [String]()
var identities = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
identities = ["G1", "G2", "G3", "G4"]
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return SubChaptersArray.count
}
override func tableView(tableview: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCellWithIdentifier("SecondCell") as UITableViewCell!
cell.textLabel?.text = SubChaptersArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let vcName = identities[indexPath.row]
let viewcontroller = storyboard?.instantiateViewControllerWithIdentifier(vcName)
self.navigationController?.pushViewController(viewcontroller!, animated: true)
}
}
I have searched google and here for an answer but could find nothing to help, so any explanations as to how to fix this problem would be great and much appreciated.
Thanks
Mark
Hi all. I'm troubling with tableView in swift. Actually i created the table view with two rows(About and Login) in main view controller. Problem at initial, when i click the about or Login , then the New View controller is not opened. But, I try second time to another one row, then the first clicked view Controller is opened at this time of Clicking. so, this cycles shown wrong view controller at every time of clicking. Please tell me as whats wrong with my Code?? Please refer screenshot given below.Thanks in advance!!!
import UIKit
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet var tableView: UITableView!
var titles = ["About","LogIn"]
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titles.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath) as! TableCell
cell.label.text = titles[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
switch indexPath.row
{
case 0: self.performSegueWithIdentifier("aboutSegue", sender: self)
case 1: self.performSegueWithIdentifier("loginSegue", sender: self)
default: break
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "aboutSegue"
{
let vc = segue.destinationViewController as! about
vc.title = "About"
}
else
{
let vc = segue.destinationViewController as! login
vc.title = "Login"
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Below Screen shown, I clicked About Row. But, the Login ViewController is Opened.
This is my Story Board Connections.
Just update didSelectRowAtIndexPath method
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
switch indexPath.row
{
case 0:
self.performSegueWithIdentifier("aboutSegue", sender: self)
break;
case 1:
self.performSegueWithIdentifier("loginSegue", sender: self)
break;
default: break
}
}
I am developing an app using JSQMessagesViewController. But there is problem that message bubbles are aligning irregular order in collectionView.
Here is a Screenshot
Here is the code:
import UIKit
class mDetailContainerViewController: JSQMessagesViewController, JSQMessagesCollectionViewDelegateFlowLayout{
var userName = ""
var messages = [JSQMessage]()
let incomingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImageWithColor(UIColor.lightGrayColor())
let outgoingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImageWithColor(UIColor.greenColor())
override func viewDidLoad() {
super.viewDidLoad()
self.userName = "iPhone"
for i in 1...10{
var sender = (i%2 == 0) ? "Syncano" : self.userName
var message = JSQMessage(senderId: sender, displayName: sender, text: "Text")
self.messages += [message]
}
self.collectionView.reloadData()
self.senderDisplayName = self.userName
self.senderId = self.userName
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func collectionView(collectionView: JSQMessagesCollectionView!, messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
var data = self.messages[indexPath.row]
return data
}
override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
var data = self.messages[indexPath.row]
if (data.senderId == self.senderId){
return self.outgoingBubble
}else{
return self.incomingBubble
}
}
override func collectionView(collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
return nil
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.messages.count
}
override func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: NSDate!) {
var message = JSQMessage(senderId: senderId, displayName: senderDisplayName, text: text)
messages += [message]
self.finishSendingMessage()
}
override func didPressAccessoryButton(sender: UIButton!) {
}
I searched the solution a bit on the internet, but all the instructions are just complicated. I did not figure out anything. Besides, I am not sure that I found a solution. If someone will explain the solution simply, I will be pleased.
Note: JSQMessagesViewController is shown in container view.
Lastly, how can I change the send button title and text field placeholder with using localizations.
Thanks.
I am not familiar with this SDK but I think it is because both of these lines
let incomingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImageWithColor(UIColor.lightGrayColor())
let outgoingBubble = JSQMessagesBubbleImageFactory().incomingMessagesBubbleImageWithColor(UIColor.greenColor())
are incomingMessagesBubbleImageWithColor. Set outgoingBubble to JSQMessagesBubbleImageFactory().outgoingMessagesBubbleImageWithColor (if that exists)
And I've never used this message class before but I believe your alignment issue with the green text might be solved with something like...
outgoingBubble.textLabel.textAlignment = NSTextAlignment.Center
To fix the bubble locations, remove the avatar images with this code;
self.collectionView.collectionViewLayout.incomingAvatarViewSize = CGSizeZero
self.collectionView.collectionViewLayout.outgoingAvatarViewSize = CGSizeZero
For anybody having troubles with this in the future check that your code doesnt look like this:
override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let data = messages[indexPath.row]
if data.senderId == currentUser.objectId! {
return incomingBubble
} else {
return outgoingBubble
}
}
it should look like this:
override func collectionView(collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
let data = messages[indexPath.row]
if data.senderId == PFUser.currentUser()!.objectId! {
return outgoingBubble
} else {
return incomingBubble
}
}
that fixed it for me!
in Swift 3:
For your specific problem that's centering the text inside the bubble:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
cell.textView.textAlignment = .center
return cell
}