Outlets cannot be connected to repeating content - swift

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")
}
}

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
}

Pass data from UICollectionViewCell to another UICollectionViewCell in code without storyboards using Swift

I have a UICollectionViewCell (say UICollectionViewCell 1) that has a button and UITextfields inside the cell. The UICollectionViewCell that is connected to (ViewController 1) pushes ViewController 1 to a detailedCollectionViewCell which is (ViewController 2), which also holds a button and UITextfields inside the cell.
This is done thru protocol and delegate. My issue, I would like to pass the data from the UITextfields that sits in the UICollectionViewCell (ViewController 1) to the detailedViewCell UITextfield (ViewController 2) when hitting the button in the first UICollectionViewCell (viewController 1).
Here is my code:
//Protocol View Controller 1
protocol InfoCellDelegate: class {
func buttonToDetailCell()
}
// Inside ViewController 1 class
func buttonToDetailCell() {
let vc = InfoViewController(collectionViewLayout: UICollectionViewFlowLayout())
navigationController?.pushViewController(vc, animated: true)
}
// UICollectionViewCell
weak var delegate: InfoCellDelegate?
#objc func InfoButtonPressed(_ sender: UIButton) {
if cellOne.text != "" {
delegate?.buttonToDetailCell()
}
}
Since you are using a custom delegate protocol, you have control over what arguments can be passed to the function.
protocol InfoCellDelegate: class {
func buttonToDetailCell(text: String?)
}
delegate?.buttonToDetailCell(text: textField.text)
Having said that, I think a cleaner solution is to not use a button, and catch the collection view selection event directly with collectionView(_:, didSelectItemAt:).
Then you can retrieve the value from the cell.
// in VC1
collectionView.delegate = self
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as? MyCell
let text = cell?.textField.text
// do something with text
}
Edit:
Here's some semi-complete pseudocode that will do something like what you're after.
protocol ButtonDelegate: class {
func buttonTapped(text: String?)
}
class ViewControllerOne: UIViewController, UICollectionViewDataSource, ButtonDelegate {
func buttonTapped(text: String?) {
let vc = ViewControllerTwo(nibName: nil, bundle: nil)
vc.text = text
self.present(vc, animated: true, completion: nil)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: MyCell = collectionView.dequeueReusableCell... // etc.
cell.delegate = self
return cell
}
}
class ViewControllerTwo: UIViewController {
var text: String?
}
class MyCell: UICollectionViewCell {
var button = UIButton()
var textField = UITextField()
weak var delegate: ButtonDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
self.button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc private func buttonTapped(_ sender: UIButton) {
self.delegate?.buttonTapped(text: self.textField.text)
}
}

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.

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?

Table View with buttons inside of UIViewController

I am trying to use a Table View with cell inside of UIViewController and I want each row to have a button in it. The reason I am using UIViewController instead of UITableView is because I want to have other stuff in that view instead of the whole screen taken by table view.
problem I am having is I only see one button in the last cell. How I can fix this so each row has button in it?
I was hoping that could use something like this
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var logButton: UIButton!
#IBOutlet weak var mytableView: UITableView!
let carLocations = ["Row One", "Row Two", "Row Three"]
override func viewDidLoad() {
super.viewDidLoad()
mytableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return carLocations.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell: UITableViewCell = mytableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
myCell.textLabel?.text = carLocations[indexPath.row]
myCell.detailTextLabel?.text = " Detailed text"
logButton.tag = indexPath.row
// I was hoping that I could use something like this
// myCell.logButton.tag = indexPath.row
return myCell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
// handle delete (by removing the data from your array and updating the tableview)
}
}
}
You can use Custom Cell this way.
Create a new swift file with subclass of UITableViewCell.
Assign that class to your cell by selecting your cell and go to Identity Inspector and it will look a like:
And add elements into your cell which you need for example I have added two labels and one button into cell as per your need and cell will look like:
After that connect outlet of that element into your custom call and your Custom tableview cell class will be:
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var titleLbl: UILabel!
#IBOutlet weak var DetailLabel: UILabel!
#IBOutlet weak var btn: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Now you can create a custom cell with custom tableview cell class this way in your cellForRowAtIndexPath method:
let myCell = mytableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
And you can assign values to it this way:
myCell.titleLbl.text = carLocations[indexPath.row]
myCell.DetailLabel.text = "Detailed Text"
myCell.btn.tag = indexPath.row
And final code will be:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = mytableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell
myCell.titleLbl.text = carLocations[indexPath.row]
myCell.DetailLabel.text = "Detailed Text"
myCell.btn.tag = indexPath.row
return myCell
}
And your result will be:
Check this sample for more Info.
Drop a UITableViewCell on your tableview. That will give you option to customize your cell's look and feel. Create a new class inheriting from UITableViewCell and add that as a class to your tableview cell. Create outlets from cell to this new file and then use cellForRowAtIndexPath to set the properties of the controls inside your cell.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! CBTableViewCell
// add self as delegate for tablecell so delegate can call the function defined within
cell.delegate = self
cell.title.text = self.items[indexPath.row]
return cell
}
I would use Custom Cells to solve this problem...
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:CustomCell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomCell
//Do sth
return cell
}
Your cell:
import UIKit
class CustomCell: UITableViewCell {
#IBOutlet var Button: UIButton!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Actually it is very easy: You just drag a UITableView into your view in the size you want. You add a prototype cell to it and then you customise that cell by dragging in labels, etc. You make a new class, which inherits from that UITableViewCell as explained earlier. You also connect the labels and buttons to the class as explained i the other answers. Apple has a very good explanation here Go to the section where they explain how to customise the cell.