We are trying to make a collection view. In each cell the users can choose an image and enter text into a text field. We noticed that after adding four cells, when we add a new cell, the text field is already filled with the information from previous cells. In our code, we never programmatically fill the text field (which starts out empty), we allow the user to do this. Any suggestions?
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Image", forIndexPath: indexPath) as! CollectionViewCell
cell.deleteButton?.addTarget(self, action: #selector(AddNewItem.xButtonPressed(_:)), forControlEvents: UIControlEvents.TouchUpInside)
cell.deleteButton?.layer.setValue(indexPath.row, forKey: "index")
let item = items[indexPath.item]
let path = getDocumentsDirectory().stringByAppendingPathComponent(item.image)
cell.imageView.image = UIImage(contentsOfFile: path)
cell.imageView.layer.borderColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3).CGColor
cell.imageView.layer.borderWidth = 2
cell.layer.cornerRadius = 7
return cell
}
You can use this in UICollectionViewCell custom class
override func prepareForReuse() {
self.profileImg.image = #imageLiteral(resourceName: "Profile Icon Empty")
super.prepareForReuse()
}
Problem is that you are using dequeReusableCellWithIdentifier which returns already created cell(that you were using before). That's why it's already filled with previous data. You need to clear this data before showing this cell, or fill it from some storage(for example array that represents your collection view cells(each object in array somehow related to cell, in your case that is text wroten in cell))
Here's how I ultimately ended up resolving it.
I created an Item class which contained all of the fields which are shown in the collection view cell and created an array of Items.
Here is a simplified version of my CollectionViewCell class, which here only has a single text field:
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var itemName: UITextField!
var item: Item?
func initializeListeners(){
itemName.addTarget(self, action: #selector(itemNameChanged(_:)), forControlEvents: UIControlEvents.EditingChanged)
}
//When the item name is changed, make sure the item's info is updated
func itemNameChanged(textField: UITextField) {
item?.itemName = textField.text!
}
}
Here's a simplified version of the cellForItemAtIndexPath function in my view controller class:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! CollectionViewCell
cell.initializeListeners()
let item = items[indexPath.item]
cell.item = item
cell.itemName.text = item.itemName
return cell
}
The reason is that collectionViewLayout.collectionViewContentSize.height
is taller than the real contents size! It is recommended to keep UICollectionView calculate the height automatically (without using UIScrollView, let UICollectionView maintain the scroll), as manual change will cause lots of weird behaviors.
Related
I have a complicated situation and I need your help to figure out what should I do.
I have one prototype UIcollectionView, this prototype should be created 4 times for each style type.
I defined these style type as an enum:
enum Colors {
case black, blue, red, green
}
var color = Colors.black
Inside of CollectionViewCell I have also a tableView that has one prototype that contain a label. And there are four arrays that TableViews should be filled by these arrays:
var black = ["black1","black2","black3"]
var blue = ["blue1","blue2","blue3"]
var red = ["red1","red2","red3"]
var green = ["green1","green2","green3"]
now, I tried to make a connection between these TableViews and collectionViews
first for UICollectionView
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch indexPath.row {
case 0,1,2,3:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "colors", for: indexPath) as? colorsCell {
switch indexPath.row {
case 1:
self.color = .black
case 2:
self.color = .blue
case 3:
self.color = .red
case 4:
self.color = .green
default:
break
}
return cell
}
default:
break
}
return UICollectionViewCell()
}
Then, for TableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0,1,2,3:
if let cell = tableView.dequeueReusableCell(withIdentifier: "colCell", for: indexPath) as? colCellDashboard {
switch self.color {
case .black:
cell.title.text = black[indexPath.row]
case .blue:
cell.title.text = blue[indexPath.row]
case .red:
cell.title.text = red[indexPath.row]
case .green:
cell.title.text = green[indexPath.row]
}
return cell
}
return UITableViewCell()
}
The result isn't good enough, the first three tableview in the first three collectionview were filled by blue array, and the last one is correct that filled with green array.
I will be appreciated if you can help me on this.
When the tableView is nested inside the collection you should use
class CollectionCell:UICollectionViewCell,UITableViewDelegate,UITableViewDataSource {
var tableArr = [String]() // table dataSource array
func configure(_ res:[String]) {
tableArr = arr
self.tableView.reloadData()
}
///////
here implement the cellForRowAt and numberOfRows for the nested tableView
}
Inside the vc that contains the collectionView declare the array like this
let arr = [["black1","black2","black3"] , ["blue1","blue2","blue3"] ,
["red1","red2","red3"] , ["green1","green2","green3"]]
Then inside cellForItemAt
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "colors", for: indexPath) as? colorsCell {
cell.configure(arr[indexPath.row])
Ahh, The issue with you code is that in your cellForItemAt delegate method of collection view you are assigning color to a class variable self.color. This will be overwritten for each call to the delegate. So when cellForRowAt delegate for tableview is called it will have the overwritten value of self.color (whatever will be the most updated value) and as a result you will see unexpected entry in your table.
Having tableviews inside collectionViews is kinda common problem. The simplest approach to solve it is:
-Put your datasouce for tableview inside the collectionViewCell.(create a variable in UICollectionViewCell subclass.)
-Assign value to your datasource for each tableView inside cellForItemAt delegate of CollectionView.
-Write all tableViewDelegates inside collectionViewCell and use the datasource varaible of CollectionViewCell to fill tableView.
The key is to underStand that tableView is inside CollectionViewCell and therefore, your CollectionViewCell is responsible to create the tableView.
The CollectionViewCell Structure given be #Sh_Khan looks fine(So not putting similiar code).
I have a uicollectionview with a series of custom class cells that have a few textviews and a uibutton. With over 100 cells, I just want to toggle the uibutton image for each respective cell. The uibutton is a favorites button, and like most apps I just want to favorite and "un-favorite" different cells.
NOTE: I tried to add the gesture recognizer in the class directly, but for some reason the image changes, but it highlights multiple cells instead of the specific cell that was clicked
my code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SimpleExampleSubCell
cell.backgroundColor = UIColor.init(white: 0.10, alpha: 0.25)
cell.infoLine2TextVw.text = ""
cell.infoLine3TextVw.text = ""
if let heading_name = self.dict_dict_holder[indexPath.item]["Name"]{
cell.headerTextVw.text = heading_name
cell.infoLine1TextVw.text = self.dict_dict_holder[indexPath.item]["Phone"]
}
cell.bringSubview(toFront: cell.headerTextVw)
cell.favorite_button.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(AddFavorite(withSender:))))
return cell
}
#objc func AddFavorite(withSender sender:UIButton){
print("clicked")
//The line below fails each time I run it.
sender.setImage(newImage.png,.normal)
}
Replace
#objc func addFavorite(withSender sender:UIButton){
with
// not recommended use touchUpInside
#objc func addFavorite(_ sender:UITapGestureRecognizer){
let btn = sender.view! as! UIButton
}
OR better
cell.favorite_button.addTarget(self, action:#selector(addFavorite), for: .touchUpInside)
Don't Add tapgestures to buttons , as they they have their own targets like touchUpInside or touchUpOutside and many more
table cells are reused you need to nil them inside cellForRowAt or give an else
if someCondition {
cell.favorite_button.setImage(newImage1.png,.normal)
else {
cell.favorite_button.setImage(newImage2.png,.normal)
}
you have to set the default image (plus everything you want to reset) for each cell in the prepareForReuse() method so it clears up the reused content
This is my requirement:
I want my tableView's cell to be like the last cell, its border is margin the tableView some pix, not contradict the tableview's edge.(I want this is because when I click down the cell, there is gray effect on the cell)
How to do with that?
u can't resize the cell's, instead u can set the views's layer properties to achieve the similar effect, for example, (u are not mentioning which language u are using, i assume u are using swift).
i will assume your custom cell contains a UIView and some other view components, like below,
and also add outlet for imageHolderView in the above image,
out let name will be holderView as shown in below image,
in the custom cell class, define two methods for selection management, and your custom cell class would look like below,
class CustomCell: UITableViewCell {
#IBOutlet weak var circleNameTextField: UILabel!
#IBOutlet weak var holderView: UIView!
var cellindexPath:IndexPath?
var selectedIndexPath:IndexPath?
func selectTheCell() {
if self.selectedIndexPath?.row == self.cellindexPath?.row {
self.holderView.layer.cornerRadius = 6.0
self.holderView.layer.masksToBounds = true
self.holderView.layer.borderWidth = 4.0
self.holderView.layer.borderColor = UIColor.red.cgColor
self.backgroundColor = UIColor.gray
} else {
self.resetCellWith(animate: false)
}
}
func resetCellWith(animate:Bool) {
self.holderView.layer.cornerRadius = 0.0
self.holderView.layer.masksToBounds = false
self.holderView.layer.borderWidth = 0.0
self.holderView.layer.borderColor = UIColor.clear.cgColor
self.backgroundColor = UIColor.orange
}
}
now all u have to do is call the above methods, from controller and update the cell behaviour, for example,
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selIndexPath = indexPath
self.aTableView.reloadSections(IndexSet(integer: 0), with: .none)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : CustomCell? = tableView.dequeueReusableCell(withIdentifier: "CUSTOM_CELL", for: indexPath) as? CustomCell//tableView.dequeueReusableCell(withIdentifier: "CUSTOM_CELL") as? CustomCell
cell?.cellindexPath = indexPath
if let selectedIndexPath = self.selIndexPath {
cell?.selectedIndexPath = selectedIndexPath
cell?.selectTheCell()
} else {
cell?.resetCellWith(animate:false)
}
cell?.selectionStyle = .none
return cell!
}
with the above arrangement, u can get the table cell and selection like below,
NOTE: well, above is one way achieve this effect. and method names i simply used the sample project that i created for different purpose. :)
I know how to pass data from a UITableViewController to another ViewController. *Both of controllers are UITableViewController.
For example:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as UITableViewCell
let viewcontroller = NextViewController()
viewcontroller.someText = "Any text"
self.navigationController?.pushViewController(viewcontroller, animated: true)
}
What this does is when I select a cell in the TableViewController, in next view controller, for example a UILabel, say myLabel, is set and a string variable is prepared as someText. And set like self.myLabel.text = someText. And when a row is selected, then in the second view controller, "Any text" will be displayed.
However, this is not what I want to use. By which I mean, my goal I am trying to achieve is:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as UITableViewCell
//In this case, the next view controller is UITableView.
let viewcontroller = NextViewController()
// I want a selected cell to lead to contain an array of String,
//so that next view controller will hold multiple cells.
// which are only unique to each cell in first TableViewController.
viewcontroller.someArray = array????
self.navigationController?.pushViewController(viewcontroller, animated: true)
}
Update
In my SecondTableViewCOntroller
There is a nvaigationBar and cells are produced by adding text by a UITextField inside UIAlertAction. These data is saved with UserDefaults.standfard with "table2". Also, there is a variable, an array of String set;
//Gloabally Declared
var myArray = [String]();
var array = [String]();
//This function is displayed in didViewLoad()
func data(){
if let myArray = savedata.stringArray(forKey: KEY) {
array = myArray
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as UITableViewCell
for object in cell.contentView.subviews
{
object.removeFromSuperview();
}
let getData = UserDefaults.standard.stringArray(forKey:"table2")
cell.textLabel?.text = getData[indexPath.row]
cell.textLabel?.font = UIFont(name: familyFont, size: 19)
cell.textLabel?.font = UIFont.systemFont(ofSize: 19)
cell.textLabel?.textColor = UIColor.darkGray
cell.accessoryType = .disclosureIndicator
cell.textLabel?.textAlignment = .center
return cell
}
In my FirstTableViewController:
In this tableViewController, cells are populated by adding text in a UITextField placed on the navigationBar. Then I want the selected cell to hold cells created in SecondTableViewController by adding text by a UITextFieldinsdie UIAlertAction. These data is saved with UserDefaults.standfard with "table1"
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as UITableViewCell
for object in cell.contentView.subviews
{
object.removeFromSuperview();
}
let getData = UserDefaults.standard.stringArray(forKey:"table1")
cell.textLabel?.myArray = getData[indexPath.row]
cell.textLabel?.font = UIFont(name: familyFont, size: 19)
cell.textLabel?.font = UIFont.systemFont(ofSize: 19)
cell.textLabel?.textColor = UIColor.darkGray
cell.accessoryType = .disclosureIndicator
cell.textLabel?.textAlignment = .center
return cell
}
I do not know how to implement code that achieves this. I have researched posts and googled so hard, but what I could find was only about passing data between a view controller and table view controller, but in my case, it is about passing data between TableView to TableView. So this might help others who have the same trouble too.
This has bugged me for a long time. Please help me...
Thanks!
You are close, and the concept stays the same. In this case you would pass the array of Strings that you want to use in the next tableView and use that array as the datasource for the second table so you would have:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = NewTableViewController()
vc.dataSourceArray = array
self.navigationController.pushViewController(vc, animated: true)
}
Then in your new tableView set your numberForSections(), numForRows(), and cellForIndexPath() based on your dataSourceArray. This will populate the new tableView with the passed data.
As per your question, I think you are trying to send your data to custom view class not to viewController class.
So I suggested to customize the init method of view class.
NB : The code I have shown below is written in Swift 2.1.
Here in the example I have taken a UIView and design table view inside that view. I passed the data from the view controller and showing the list of data as dropdown list.
Code :
In the Custom View
init(frame: CGRect, arrData: NSArray) {
super.init(frame: frame)
arrListData = NSMutableArray(array: arrData)
//In this method I have designed the table view
designListView();
}
In the ViewController :
func ListButtonClicked()
{
let arr = ["Asia", "Europe", "Africa", "North America", "South America", "Australia", "Antarctica"]
dropDownlist = DropdownView(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height), arrData: arr);
dropDownlist.backgroundColor = UIColor.clearColor();
self.view.addSubview(dropDownlist)
}
Here I have mentioned part of code, but for your convenience I am adding the output screen so that you can check whether this fulfil your requirement or not.
My output:
I currently am using a Collection View to display a list of events to a user, and each one of my custom cells has a button that invites the user to attend the event. When pressed, the button image should then change to a newImage.png which displays that they are now attending that event. When I do this in my code below, pressing the button does in fact change the picture, but as I scroll down my collection view, multiple cells that have yet to be clicked also have changed to the "newImage.png." How can I stop this from happening?
class CustomCell: UICollectionViewCell{
#IBAction func myButtonAction(sender: UIButton) {
myButtonOutlet.setImage(UIImage(named: "newImage.png"), forState: UIControlState.Normal)
}
#IBOutlet weak var myButtonOutlet: UIButton!
}
The collection view is reusing cells, as it is designed to do. What you should do is reset the image in your cellForItemAtIndexPath implementation.
I've had this issue before too and this is most likely do to cell reuse. What you might try to do to avoid this problem is to explicitly set the cell's image in your cellForItemAtIndexPath() and then add something to your model that keeps track of which events the user is attending. Then, again in your cellForItemAtIndexPath(), check the model to see what button should be on that cell, and then change it accordingly.
You need to store selected button index in your class and check perticular index in your function
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! UICollectionViewCell
cell.backgroundColor = UIColor.blackColor()
if(selectedIndex == indexPath.row){
myButtonOutlet.setImage(UIImage(named: "newImage.png"), forState: UIControlState.Normal)
//change background image here also your button code
}
return cell
}
and after doing this steps . Reload collection view .
This ended up solving my problem. I have an array that I store my Cells in. Each cell has a boolean called isAttending. In my cellForItemAtIndexPath method, I implemented the code below along with the function switchImage:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("CalendarCell", forIndexPath: indexPath) as! CalendarCell
cell.customButton.layer.setValue(indexPath.row, forKey: "index")
cell.customButton.addTarget(self, action: "switchImage:", forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func switchImage(sender: UIButton){
let index : Int = (sender.layer.valueForKey("index")) as! Int
if (events[index].isAttending == false){
events[index].isAttending = true
cell.customButton.setImage(UIImage(named: "isAttending.png"), forState: UIControlState.Normal)
}else{
events[index].isAttending = false
cell.customButton.setImage(UIImage(named: "isNotAttending.png"), forState: UIControlState.Normal)
}
self.collectionView.reloadData()
}