Reaching UI Elements created by code at different class - swift

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
}

Related

Showing/Hiding images in CollectionView Cell

I'm trying to show some images and hide others using ".isHidden" in my CollectionView. But when I scroll down or reload the collectionView they either get reordered incorrectly or hidden entirely.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReadBookCell", for: indexPath) as! ReadBookCell
let item = readBookArray[indexPath.item]
for star in cell.starImgOutletCollection {
if star.tag <= item.starRating {
star.isHidden = false
} else {
star.isHidden = true
}
}
return cell
}
Edit: Here is my prepareForReuse
override func prepareForReuse() {
super.prepareForReuse()
for star in starImgOutletCollection {
star.isHidden = true
}
}
Assuming your "stars" are 5 image views in a stack view like this:
And, assuming your starRating will be between 0 and 5 (Zero being no rating yet)...
In your cell class, create a reference to the stack view - since your question mentions starImgOutletCollection I'm assuming you are using #IBOutlet (that is, not creating your views via code), so:
#IBOutlet var starsStackView: UIStackView!
Then, still in your cell class, add this func:
func updateStars(_ starRating: Int) {
for i in 0..<starsStackView.arrangedSubviews.count {
starsStackView.arrangedSubviews[i].isHidden = i >= starRating
}
}
Now, in cellForRowAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReadBookCell", for: indexPath) as! ReadBookCell
let item = readBookArray[indexPath.item]
cell.updateStars(item.starRating)
// do the other stuff to set labels, images, etc
// in the cell
return cell
}
You no longer need the outlet collection for the "star" image views, and you no longer need to implement prepareForReuse().

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

Tap button to change UICollectionView Cell size?

I have a collection view on my HomeController (UICollectionView) Class and my PostCell (UICollectionViewCell) class. HomeController has just 4 different cells. I also have a button inside my PostCell that when touched, handles a handleChangeSize function. When I touch that button, I want that specific cells size to change. Preferably change in height, similar to Instagrams "show more" feature on their cells. Any help will be highly, highly appreciated. Thank you... Here's my code:
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor(white: 0.90, alpha: 1.0)
collectionView?.register(PostCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.showsVerticalScrollIndicator = false
collectionView?.alwaysBounceVertical = true
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PostCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = (view.frame.width) * 9 / 16
return CGSize(width: view.frame.width, height: height + 50 + 50)
}
}
class PostCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
lazy var myButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Change Size", for: .normal)
button.addTarget(self, action: #selector(handleChangeSize), for: .touchUpInside)
return button
}()
func handleChangeSize() {
}
func setupViews() {
addSubview(myButton)
myButton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
myButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I have done this in tableView. follow the same for collectionView.
var expandedCells = [Int]()
here is the cellForRowAtIndexPath method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RequestTableViewCell") as! RequestTableViewCell
cell.selectionStyle = .none
cell.optionButton.tag = indexPath.row
cell.optionButton?.addTarget(self, action: #selector(RequestsViewController.deleteAction), for: UIControlEvents.touchUpInside)
return cell
}
and the action for option button in tableViewCell is
func deleteAction(_ sender: UIButton){
if let button = sender as? UIButton {
// If the array contains the button that was pressed, then remove that button from the array
if expandedCells.contains(sender.tag) {
expandedCells = expandedCells.filter { $0 != sender.tag }
}
// Otherwise, add the button to the array
else {
expandedCells.removeAll()
expandedCells.append(sender.tag)
}
// Reload the tableView data anytime a button is pressed
self.requestTableView.beginUpdates()
self.requestTableView.endUpdates()
}
}
and the heightForRowAtIndexPath method is
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Set the row height based on whether or not the Int associated with that row is contained in the expandedCells array
if expandedCells.contains(indexPath.row) {
return 112
} else {
return 67
}
}
The solution to this problem is quite easy. First, you need to specify what height you want to set for the particular cell and perform that change in handleChangeSize() method (this could be done by simple frame.size.height = ...).
After that, you probably need to resize the whole collection view, otherwise, there are going to be some nasty surprises for you. Perform the calculation (to get the new height) after you have resized the cell (you might call a notification or something that triggers whenever a to resize is necessary).

Swift - JSQMessagesViewController - cell custom

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?