UIswitch duplicate issue in table cell - ios

In my App, I have a tableview with 7 sections and each section has 8cells containing Stepper(in UIView),UIButton(in UIView) and UISwitch(in UIview).
Now I am able to set and get values for stepper and UIButton But my issue is with UISwitch.
It is getting reused and shows On/Off states simultaneously(wiered UI).
Also I have maintained an Array to save status of switch but it doesnot reflect properly.
Below is the code
var myData1 : [Bool] = [false,false,false,false,false,false,false,false]
let cell:DefaultTableViewCell = (self.tblviewSwitchPOints.dequeueReusableCell(withIdentifier: "defaultcell", for: indexPath) as? DefaultTableViewCell)!
cell.viewForButton.tag = indexPath.row
cell.selectionStyle = .none
cell.viewForSwitch.tag = indexPath.row
print("in cell for row")
if indexPath.section == 0
{
switch indexPath.row
{
case 0:
cell.lblMain.text = timePerDay[indexPath.row]
cell.lblTemp.text = row1[0]
cell.lblTime.text = row1[1]
cell.lblMain.tag = indexPath.row
cell.viewForStepper.addSubview(valueStepper1)
cell.viewForButton.addSubview(btnCustom1)
cell.viewForSwitch.addSubview(switchOnOff1)
if myData1.count==0
{
}else
{
switchOnOff1.isOn = myData1[0]
}
switchOnOff1.tag = 0
switchOnOff1.addTarget(self,action:#selector(actionOnOff1(sender:)), for:.valueChanged )
let temp = arrTemp1[indexPath.row]
if arrTimeInDouble1.count==0
{
}else if temp.elementsEqual("FFFF")
{
valueStepper1.value = 5
}else
{
valueStepper1.value = arrTimeInDouble1[indexPath.row]
}
valueStepper1.tag = indexPath.row
btnCustom1.tag = indexPath.row
if arrTime1.count==0
{
}else
{
btnCustom1.titleLabel?.text = arrTime1[indexPath.row]
}
valueStepper1.addTarget(self, action: #selector(valueChangedTemp1(sender:)), for: .valueChanged)
btnCustom1.addTarget(self, action:#selector(datePickerTapped1), for: .touchUpInside)
print("in cell fro row1\(myData1)\(switchOnOff3.isOn)\(switchOnOff1.tag)")
return cell
case 1:
cell.lblMain.text = timePerDay[indexPath.row]
cell.lblTemp.text = row2[0]
cell.lblTime.text = row2[1]
cell.lblMain.tag = indexPath.row
cell.viewForStepper.addSubview(valueStepper2)
cell.viewForSwitch.addSubview(switchOnOff2)
if myData1.count==0
{
}else
{
switchOnOff2.isOn = myData1[1]
}
switchOnOff2.tag = indexPath.row
if arrTimeInDouble1.count==0
{
}else
{
valueStepper2.value = arrTimeInDouble1[indexPath.row]
}
cell.viewForButton.addSubview(btnCustom2)
btnCustom2.tag=indexPath.row
valueStepper2.tag = indexPath.row
if arrTime1.count==0
{
}else
{
btnCustom2.titleLabel?.text = arrTime1[indexPath.row]
}
switchOnOff2.addTarget(self, action: #selector(actionOnOff1(sender:)), for:.valueChanged )
valueStepper2.addTarget(self, action: #selector(valueChangedTemp1(sender:)), for: .valueChanged)
btnCustom2.addTarget(self, action:#selector(datePickerTapped1), for: .touchUpInside)
return cell
}

A lot may have gone wrong but what I suspect in your case is that each of your switches has multiple target/action pairs assigned; You dequeue a cell and then call addTarget on switches where a previous target is still on the switch.
In any case what is best done when dealing in these situations is to put most of your code into your table view cell. I actually suggest you to use delegate to get messages back from cell to your view controller so dequeuing should look something like this:
let cell: DefaultTableViewCell = tableView.dequeueReusableCell(withIdentifier: "defaultcell", for: indexPath) as! DefaultTableViewCell
cell.myObject = myObjectSections[indexPath.section][indexPath.row]
cell.delegate = self
return cell
Nothing else. Now cell should do all the logic:
class DefaultTableViewCell: UITableViewCell {
weak var delegate: DefaultTableViewCellDelegate?
var myObject: MyObject? {
didSet { refresh() }
}
override func awakeFromNib() {
super.awakeFromNib()
self.switch.addTarget(self, action:#selector(switchMoved)), for:.valueChanged)
}
func refresh() {
// Change all UI related stuff here
}
#objc private func switchMoved() {
if let myObject = myObject {
delegate?.defaultTableViewCell(self, didChangeWhateverStateOf:myObject to: switch.on)
}
}
}
Naturally a protocol needs to be defined appropriately. For my case that is
protocol DefaultTableViewCellDelegate: class {
func defaultTableViewCell(_ sender: DefaultTableViewCell, didChangeWhateverStateOf myObject: MyObject, to flag: Bool)
}
and your view controller needs to implement this protocol.
Sometimes it may actually make sense to add an index path with the data as well. To do so you simply upgrade this code so cell also contains var indexPath: IndexPath!. Now after dequeuing a cell you assign its new index path. And then in your delegate method you can access it at any time:
extension MyViewController: DefaultTableViewCellDelegate {
func defaultTableViewCell(_ sender: DefaultTableViewCell, didChangeWhateverStateOf myObject: MyObject, to flag: Bool) {
print("This is cell at section \(sender.indexPath.section), row: \(sender.indexPath.row)")
}
}

Would be good if you create a custom cell with UISwitch in it. Then you can assign value to the switch like this -
var cell : SwitchTableViewCell? = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier") as? SwitchTableViewCell
if(cell == nil)
{
cell = SwitchTableViewCell(style: .default, reuseIdentifier: "CellIdentifier")
}
if(indexPath.section == 0)
{
if(myData1.count != 0)
{
cell?.switchOnOff.isOn = myData1[indexPath.row];
}
}
return cell!;

Related

Facebook native Ads are not clickable again in UITableView Cells after scroll

I have implemented facebook native Ads in UITableView, for first 1-2 times it clickable but when I scroll tableview and come again back to the same cell, now Ads are not clicking, I am using swift 3.2
Below is the cell implementation.
let ad = adsManager.nextNativeAd
let cell = self.tableHome.dequeueReusableCell(withIdentifier: "HomeAdsTableViewCell", for: indexPath) as! HomeAdsTableViewCell
cell.message.text = ad?.body
cell.title.text = ad?.title
cell.callToActionButton.setTitle(ad?.callToAction, for: .normal)
if let pic = ad?.coverImage {
cell.postImage.setImageWithIndicator(imageUrl:pic.url.absoluteString)
}
ad?.registerView(forInteraction: cell.postView, with: self)
cell.selectionStyle=UITableViewCellSelectionStyle.none
return cell
//create a new object of nativead in your class//
var previousNativead : FBNativeAd?
let ad = adsManager.nextNativeAd
self.previousNativead?.unregisterView()
let cell = self.tableHome.dequeueReusableCell(withIdentifier: "HomeAdsTableViewCell", for: indexPath) as! HomeAdsTableViewCell
cell.message.text = ad?.body
cell.title.text = ad?.title
cell.callToActionButton.setTitle(ad?.callToAction, for: .normal)
if let pic = ad?.coverImage {
cell.postImage.setImageWithIndicator(imageUrl:pic.url.absoluteString)
}
previousNativead = ad
ad?.registerView(forInteraction: cell.postView, with: self)
cell.selectionStyle=UITableViewCellSelectionStyle.none
return cell
I suggest you follow the steps here. He told me about putting a facebook ad between cells.
https://www.appcoda.com/facebook-ads-integration/
It's like you do not make mistakes in the place you turn the idiot.
You should adapt this part in the link to your own.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if adsCellProvider != nil && adsCellProvider.isAdCellAtIndexPath(indexPath, forStride: UInt(adRowStep)) {
return adsCellProvider.tableView(tableView, cellForRowAtIndexPath: indexPath)
}
else {
let cell = tableView.dequeueReusableCellWithIdentifier("idCellSample", forIndexPath: indexPath) as! SampleCell
cell.lblTitle.text = sampleData[indexPath.row - Int(indexPath.row / adRowStep)]
return cell
} }
You should also use this method on screen refreshes and turns.
func configureAdManagerAndLoadAds() {
if adsManager == nil {
adsManager = FBNativeAdsManager(placementID: "PLACEMENT_ID", forNumAdsRequested: 5)
adsManager.delegate = self
adsManager.loadAds()
}}
override func viewWillAppear(animated: Bool) {
configureAdManagerAndLoadAds()}
Finally you should check the relevant fields, which may not be compatible with content swift 3.
First in your viewDidLoad() check that you add
override func viewDidLoad() {
super.viewDidLoad()
adsManager = FBNativeAdsManager(placementID: "YOUR_PLACEMENT_ID_HERE", forNumAdsRequested: "YourNumAds")
adsManager.delegate = self
adsManager.loadAds()
}
then to comply with the FBNativeAdsManagerDelegate, you will need to add the following method, nativeAdsLoaded().
func nativeAdsLoaded() {
adsCellProvider = FBNativeAdTableViewCellProvider(manager: adsManager, forType: FBNativeAdViewType.genericHeight300)
adsCellProvider.delegate = self
if self.tableview != nil {
self.tableview.reloadData()
}
}
the last thing you should do is to registerView directly to the cell not to postView
ad?.registerView(forInteraction: cell, with: self)
hope that will help you.

How to change the view and at the same time still let the user edit in the TextField?

So I'm having an array of phone numbers and I iterate through it, if one from the array is the same as the one in the textField, I will reload the table row and show some image with number ok and stuff like that. Else let's say if the user now changes that number which was good, like deletes a decimal, now the number no longer matching the ones from the array so I need to change that cell view.But this current code reloading my table for every change in the textField and by reloading it won't stay anymore in editing mode.
#objc func textFieldValueCahnged(_ textField: UITextField) {
if textField.tag == 0 {
if !shouldValidatePhone {
for phone in self.phoneNumbers {
if phone == textField.text! {
self.phoneNumber = phone
GlobalMainQueue.async {
self.phoneValidated = true
// self.reloadCellForIdentifier("ExtraFieldCell")
self.mainTableView.reloadRows(at: [self.rowindexpath!], with: .fade)
}
break
} else {
GlobalMainQueue.async {
self.phoneValidated = false
self.phoneNumber = textField.text!
self.mainTableView.reloadRows(at: [self.rowindexpath!], with: .fade)
// TODO: IF IT 'S typed a different number that the ones in self.phoneNumbers array we need to update the view with the cell.elementsAreHidden()
}
break
}
}
}
} else {
verifyCode = textField.text!
}
}
the tableView cell:
let cell = tableView.dequeueReusableCell(withIdentifier: "PhoneValidationCell") as! PhoneValidationCell
self.rowindexpath = indexPath
cell.label.text = "\(currentField.label):"
cell.textField.addTarget(self, action: #selector(textFieldValueCahnged(_:)), for: .editingChanged)
cell.sendCodeBtn.addTarget(self, action: #selector(sendCodeButtonPressed(_:)), for: .touchUpInside)
cell.textField.tag = 0
cell.sendCodeBtn.tag = 0
cell.textFieldForVeryfing.addTarget(self, action: #selector(textFieldValueCahnged(_:)), for: .editingChanged)
cell.verifyCodeBtn.addTarget(self, action: #selector(sendCodeButtonPressed(_:)), for: .touchUpInside)
cell.textFieldForVeryfing.tag = 1
cell.verifyCodeBtn.tag = 1
if self.phoneNumbers.isEmpty {
if let phoneNumbers = currentField.userPhones {
self.phoneNumbers = phoneNumbers
}
}
if !phoneValidated && !shouldValidatePhone {
if let value = self.extraFieldsValues[currentField.name] as? String {
for i in phoneNumbers {
if i == value {
cell.phoneVerified()
break
} else {
cell.elementsAreHidden()
}
}
if phoneNumbers.isEmpty {
cell.elementsAreHidden()
}
cell.label.textColor = UIColor.black
cell.textField.text = value
self.phoneNumber = value
} else {
cell.label.textColor = Theme.placeholderColor()
cell.textField.text = ""
}
} else {
// cell.phoneVerified() /// check here
cell.textField.text = self.phoneNumber
}
if shouldValidatePhone && !phoneValidated {
cell.phoneToBeVerified()
}
if phoneValidated && shouldValidatePhone {
cell.phoneVerified()
}
basically when phoe number is in the textField I'm showing cell.elementsAreHidden() . when number needs to be validated cell.phoneToBeVerified() . and when it s verified cell.phoneVerified()
You should store the indexPath of the cell you are editing and focus again on it's textfield after the reload by calling cell.textField.becomeFirstResponder().
For example:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath == self.rowindexpath {
(cell as? ProjectTableViewCell)?.textField.becomeFirstResponder()
}
}

Multiple cell selection with search

I've created a UITableView which contains a list of teams which all have a accessoryView. This accessoryView indicates whether a cell is selected or not. Each cell is connected with a custom Object in my Team class.
When a cell is selected it then saves the particular team Object in a teamSelected array.
All this works fine. However there seem to be a issue when search and then filter the data and select a cell it seem to add the wrong object and change the accessoryView on a wrong object to?
How come it does to not add the correct object on cell selection when the searchBar is active?
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("teamCell", forIndexPath: indexPath) as! TeamCell
if (self.teamSearchController.active) {
cell.textLabel?.text = filteredTableData[indexPath.row].name
} else {
cell.textLabel?.font = UIFont(name: "HelveticaNeue-Light", size: 20)
cell.textLabel?.text = self.teamArray[indexPath.row].name as String
}
let team = self.teamArray[indexPath.row] as Team
var removed = false
for (index, value) in enumerate(self.teamSelected) {
if (value.id == team.id) {
cell.accessoryView = cell.accessoryCheck
removed = true
}
}
if (!removed) {
cell.accessoryView = cell.accessoryUncheck
}
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if (self.teamSearchController.active) {
team = self.filteredTableData[indexPath.row] as! Team
removed = false
} else {
team = self.teamArray[indexPath.row] as Team
removed = false
}
for (index, value) in enumerate(self.teamSelected) {
if (value.id == team.id) {
self.teamSelected.removeAtIndex(index)
removed = true
}
}
if (!removed) {
self.teamSelected.append(team)
}
var userDefaults = NSUserDefaults.standardUserDefaults()
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(self.teamSelected)
userDefaults.setObject(encodedData, forKey: "teams")
userDefaults.synchronize()
tableView.reloadData()
}
I have done this in objective c. I have also used uisearchview Controller. I have use this code. cellSelected is a nsmutable array. Use this code in didSelect method of table View.
if (tableView == self.searchDisplayController.searchResultsTableView)
{
if ([self.cellSelected containsObject:[self.searchArray objectAtIndex:indexPath.row][#"id"]])
{
[self.cellSelected removeObject:[self.searchArray objectAtIndex:indexPath.row][#"id"]];
}
else
{
[self.cellSelected addObject:[self.searchArray objectAtIndex:indexPath.row][#"id"]];
[self.selectedCategoryArray addObject:getSelectedCategory];
}
}
after this use this code in cellforrowatindexpath method
if (tableView == self.searchDisplayController.searchResultsTableView)
{
NSLog(#"self.searchArray..%#",self.searchArray);
titleName.text = [self.searchArray objectAtIndex:indexPath.row][#"name"];
if ([self.cellSelected containsObject:[self.searchArray objectAtIndex:indexPath.row][#"id"]])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
In the above code make else and use your actual array. hope this will help you. Thanks

CollectionView objects (Swift)

I want the highlight to change the size and appearance of an object inside the collection view.
How can I set object properties in a collection view cell, within the "didHighlight" method?
In "cellForItemAtIndexPath" you declare the reusable cells as the class
and just use "cell.MyOutlet.backgroundColor = UIColor.blueColor()"
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if collectionView == self.CollectionViewController {
let (FriendFirstName,FriendLastName) = friends[indexPath.row]
let cell: CustomCellA = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell", forIndexPath: indexPath) as! CustomCellA
if indexPath.section == 0 {
cell.cellTitle.text = Name
cell.imgCell.image = UIImage(named: Pics[indexPath.row])
cell.imgCell.layer.masksToBounds = true
cell.self.imgCell.layer.cornerRadius = 20
return cell
} else {
let cell2: AddCell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell2", forIndexPath: indexPath) as! AddCell
return cell2
}
} else if collectionView == self.EmojiCollectionViewController {
let cellB: CustomCellB = collectionView.dequeueReusableCellWithReuseIdentifier("demoCellB", forIndexPath: indexPath) as! CustomCellB
cellB.MyLabel.text = arrayOne[indexPath.row]
return cellB
} else {
let cellC: CustomCellC = collectionView.dequeueReusableCellWithReuseIdentifier("demoCellC", forIndexPath: indexPath) as! CustomCellC
// ...Set up cell
let height = self.CollectionViewController2.frame.height
cellC.frame = CGRectMake(cellB.frame.origin.x, 0, cellB.frame.size.width, height)
cellC.updateConstraintsIfNeeded()
cellC.layoutIfNeeded()
cellC.imgVw.image = UIImage(named: pictures[indexPath.row] as! String)
return cellC
}
}
func collectionView(collectionView: UICollectionView, didHighlightItemAtIndexPath indexPath: NSIndexPath) {
if collectionView == self.CollectionViewController {
if indexPath.section == 0 {
let cell: CustomCellA = CustomCellB()
cell.MyLabel.backgroundColor = UIColor.blueColor() //crashes due to nil value)
}
} else {
}
}
I tried using a similar definition in didHighlight and it keeps crashing.
Let didHighlightItemAtIndexPath only change the data, not the view. So, make friends[indexPath.row] an object or add another parameter to tuple. And in didHighlightItemAtIndexPath do something like the following:
if collectionView == self.CollectionViewController {
if indexPath.section == 0 {
let (fname, lname, color) = friends[indexPath.row];
friends[indexPath.row] = (fname, lname, UIColor.blueColor())
}
}
And in cellForItemAtIndexPath:
if collectionView == self.CollectionViewController {
let (FriendFirstName, FriendLastName, color) = friends[indexPath.row]
if indexPath.section != 0 {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell2", forIndexPath: indexPath) as! AddCell;
return cell;
} else if color == nil {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell", forIndexPath: indexPath) as! CustomCellA;
cell.cellTitle.text = Name
cell.imgCell.image = UIImage(named: Pics[indexPath.row])
cell.imgCell.layer.masksToBounds = true
cell.self.imgCell.layer.cornerRadius = 20
return cell
} else {
cell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCellB", forIndexPath: indexPath) as! CustomCellB;
// your code for CustomCellB
return cell;
}
}
EDIT: Updated, so instead of objects it uses tuples. Also added the functionality that you need. Basically, you need to create two prototype cells in the interface builder with different Reuse Identifiers and Classes. And then dequeue the correct identifier in the index path. Also, I refactored some of your code and if I were you I would create a different function for each collectionView and do something like:
if collectionView == self.CollectionViewController {
return self.dequeueCollectionCell(indexPath);
} else if collectionView == self.EmojiCollectionViewController {
return self.dequeuEmojiCell(indexPath);
} else {
return self.dequeueSomeOtherCell(indexPath);
}
Also, the code that you provided... I hope it is not an actual production code and you changed the values for this forum. Otherwise, in couple of days even, you are going to get lost in what is happening here. Too many inconsistent variable names and identifiers.
One more also. Use naming conventions in your class names. Read this forum post for more information. Apple uses camelCase everywhere. In majority of instances, the first letter is capitalized for class names, not object names.
first you have to define the collectionView Cell then do what ever you want on that cell. to define your sell add the below lines into didHighlightItemAtIndexPath
if let cellToUpdate = self.dataCollection.cellForItemAtIndexPath(indexPath) {
//your code here.
}

How to select button in UICollectionViewCell and retrieve state for another action in swift?

I have two buttons in a uicollectioncell and want to select one and then perform an action on the other depending on the state of the first button.
I have tried this but it throws up : "swift fatal error: unexpectedly found nil while unwrapping an Optional value"
func firstAction(sender:UIButton) {
var cell = Customcell()
cell.firstButton.selected = true
cell.firstButton.selected = !cell.firstButton.selected
}
#IBAction func secondAction(sender: UIBarButtonItem) {
var cell = CustomCell()
if cell.firstButton.selected == true {
// DO stuff....
} else {
println("No button selected")
}
}
I set the button in cellForItemAtIndexPath like this:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! CustomCell
cell.firstButton.addTarget(self, action: "firstAction:", forControlEvents: UIControlEvents.TouchUpInside)
cell.secondButton.addTarget(self, action: "secondAction:", forControlEvents: UIControlEvents.TouchUpInside)
How can I fix this?
In your action methods you create new instances of CustomCell instead of referencing to the cell which holds the buttons. Try this code:
func buttonAction (sender: UIButton) {
var cell = sender as UIView
while (!cell.isKindOfClass(UITableViewCell)) {
cell = cell.superview!
}
if sender == cell.firstButton {
// Do something when the first button was touched
} else if sender == cell.secondButton {
// Do something when the second button was touched
}
}
Add this function as the action for both buttons.

Resources