Uibutton Image for Normal State in Collectionview Cell Repeats Itself Every Four Cells

UIButton image for normal state in collectionview cell repeats itself every four cells

Every time your cell is dequeued cellForItemAt is called. This is the place where you configure your cell data. So if you need to show cell marked as favourite, you can do it here.

So how do you do it there? Let's say all your cells are not selected in the beginning. Fine. You don't have to say anything in cellForItemAt. Now let's say you mark a few cells as favourite. What happens here is, it will reflect the change when the cell is visible because the button is hooked to a selector which will make the changes.

Now here is the problem. When you scroll and the cell disappears, the information about your cell being marked as favourite is lost! So what you need to do, is maintain an array which will store the IndexPath of all the selected cells. (Make sure to remove the IndexPath when a cell is removed from favourite!) Let's call that array favourites. If you can use your data source for the collection view to store the selected state information that is also fine. Now you have to store the information about whether your cell is marked as favourite in your button selector.

@objc func buttonTapped() {
if favourites.contains(indexPath) { // Assuming you store indexPath in cell or get it from superview
favourites.removeAll(where: {$0 == indexPath})
} else {
favourites.append(indexPath)
}
}

After you have stored the information about the cell, every time you dequeue a cell, you need to check if the IndexPath is favourites. If it is, you call a method which sets the cell in the selected state.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Dequeue cell and populating cell with data, etc
if favourites.contains(indexPath) {
cell.showFavourite()
}
}

Done? No! Now we have another problem. This problem is associated with the reuse of the cell. So what happens in cellForItemAt actually? You dequeue a cell and use it to display information. So when you dequeue it what happens is, it might have already been used for showing some other information in some other index path. So all the data that was existing there will persist. (Which is why you have the problem of favourites repeating every 4 cells!)

So how do we solve this? There is method in UICollectionViewCell which is called before a cell is dequeued - prepareCellForReuse. You need to implement this method in your cell and remove all the information from the cell, so that it is fresh when it arrives at cellForItemAt.

func prepareForReuse() {
//Remove label text, images, button selected state, etc
}

Or you could always set every value of everything inside the cell in cellForItemAt so that every information is always overwritten with the necessary value.

Edit: OP says he has a collection view inside a collection view. You can identify which collection view is called like this,

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

if collectionView === favoriteCollectionView { // This is the collection view which contains the cell which needs to be marked as favourite
// Dequeue cell and populating cell with data, etc
if favourites.contains(indexPath) {
cell.showFavourite()
}
return cell
}
// Dequeue and return for the other collectionview
}

Why is that? Button toggles the state on a custom cell whose background image is reused by other cells

I've solved the problem,
First, define an array of global records that clicked over the button

    lazy var numbercells:[Int] = []

Secondly, in the

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell : FoneCell = tableView.dequeueReusableCell(withIdentifier: "Focid", for: indexPath) as! FoneCell;

if self.numbercells.contains(indexPath.row){
cell.loveBtn.isSelected = true;
}else{
cell.loveBtn.isSelected = false;

}
cell.loveBtn.addTarget(self, action: #selector(LickCheck), for: UIControl.Event.touchUpInside);
cell.loveBtn.tag = indexPath.row;

return cell;

}

The last

    @objc func LickCheck(_ sender:UIButton){

if !sender.isSelected {
self.numbercells.append(sender.tag);
addDate(sender.tag)
}else{
self.numbercells = self.numbercells.filter(){$0 != sender.tag}
deleteDate(sender.tag)
}
let posinton = IndexPath(row: sender.tag, section: 0);
self.tableView.reloadRows(at: [posinton], with: UITableView.RowAnimation.none)

}

This solves the button select state reuse problem on the cell

Hiding a specific cell hides random cells

Its because cell was reused.

Always keep in mind, when you use if in cellForRowAtIndexPath, you must always implement else part.

if (indexPath.row == 3){
cell.hidden = YES;
} else {
cell.hidden = NO;
}

or

cell.hidden = indexPath.row == 3;

Deleting Favorited Cell

Use NotificationCenter from your list view controller to alert other view controller for change button style.

Issue in increase height of cell on Read more UIButton

@Дмитрий Деникаев.

I have found Solution. You can check my working demo project here..

1) You need to set UILabel property setNumberOfLines = 0.

Sample Image

2) Create two @IBOutlet constraint for view increase and decrease and set its priority. ex. priority = 750 and priority = 250(vice versa).

  • first constrain for label fix hight and its priority in story board is 750.

    Sample Image

  • second constrain for label bottom to its superview and its priority in story board is 250.

    Sample Image

**look into following code **

In ViewTableViewCell.swift

  import UIKit

class ViewTableViewCell: UITableViewCell {


@IBOutlet var fixedHeightCon : NSLayoutConstraint!
@IBOutlet var graterHeightCon : NSLayoutConstraint!
@IBOutlet weak var lblText : UILabel!
@IBOutlet weak var btnMore: UIButton!
override func awakeFromNib() {
super.awakeFromNib()

}

override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)

// Configure the view for the selected state
}

}

in ViewController.swift

 import UIKit

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrData.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : ViewTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ViewTableViewCell
cell.btnMore.tag = indexPath.row
cell.lblText.text = arrData[indexPath.row]
cell.layoutSubviews()
return cell

}

@IBOutlet weak var tblView: UITableView!
var arrData = ["This is long description.This is long description.This is long description.This is long description.This is long description.This is long description.This is long description.This is long description.This is long description.This is long description.This is long description.This is long description.This is long description.This is long description.","This is long description123.This is long description123.This is long description123.This is long description123.This is long description123.This is long description123.This is long description123.This is long description123.This is long description123.This is long description123.This is long description123.This is long description123.This is long description123."]
override func viewDidLoad() {
super.viewDidLoad()
tblView.tableFooterView = UIView()
tblView.rowHeight = UITableView.automaticDimension
tblView.estimatedRowHeight = 77
tblView.delegate = self
tblView.dataSource = self
// Do any additional setup after loading the view, typically from a nib.
}

@IBAction func changelabelHeight(sender:UIButton){
let indexpath = NSIndexPath(row: sender.tag, section: 0)
let cell = self.tblView!.cellForRow(at: indexpath as IndexPath) as? ViewTableViewCell

if(cell!.fixedHeightCon.priority == UILayoutPriority(rawValue: 750)){
cell!.btnMore.setTitle("Show Less", for: UIControl.State.normal)
cell!.fixedHeightCon.priority = UILayoutPriority(rawValue: 250)
cell!.graterHeightCon.priority = UILayoutPriority(rawValue: 750)



}else{
cell!.btnMore.setTitle("Read More", for: UIControl.State.normal)
cell!.fixedHeightCon.priority = UILayoutPriority(rawValue: 750)
cell!.graterHeightCon.priority = UILayoutPriority(rawValue: 250)

}
tblView.reloadData()

}
}

I hope this answer helpful to you. Happy Coding :)

Accessing IBOutlet from subclass, outlets cannot be connected to repeating content subclass

You set the button properties (such as Title) in cellForItemAt.

Side note: using Subclass as the name of your class will be very confusing.

Here's a quick example:

class MyButtonCell: UICollectionViewCell{
@IBOutlet weak var buttonOne: UIButton!

var callback: (() -> ())?

@IBAction func buttonTapped(_ sender: UIButton) {
callback?()
}
}

class TestCollectionViewController: UICollectionViewController {

let buttonTitles: [String] = [
"First", "Second", "Third", "etc..."
]

override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return buttonTitles.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCellID", for: indexPath) as! MyButtonCell
cell.buttonOne.setTitle(buttonTitles[indexPath.item], for: [])
cell.callback = {
print("Button was tapped at \(indexPath)")
// do what you want when the button is tapped
}
return cell
}
}

Notice that I also added an @IBAction for the button inside the cell subclass. I also added this var / property:

var callback: (() -> ())?

That makes it easy to set up a closure in your controller code - again, in cellForItemAt - allowing your controller to handle and act when the button in a cell is tapped.


Edit

Here is a complete implementation:

class MyButtonCell: UICollectionViewCell{
@IBOutlet weak var buttonOne: UIButton!

var callback: ((UICollectionViewCell) -> ())?

override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.layer.borderWidth = 1
contentView.layer.borderColor = UIColor.black.cgColor
}

@IBAction func buttonTapped(_ sender: UIButton) {
callback?(self)
}
}

class StevenViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

let buttonTitles: [String] = [
"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"
]

@IBOutlet var collectionView: UICollectionView!

override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return buttonTitles.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCellID", for: indexPath) as! MyButtonCell

// set the button title (and any other properties)
cell.buttonOne.setTitle(buttonTitles[indexPath.item], for: [])

// set the cell's callback closure
cell.callback = { [weak self] theCell in
guard let self = self,
let indexPath = collectionView.indexPath(for: theCell)
else { return }
print("Button was tapped at \(indexPath)")
// do what you want when the button is tapped
}

return cell
}
}

And here's the Storyboard source - if you haven't done this before, create a new Storyboard, select Open As -> Source Code, delete what's there, copy and paste the following... then you can select Open As -> Interface Builder -Storyboard:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="81J-PL-nYH">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="collection view cell content view" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Steven View Controller-->
<scene sceneID="UOc-kB-D4u">
<objects>
<viewController id="81J-PL-nYH" customClass="StevenViewController" customModule="FirstNewMini" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="5pE-V8-AOY">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="eKk-rE-Fyn">
<rect key="frame" x="20" y="567" width="335" height="60"/>
<color key="backgroundColor" red="0.99942404029999998" green="0.98555368190000003" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="60" id="t8R-1V-ELi"/>
</constraints>
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" automaticEstimatedItemSize="YES" minimumLineSpacing="10" minimumInteritemSpacing="10" id="34u-td-lg8">
<size key="itemSize" width="60" height="60"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="myCellID" id="slK-WV-d6z" customClass="MyButtonCell" customModule="FirstNewMini" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="inr-uV-Mkf">
<rect key="frame" x="0.0" y="0.0" width="60" height="60"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="jO2-43-Y97">
<rect key="frame" x="7" y="15" width="46" height="30"/>
<color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="Button"/>
<connections>
<action selector="buttonTapped:" destination="slK-WV-d6z" eventType="touchUpInside" id="ILI-g9-h3v"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="jO2-43-Y97" firstAttribute="centerY" secondItem="inr-uV-Mkf" secondAttribute="centerY" id="K8s-Jl-sfY"/>
<constraint firstItem="jO2-43-Y97" firstAttribute="centerX" secondItem="inr-uV-Mkf" secondAttribute="centerX" id="kF4-R0-H31"/>
</constraints>
</collectionViewCellContentView>
<connections>
<outlet property="buttonOne" destination="jO2-43-Y97" id="EkG-Dz-2DC"/>
</connections>
</collectionViewCell>
</cells>
</collectionView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="OxZ-Gp-ekf" firstAttribute="trailing" secondItem="eKk-rE-Fyn" secondAttribute="trailing" constant="20" id="Gua-Zi-IFQ"/>
<constraint firstItem="OxZ-Gp-ekf" firstAttribute="bottom" secondItem="eKk-rE-Fyn" secondAttribute="bottom" constant="40" id="I6q-K5-nN3"/>
<constraint firstItem="eKk-rE-Fyn" firstAttribute="leading" secondItem="OxZ-Gp-ekf" secondAttribute="leading" constant="20" id="sCP-Nn-RqH"/>
</constraints>
<viewLayoutGuide key="safeArea" id="OxZ-Gp-ekf"/>
</view>
<connections>
<outlet property="collectionView" destination="eKk-rE-Fyn" id="rxw-DZ-Kpi"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Ap0-Qs-M9q" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="199.19999999999999" y="155.17241379310346"/>
</scene>
</scenes>
</document>

The result when you run it should look like this (I gave the collection view a yellow background to make it easy to see the frame):

Sample Image



Related Topics



Leave a reply



Submit