Multiple Collection Views with same XIB Cell and button - ios

I have a View Controller with multiple Collection Views.
Each collection view is using the same custom cell with xib. In this xib i have a button.
The collectionViews names are
1) manPerfumeCV
2) womanPerfumeCV
3) kidsPerfumeCV
Inside cellForItemAt i have cell.productCart.tag = indexPath.row
let cell:iPhoneCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "iPhoneCell", for: indexPath) as! iPhoneCollectionViewCell
if collectionView == self.womanPerfumeCV {
let prods = womanPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
} else if collectionView == self.manPerfumeCV {
let prods = manPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
} else if collectionView == self.kidsPerfumeCV {
let prods = kidsPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
}
and in the same view controller i have this action for the button from the xib file
#IBAction func iPhoneAddToCart(_ sender: AnyObject) {
let butt = sender as! UIButton
let indexP = IndexPath(row: butt.tag, section: 0)
let cell = manPerfumeCV.cellForItem(at: indexP) as! iPhoneCollectionViewCell
print(manPerfumeData[indexP.row].price)
}
Each collection view has its own [Products] array.
1) manPerfumeData
2) womanPerfumeData
3) kidPerfumeData.
In my code if i tap at the button which is at the 1st collection view with manPerfumeData it prints the price very well.
Although if i push the button on the 2nd or 3rd collection view the app crashes.
Is there any way to know from wich collection view he pushed the button so i can load the spesific [Products] array ??
Thanks!

You can use tag property of UIButton.
Let consider your code
let cell:iPhoneCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "iPhoneCell", for: indexPath) as! iPhoneCollectionViewCell
if collectionView == self.womanPerfumeCV {
//Your Code
cell.productCart.tag = indexPath.row + 1000
} else if collectionView == self.manPerfumeCV {
//Your Code
cell.productCart.tag = indexPath.row + 2000
} else if collectionView == self.kidsPerfumeCV {
//Your Code
cell.productCart.tag = indexPath.row + 3000
}
You noticed that I changed the tag property of UIButton. Now when tap on button, check tag property of UIButton. Like this
if (sender.tag >= 1000 && sender.tag<2000)
{
//manPerfumeCV
}
else if (sender.tag >= 2000 && sender.tag<3000)
{
//womanPerfumeCV
}
else
{
//kidsPerfumeCV
}
For fetching value from array, subtract the beginning value from tag property and you will get value, like this
For manPerfumeCV
indexValue = sender.tag - 1000
For womanPerfumeCV
indexValue = sender.tag - 2000
For kidsPerfumeCV
indexValue = sender.tag - 3000
This value can directly use for getting data from array.

inorder to achieve it you can add tag property to the collectionViews in viewDidLoad
override func viewDidLoad() {
// your code
self.womanPerfumeCV.tag = 0;
self.manPerfumeCV.tag = 1;
self.kidsPerfumeCV = 2;
}
using this you can identify the source
// Now its time to listen to the buttonEvent
I would suggest to keep the event listener in the same controller or make a protocol callback only with index and tag. To keep it simple lets make the changes in same viewController File
if collectionView == self.womanPerfumeCV {
let prods = womanPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
} else if collectionView == self.manPerfumeCV {
let prods = manPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
} else if collectionView == self.kidsPerfumeCV {
let prods = kidsPerfumeData[indexPath.row]
cell.configureCell(products: prods)
cell.productCart.tag = indexPath.row
}
cell.yourButton.tag = (collectionView.tag * 1000 )+(indexPath.row);
cell.yourButton.addTarget(self, action: #selector(ButtonClicked(_:)), forControlEvents: .TouchUpInside)
now add selector to event
func ButtonClicked(sender: UIButton) {
let index : Int = sender.tag % 1000;
switch sender.tag {
case let x where x < 1000:
loadDataFromFirstArrayWithIndex(index);break;
case let x where x <2000:
loadDataFromSecondArrayWithIndex(index);break;
default:
loadDataFromThirdArrayWithIndex(index);
}
}

Related

Get selected cell indexPath.section and indexPath.row in button action in swift

i'm having a table view in my view controller, in which there are some sections and cells under that sections. These sections and cells are dynamic. i have a button with an action attached to it. I want to get the indexPath.section and indexPath.row of that selected cell in my button action where i'm making a condition on the basis of that. How can i get that in my button action?
My code for selecting cell is,
if allowedSelection == 0 {
if selectionStatus == false {
let sectionToReload = indexPath.section
let indexSet: IndexSet = [sectionToReload]
selection[indexPath.section].isSelected[indexPath.row] = !selectionStatus
let price = AddonCategoryModel![indexPath.section].addonItems![indexPath.row].price
selection[indexPath.section].numberOfSelectedItems = selection[indexPath.section].numberOfSelectedItems - 1
selection[indexPath.section].sectionRowsPrice[indexPath.row] = price!
self.addonTableView.reloadSections(indexSet, with: .automatic)
}
Here is my button action in which i want to get both of them,
if selctedItems == allowedSelection{
var addOnItemsSum = 0
for index in 0..<selection.count {
for count in 0..<selection[index].sectionRowsPrice.count {
addOnItemsSum = addOnItemsSum + selection[index].sectionRowsPrice[count]
print(addOnItemsSum)
}
}
else
{
print("Hello")
}
Yet it is only giving me results on index 0 i want it to give of the selected index.My button is outside the table view like a normal button having an action.

Collection view - Bubble border and fill color changes to different color

I am using collection view and we have added bubble to the cell.
Also, bubble has the different fill color and border color.
Please find attachment for details.
but when we scroll collection view that time bubble color sometime changed to different and again restored to correct color.
Here's my code:
func collectionView(_ collectionView: UICollectionView,cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// INIT CELLS INSIDE COLLECTION VIEW
if(collectionView == customContentCollectionView){
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: contentCellId, for: indexPath) as! MyContentCell
setupContentCellComponents(cell: cell)
// Configure the cell
cell.horizontalLine.backgroundColor = Color.lightBlue
var labelColour = UIColor()
// If we found record
if(self.bubbleArray[indexPath.section][indexPath.item].interactions != ""){ // GENERATING CUSTOM BUBBLE COLOR AS PER INTERACTIONS
let (bubbleBorder, bubbleFill, labelColor, inspectorNav) = getBubbleColor(controlType: controlParam, count: Int(self.bubbleArray[indexPath.section][indexPath.item].interactions)!, selected: false)
cell.shapeLayer.strokeColor = bubbleBorder.cgColor
cell.shapeLayer.fillColor = bubbleFill.cgColor
cell.gradient.colors = [bubbleFill.cgColor, bubbleFill.cgColor]
labelColour = labelColor
}
cell.labelCount.font = UIFont(name: cellFontName, size: cellFontSize)
cell.labelCount.text = self.bubbleArray[indexPath.section][indexPath.item].interactions
if (self.bubbleArray[indexPath.section][indexPath.item].umid != ""){
cell.tag = Int(self.bubbleArray[indexPath.section][indexPath.item].umid)!
cell.labelCount.tag = Int(self.bubbleArray[indexPath.section][indexPath.item].umid)!
cell.labelCount.textColor = labelColour
}
else (self.bubbleArray[indexPath.section][indexPath.item].umid == ""){ // REMOVING BUBBLE IF NO CONTENT
cell.shapeLayer.removeFromSuperlayer()//remove from superview
}
}
Here's scrolling logic :
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// selectedSection is the value where you have tapped
if(selectedSection != -1){
for i in 0...numberOfItemsInSection{ // CODE TO MAINTAIN ROW HIGHLIGHT POST SCROLL
let newIndices = IndexPath(row: i, section: selectedSection)
for visibleIndices in customContentCollectionView.indexPathsForVisibleItems{
if(newIndices == visibleIndices){
print("NEW INDICES: \(newIndices)")
if(newIndices.section == selectedSection){
selectedRowCells.append(customContentCollectionView.cellForItem(at: newIndices)!)
}
// horizontal line for selected bubble
let singleCell : MyContentCell = customContentCollectionView.cellForItem(at: newIndices)! as! MyContentCell
singleCell.horizontalLine.backgroundColor = UIColor.white
if(previouslySelectedIndex != nil && visibleIndices == previouslySelectedIndex){
changeBubbleColor(index: previouslySelectedIndex, selected: true)
break
}
}
}
Try this way,
Here if cell satisfies the condition then properties are set but if the condition is not satisfied at that time cell takes previous properties set in the conditon so you also need to set the properties in else condition also
so try by setting properties as suggested with comment in code below
I hope you will get your solution
func collectionView(_ collectionView: UICollectionView,cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// INIT CELLS INSIDE COLLECTION VIEW
if(collectionView == customContentCollectionView){
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: contentCellId, for: indexPath) as! MyContentCell
setupContentCellComponents(cell: cell)
// Configure the cell
cell.horizontalLine.backgroundColor = Color.lightBlue
var labelColour = UIColor()
// If we found record
if(self.bubbleArray[indexPath.section][indexPath.item].interactions != "")
{ // GENERATING CUSTOM BUBBLE COLOR AS PER INTERACTIONS
let (bubbleBorder, bubbleFill, labelColor, inspectorNav) = getBubbleColor(controlType: controlParam, count: Int(self.bubbleArray[indexPath.section][indexPath.item].interactions)!, selected: false)
cell.shapeLayer.strokeColor = bubbleBorder.cgColor
cell.shapeLayer.fillColor = bubbleFill.cgColor
cell.gradient.colors = [bubbleFill.cgColor, bubbleFill.cgColor]
labelColour = labelColor
}
else
{
//set default cell.shapeLayer.strokeColor
//set default cell.shapeLayer.fillColor
//set default cell.gradient.colors
//set default labelColour
}
cell.labelCount.font = UIFont(name: cellFontName, size: cellFontSize)
cell.labelCount.text = self.bubbleArray[indexPath.section][indexPath.item].interactions
if (self.bubbleArray[indexPath.section][indexPath.item].umid != "")
{
cell.tag = Int(self.bubbleArray[indexPath.section][indexPath.item].umid)!
cell.labelCount.tag = Int(self.bubbleArray[indexPath.section][indexPath.item].umid)!
cell.labelCount.textColor = labelColour
}
else
{
//Set default cell.tag
//Set default cell.labelCount.tag
//Set default cell.labelCount.textColor
}
}

How to get back the label again while removing from superview in table view cell in swift 3?

I had used the code remove from superView to remove labels from screen when there is no address available just showing one label as shown in below
but here when I got address from the api and was not displaying properly and the code itself was entering into it is showing in image as shown below
but the proper image should be displayed with name label , address label and mobile number label can anyone help me how to display the address label and mobile number label after removing from view when address has got from api ?
here is my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.section == 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! AddressTableViewCell
let dict = guestShippingAddressModel
self.tableDetails.isHidden = false
self.activityIndicator.stopAnimating()
self.activityIndicator.hidesWhenStopped = true
cell.deleteButton.tag = indexPath.row
if self.street?.isEmpty == true || self.street?.isEmpty == nil {
cell.addressLabel.isHidden = true
cell.mobileNumberLabel.isHidden = true
cell.radioButton.isHidden = true
cell.editButton.isHidden = true
cell.deleteButton.isHidden = true
cell.addresslabel.removeFromSuperview()
cell.mobileNumberlabel.removeFromSuperview()
cell.nameLabel.text = "No address available"
if delayCheck == true {
let when = DispatchTime.now() + 5 // change 2 to desired number of seconds
DispatchQueue.main.asyncAfter(deadline: when) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let addtoCartVC = storyboard.instantiateViewController(withIdentifier: "newAddress") as! NewAddressViewController
self.navigationController?.pushViewController(addtoCartVC, animated: true)
}
}
}
else {
cell.addressLabel.isHidden = false
cell.radioButton.isHidden = false
cell.editButton.isHidden = false
cell.deleteButton.isHidden = false
cell.nameLabel.isHidden = false
cell.nameLabel.text = "\((dict?.firstName)!) \((dict?.lastName)!)"
cell.addressLabel.text = "\((self.street)!) \((dict?.city)!) \((dict?.region)!) \((dict?.postCode)!)"
cell.mobileNumberLabel.text = "\((dict?.telephone)!)"
}
cell.radioButton.tag = indexPath.row
cell.editButton.tag = indexPath.row
cell.deleteButton.tag = indexPath.row
cell.editButton.isHidden = true
cell.deleteButton.isHidden = true
cell.radioButton.addTarget(self, action: #selector(selectRadioButton(_:)), for: .touchUpInside)
cell.deleteButton.addTarget(self, action: #selector(deleteAction(button:)), for: .touchUpInside)
cell.editButton.addTarget(self, action: #selector(editButtonAction(_:)), for: .touchUpInside)
let checkIndex = self.checkIsRadioSelect.index(of: indexPath.row)
if(checkIndex != nil) {
cell.radioButton.isSelected = true
cell.editButton.isHidden = false
cell.deleteButton.isHidden = false
}
else
{
cell.radioButton.isSelected = false
cell.editButton.isHidden = true
cell.deleteButton.isHidden = true
}
if (checkIsPaymentRadioSelect == true) {
let defaultvalue = street
if defaultvalue?.isEmpty == false {
cell.radioButton.isSelected = true
cell.editButton.isHidden = false
cell.deleteButton.isHidden = false
addressSelected = true
}
}
return cell
}
instead of remove address label and mobile label from superview set the height constraint for that labels and change the height constant to 0 to hide the labels and make the cell height dynamic like below.
tblList.rowHeight = UITableViewAutomaticDimension;
tblList.estimatedRowHeight = CGFloat(100)
Man, don't write so many code in dataSource method of UITableViewDataSource. It's very hard to handle and understand what is really going on! There is sooo many strange logic I don't really understand. So I recommend you
to make all configuration in UITableViewCell subclass (AddressTableViewCell & NewAddressViewController in your case)
Models is better to write like structs / classes but not dictionary
activityIndicator shouldn't be handled in cellForRow method. It should change depending on request whether it's still processing / ended / failed and so on.
If you are doing something like cell.radioButton.tag = indexPath you are doing something wrong cause you have method for tableView .indexPath(forCell: UICollectionViewCell). Which can say you exact indexPath
If you handle some touches from cell - you should make delegate of the cell which will send to your ViewController the cell where touch occurred like
func selectRadioButton(inCell cell: UICollectionViewCell) {
guard let indexPath = tableView.indexPath(forCell: cell) else { return }
// do what you need to do & you have your indexPath! = know where touch occurred
}
Ending my lecture - your source should fully define your cell. It means if you have some flag in the model which indicate on whether you display something or not you should write something like:
yourView.isHidden = !yourModel.isDisplayed
Why? Because cell are reused and all the subclass properties retain their state that is all UI "settings" (visibility and others remain). Read about it in google there are a lot of articles on this theme which did it better then I am
Hope I helped you!

How to get the indexPath of the current cell

I have currently trying to create a way to delete an item in a collection view at the indexPath of 1 back. So far i have used some help to create a function with scrollview did scroll to create a way to count which image the user is on by the current image method. I now need a way to count which cell the user is on. Here is my code>
var currentImage = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let x = floor(myCollectionView.contentOffset.x / view.frame.width)
if Int(x) != currentImage {
currentImage = Int(x)
//print(currentImage)
}
if currentImage > 0 {
for collectionCell in myCollectionView.visibleCells as [UICollectionViewCell] {
let indexPath = myCollectionView.indexPath(for: collectionCell as UICollectionViewCell)!
let indexPathOfLastItem = (indexPath?.item)! - 1
let indexPathOfItemToDelete = IndexPath(item: (indexPathOfLastItem), section: 0)
imageArray.remove(at: 0)
myCollectionView.deleteItems(at: [indexPathOfItemToDelete])
currentImage = 1
Based more on the comments than your actual question, what you seem to want is to get the first visible cell's index path so you can use that path to delete the cell.
let visibleCells = myCollectionView.visibleCells
if let firstCell = visibleCells.first() {
if let indexPath = myCollectionView.indexPath(for: collectionCell as UICollectionViewCell) {
// use indexPath to delete the cell
}
}
None of this should be used or done in the scrollViewDidScroll delegate method.
[Updated for Swift 5.2] Here's a slightly more succinct way than #rmaddy's answer (but that one works just fine):
guard let indexPath = collectionView.indexPathsForVisibleItems.first else {
return
}
// Do whatever with the index path here.

Send Row and Section through Tag in Button Swift

I have this inside cellForRowAtIndexPath
cell.plusBut.tag = indexPath.row
cell.plusBut.addTarget(self, action: "plusHit:", forControlEvents: UIControlEvents.TouchUpInside)
and this function outside:
func plusHit(sender: UIButton!){
buildings[sender.tag].something = somethingElse
}
Is it possible to send the indexPath.row and indexPath.section, or some alternative??
Thanks!
EDIT
I approached it like this:
My Custom Button
class MyButton: UIButton{
var myRow: Int = 0
var mySection: Int = 0
}
My Custom Cell
class NewsCell: UITableViewCell{
#IBOutlet weak var greenLike: MyButton!
In CellForRowAtIndexPath
cell.greenLike.myRow = indexPath.row
I get an error on this line.
Is it possible to send the indexPath.row and indexPath.section, or some alternative??
If you impose a limit on the number of possible rows in a section, you can combine the two into a single number. For example, if you're willing to say that there will always be fewer than 1000 rows in a given section, you can use:
cell.plusBut.tag = (indexPath.section * 1000) + indexPath.row
and then use mod and division operators to recover:
row = sender.tag % 1000
section = sender.tag / 1000
Another possibility is to check the button's superview (and it's superview, etc.) until you find the cell. Once you have the cell, you can get the index path for that cell from the table.
A third option, perhaps the best one, is to have the button target the cell rather than some other object. The cell can then trigger an action in the view controller or other object using itself as sender.
You can create a subclass of UIButton and create an extra property section in it. And then you can use that class for cell button. You can do the same for row.
Here are few possible ways after subclassing UIButton
cell.plusBut.tag = indexPath.row
cell.plusBut.section = indexPath.section
Or
cell.plusBut.row = indexPath.row
cell.plusBut.section = indexPath.section
Or
cell.plusBut.indexPath = indexPath
Choose whatever suits you.
Here's a really easy solution I use:
in CellForRow:
button.tag = indexPath.row
cell.contentView.superview?.tag = indexPath.section
in the function retrieve it like this:
func buttonTapped(_ sender: Any) {
let button = sender as! UIButton
let row = button.tag
let sec = button.superview?.tag
//retrieve item like self.array[sec][row]
//do the rest of your stuff here
}
Swift 2.x - Extension with Associated Objects
private struct AssociatedKeys {
static var section = "section"
static var row = "row"
}
extension UIButton {
var section : Int {
get {
guard let number = objc_getAssociatedObject(self, &AssociatedKeys.section) as? Int else {
return -1
}
return number
}
set(value) {
objc_setAssociatedObject(self,&AssociatedKeys.section,Int(value),objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
var row : Int {
get {
guard let number = objc_getAssociatedObject(self, &AssociatedKeys.row) as? Int else {
return -1
}
return number
}
set(value) {
objc_setAssociatedObject(self,&AssociatedKeys.row,Int(value),objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
usage:
//set
cell.contextMenuBtn.section = indexPath.section
cell.contextMenuBtn.row = indexPath.row
//read
func showContextMenu (sender:UIButton) {
let track = dictionarySongs[sender.section][sender.row]
...
}
Send Row and Section through in Button you can use tag and accessibilityIdentifier, take a look:
Set value
button.tag = indexPath.row
button.accessibilityIdentifier = "\(indexPath.section)"
Get Value
let indexRow = button.tag
let indexSection = Int(button.accessibilityIdentifier!)
I suggest another approach. I don't like the subclass solution, I don't think the button should know its position.
Why not use a dictionary to translate the button into the IndexPath
// First initialize the lookup table
var buttonPositions = [UIButton: NSIndexPath]()
// add each button to the lookup table
let b = UIButton()
let path = NSIndexPath(forItem: 1, inSection: 1)
buttonPositions[b] = path
// Looking it up works like this
buttonPostions[activeButton]?.row
You can assign a custom tag for a UIButton via extension, take a look:
extension UIButton {
private struct ButtonTag {
static var tagKey = "key"
}
var myTag : (Int , Int)? {
set {
if let value = newValue {
objc_setAssociatedObject(self, &ButtonTag.tagKey, value as! AnyObject, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
get {
return objc_getAssociatedObject(self, &ButtonTag.tagKey) as? (Int , Int)
}
}
}

Resources