Swift - JSQMessagesViewController - cell custom - swift

I understood from this link that you can customise the cell of the JSQMessagesViewController library by overriding this method:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//Configure your cell here
return //yourCustomCell
}
Is there a possibility to get a sample code to understand how to implement the actual customisation including the subclass of the JSQMessagesCollectionViewCell?
As an example, I would like to add a label included at the bottom of the message bubble showing the time the message has been sent.
Thank you.
EDIT
I have created a custom cell class as follow:
class MyCustomCell: JSQMessagesCollectionViewCell {
var textLabel: UILabel
override init(frame: CGRect) {
self.textLabel = UILabel(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height/3))
super.init(frame: frame)
self.textLabel.font = UIFont.systemFontOfSize(UIFont.smallSystemFontSize())
self.textLabel.textAlignment = .Center
contentView.addSubview(self.textLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The custom cell is made programmatically without any xib file.
So in my JSQMessagesViewController, I have to declare it as follow:
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.registerClass(MyCustomCell.self, forCellWithReuseIdentifier: "MyCustomCell")
}
And then, I'm able to override the cellForItemAtIndexPath method as follow:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCustomCell", forIndexPath: indexPath) as! MyCustomCell
return cell
}
The issue is that I cannot see the messages and previous settings anymore.
What am I doing wrong?

This is the way I managed to make it:
1- Create two sub-classes:
JSQMessagesCollectionViewCellOutgoing
import UIKit
import JSQMessagesViewController
class messageViewOutgoing: JSQMessagesCollectionViewCellOutgoing {
#IBOutlet weak var timeLabel: UILabel!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
JSQMessagesCollectionViewCellIncoming
import UIKit
import JSQMessagesViewController
class messageViewIncoming: JSQMessagesCollectionViewCellIncoming {
#IBOutlet weak var timeLabel: UILabel!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
2- Create two xib files with names related to the two subclasses above. For each file, add the custom class related in the File's owner Placeholder.
3- Copy/paste the xib templates of each related classes from the JSQMessagesViewController library stored in the Resources folder and add your custom label into it.
4- Link the custom labels to your new subclasses above
5- Override the following function
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let message = self.messages[indexPath.item]
if message.senderId == self.senderId {
let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! messageViewOutgoing
cell.timeLabel.text = localHourFormatter.stringFromDate(message.date)
return cell
} else {
let cell = super.collectionView(collectionView, cellForItemAtIndexPath: indexPath) as! messageViewIncoming
cell.timeLabel.text = localHourFormatter.stringFromDate(message.date)
return cell
}
}
Now the label is displayed in the bubble.
Still some work to do to adapt the size of the bubble for a proper display but this is not the purpose of this question.
Let me know if any comment/question.

Go to xcode make a new file then choose Cocoa Touch class and in subclass list choose
UICollectionViewCell and put your custom cell name like
customCellUICollectionViewCell.swift
After that go Mainstoryboard and drag a new collection cell to your view
Select your cell and in the right side menu in xcode choose
Show the identity inspector
and in Custom class type your custom class name
customCellUICollectionViewCell.swift
last thing in your collection view
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! customCellUICollectionViewCell
return cell
}

I believe that you are just not setting the text of you custom cell. I would also guard for safety
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCustomCell", forIndexPath: indexPath) as? MyCustomCell else {
print("Could not get my custom cell")
return UICollectionViewCell()
}
let message = messages[indexPath.row] // or what ever your messages are called
let cell.mainLabel.text = message.text
return cell
}

I got error: Can't cast from messageViewOutgoing to JSQMessagesCollectionViewCellIncoming #sbkl. Did you get this error, because we can't cast from parent type to child type?

Related

Reaching UI Elements created by code at different class

I'm creating UICollectionViewCells programmatically. That cell has a Content View (I'm using it only like a decorative frame). Inside that view I create UILabel, UIImageView and another UILabel. All set ups are done when cell is initialized. All works perfect
At ViewController I want to reach that UI Components at method:
CellForItemAt
What will be the best way to do that? Now im working with viewTags to reach particular component, but maybe there is better way?
I need to reach it to fill it with data from CoreData
BR
iMat
As you have created your UICollectionViewCell's UI programmatically, you can have those UI elements as public variable in your UICollectionViewCell.
And Create a public method to set the data into your CustomCell's UI elements as like below.
class CustomCell:UICollectionViewCell{
var imageView:UIImageView!
var label1:UILabel!
var label2:UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
//Initialize your imageView,label1 and label2
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func set(_ text1:String?, text2:String?, image:UIImage?){
imageView.image = image
label1.text = text1
label2.text = text2
}
}
In your cellForItemAt function pass your data to your custom cell as like below.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCustomCellID", for: indexPath) as! CustomCell
cell.set("1", text2: "2", image: yourImage)
return cell
}
Let's say you have your custom UICollectionViewCell subclass
class MyCell: UICollectionViewCell {
var item: Item?
...
func setUI() {
label.text = item.someProperty
...
}
...
}
You can downcast your cell as your subclass when you dequeued it
cell as! MyCell
... then you have access to subclass's methods, variables, etc.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "identifier", for: indexPath) as! MyCell
cell.item = array[indexPath.row]
cell.setUI()
...
return cell
}

Outlets cannot be connected to repeating content

I have created Collection View with Collection View Cell. Inside Collection View Cell I have inserted UIButton. Now I can’t connect outlet because of error:
The firstPercent outlet from the ViewController to the UIButton is
invalid. Outlets cannot be connected to repeating content.
How can I fix it?
It sounds like you are trying to connect the button to your ViewController, you cannot do this because the cell the button is in repeats. To connect the button you need to connect the button to a UICollectionViewCell class. I'll give you an example below on how to set up your cell.
class Cell: UICollectionViewCell {
#IBOutlet var button: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Update per comment:
class ViewController: UIViewController {
//other code
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! Cell
cell.button.addTarget(target: self, action: #selector(buttonTapped), for: .touchUpInside)
return cell
}
#objc func buttonTapped() {
print("do stuff here")
}
}

ScrollTo indexPath of a Collection View inside Collection view Cell (Nested CollectionView)

I am using a collection view inside another collection view cell. Now I want to scroll to an indexPath of inner collection View. Help me if you know this.
Based on the above image, I want to auto scroll the collection view to the red cell which is inside the nested collection view. Assume the nested collection view is a section in the Main collectionView.
Try using a reference to this collection view.
Say that you UICollectionView containing the inner UICollectionView is named YourInnerCollectionViewCell, it will looks something like:
let innerCollectionViewCellId = "innerCollectionViewCellId"
class MainCollectionView : UICollectionViewController {
var innerCollectionViewCell: YourInnerCollectionViewCell?
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.delegate = self
collectionView?.dataSource = self
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch indexPath.row {
case 2:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: innerCollectionViewCellId, for: indexPath) as! YourInnerCollectionViewCell
self.innerCollectionViewCell = cell
return cell
default:
// return other cell
}
}
....
}
class YourInnerCollectionViewCell: UICollectionViewCell {
override init(frame: CGRect){
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now that you have a reference to this cell you can access the collectionView in it and perform the scrollTo.
Hope it help.

UICollectionViewCell changes cell contents when scrolling

I have a custom UICollectionView. I am using a UIView instead of a UICollectionViewCell as I'm wanting to use the UIView elsewhere.
The UICollectionView scrolls only horizontally and has 14 cells; each with the UIView as its contents.
Inside this UIView is another UICollectionView which houses a UICollectionViewCell which is just an image of a dice.
So:
Outer UICollectionView with Cell
The cell has a UIView
The UIView has a UICollectionView and UICollectionViewCell
The UICollectionViewCell is just an imageView of a dice.
The problem I'm having is, when the UICollectionView scrolls horizontally, the contents of the cell are changing when they should remain static; sometimes it randomly moves the contents, or completely removes the contents.
I'm not sure what is causing this to happen.
How can I ensure that upon scrolling that the UICollectionView keeps its contents intact?
Outer UICollectionView:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allLocos?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:OuterCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "OuterCollectionViewCell", for: indexPath) as! OuterCollectionViewCell
if cell.cardView == nil {
let arr = UINib(nibName: "CardView", bundle: nil).instantiate(withOwner: nil, options: nil)
let view = arr[0] as! CardView
cell.contentView.addSubview(view)
cell.cardView = view
}
if let locos = self.allLocos {
let loco:EYLocomotive = locos[indexPath.row]
print(indexPath.row, loco.name, loco.orders)
cell.engineCardView?.setup(loco:loco)
}
cell.layoutIfNeeded()
return cell
}
I think the issue is being caused by this line:
cell.engineCardView?.setup(loco:loco)
My UIView (The cardView) has this code:
#IBOutlet weak var diceCollectionView: UICollectionView!
var dice:[Int] = [Int]()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setup(loco:EYLocomotive) {
self.diceCollectionView.delegate = self
self.diceCollectionView.dataSource = self
self.diceCollectionView.allowsSelection = false
self.diceCollectionView.allowsMultipleSelection = false
self.diceCollectionView.isUserInteractionEnabled = false
self.diceCollectionView.showsVerticalScrollIndicator = false
self.diceCollectionView.showsHorizontalScrollIndicator = false
self.diceCollectionView.register(UINib(nibName: "EYDiceCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "EYDiceCollectionViewCell")
// add dummy dice for testing purposes only
for var _ in 1...3 {
let die = Die().roll
dice.append(die)
}
}
The diceCollectionView is the holder of the Dice Imagery.
When we get to to the UICollectionViewDelegate; in the same file;
// MARK: Collection View Delegate
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dice.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: EYDiceCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "EYDiceCollectionViewCell", for: indexPath) as! EYDiceCollectionViewCell
let dieValue = self.dice[indexPath.row] as Int
let imageFile:String = "die-face-\(dieValue)"
cell.imageView.image = UIImage(named: imageFile)
cell.layoutIfNeeded()
return cell
}
The UICollectionView works fine, the problem comes when I start scrolling horizontally out of the viewport and then scroll back.
Contents of the cell change.
Contents of the cell sometimes change to a different one to the one I'm expecting
Contents of the cell sometimes disappear upon scrolling
What I'd like to know is how can I ensure the contents do not change?
With thanks

Complex Image based Button Layout

I'm fairly new to programming in general, I'm trying to figure out how to create numerous buttons in a complex layout. My layout is basically a transparent PNG of a body cut into about 24 sections and I want each segment of the body to be a separate button.
I've tried a few layouts in the view controller. Setting up a ton of buttons overlaying an image (couldn't keep the layout straight when launching in the simulator) and I've tried giving the buttons images, I've tried large image sized buttons but I could only use the top most button.
Is there any way to do this, or is it going to need code to be doable?
UICollectionViewController or UICollectionView is a better choice for you. You can set the UICollectionViews background image and regard the cells as your buttons.
Here is a quick example.
import UIKit
class Collection: UICollectionViewController {
private let cellId = "cell"
override init(collectionViewLayout layout: UICollectionViewLayout) {
if let l = layout as? UICollectionViewFlowLayout{
l.minimumInteritemSpacing = 0
l.sectionInset = UIEdgeInsetsZero
l.scrollDirection = .Vertical
l.footerReferenceSize = CGSize.zero
l.headerReferenceSize = CGSize.zero
let screenWidth = UIScreen.mainScreen().bounds.width
let itemWidth = CGFloat(Int(screenWidth/4))
l.itemSize = CGSize(width: itemWidth, height: itemWidth)
}
super.init(collectionViewLayout: layout)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.whiteColor()
collectionView?.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
}
extension Collection{
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 4
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath)
cell.backgroundColor = UIColor.redColor()
return cell
}
}
If you want to add touch action to a cell.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
}