swift reuse cell custom view issue - ios

I have a custom view(tagListView) inside a custom tableview cell.
When I call addTag to cell.tagListView inside "cellForRowAt", it adds a tag for every cell.
How do I add tag only for that cell? I tried to keep a count so I only add tags to those which don't have tags. But it appears that this count is the same for all cells? I know this has something to do with reusable cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = topicListTableView.dequeueReusableCell(withIdentifier: "TopicListTableCell", for: indexPath)
if let myCell = cell as? TopicListTableCell {
if let model=topicListModel{
let t=model.topics[(indexPath.row)]
if let subject=t.subject{
let (tags,title)=util().getTag(subject: subject)
myCell.subject.text=title
if(myCell.tagCount==0){
myCell.tagList.addTags(tags)
myCell.tagCount+=1
}
}
myCell.content.text=t.content
myCell.authorTime.text=t.author
if let replynum=t.replies{
myCell.commentNum.text=String(replynum)
}
if let upvoteNum=t.score{
myCell.upVote.text=String(upvoteNum)
}
if indexPath.row%2==1{
myCell.background.backgroundColor=util().lightyellow
}else{
myCell.background.backgroundColor=util().darkeryellow
}
}
}
return cell
}
code for cell:
class TopicListTableCell: UITableViewCell{
#IBOutlet weak var content: UILabel!
#IBOutlet weak var upVote: UILabel!
#IBOutlet weak var commentNum: UILabel!
#IBOutlet weak var subject: UILabel!
#IBOutlet weak var authorTime: UILabel!
#IBOutlet weak var background: UIView!
#IBOutlet weak var tagList: TagListView!
var tagCount = 0
}

As dfd already stated, your approach isn't the right one because of the way UITableViews are working in iOS.
You should have one dataSource for your tableView (some array or dictionary) which contains all the information needed to present the cells the way you want.
So I would make a struct which contains all information I need to fill one cell and then make an array of these structs to fill all cells with the information they need.
Something like this:
// In your model:
struct TopicItem {
let topic: Topic
let tags: [Tag]
let title: String
}
var topicItems = [TopicItem]()
// This is function is only a draft, so you get a better idea what i mean
func updateTopicItems() {
topicItems.removeAll()
for topic in topics {
let tags: [Tag]
let title: String
if let subject = topic.subject {
// I would refactor this function, it is called getTag() but it returns tags and title. Kinda confusing
(tags,title) = util().getTag(subject: subject)
} else {
tags = [Tag]()
title = ""
}
topicItems.append(TopicItem(topic: topic, tags: tags, title: title))
}
}
I also have added some refactoring and comments which will hopefully help you to keep your code clean and maintainable in the future :)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// cell can be force unwrapped, it will never be something different than a TopicListTableCell, unless you change your code
// and even then you want a crash, so you find the error fast (crash will only occur during development)
let cell = topicListTableView.dequeueReusableCell(withIdentifier: "TopicListTableCell", for: indexPath) as! TopicListTableCell
guard let model = topicListModel else { return cell } // If there is no model, there is nothing to do
let topicItem = model.topicItems[(indexPath.row)]
let t = topicItem.topic
cell.subject.text = topicItem.title
// You should replace the addTags() function with a setTags() function, otherwise you will get more and more tags every time you present the cell
cell.tagList.addTags(topicItem.tags)
cell.content.text = t.content
cell.authorTime.text = t.author
if let replynum = t.replies {
cell.commentNum.text = String(replynum)
} else {
// Because cells are getting reused, you need to always set all fields, otherwise you get cells with content from other cells
cell.commentNum.text = ""
}
if let upvoteNum = t.score {
cell.upVote.text = String(upvoteNum)
} else {
// Again always set all fields
cell.upVote.text = ""
}
if indexPath.row %2 == 1 {
cell.background.backgroundColor=util().lightyellow
} else {
cell.background.backgroundColor=util().darkeryellow
}
return cell
}

Related

Swift iOS -How can I pass a value Obtained Directly from a within TableViewCell's SubClass to the TableView's didSelectRow?

Before anyone suggests to pull the Firebase data from within the PlayerController's viewWillAppear, I already know how to do that and if I did it that way I know how to pass the data to the ScoreController. In this situation I need to pull the data directly from within the cell and somehow pass the data back from there.
I have a tableView inside a PlayerController that displays the randomPower, name, and score of each player. Inside the tableView's cell I pull the name and score from Firebase using a function getScoreDataFromFirebase(). The function is located directly inside the tableView's PlayerCell and once I get the values from Firebase I initialize the cell's name and score outlets right then and there.
Inside the tableView's cellForRowAtIndexPath I call cell.getScoreDataFromFirebase() and everything works fine because both outlets display the correct values.
From that point on I have a ScoreController. When a tableView cell is chosen the score is sent to the ScoreController.
The problem is since I'm pulling the data directly from within the cell itself the only way I could pass the score (pulled from Firebase) to ScoreController was to 1st set a didSet score property inside the cell.
Still inside the cell when I pull the score data from Firebase 2nd I initialize the score property with it
3rd inside the tableView's cellForAtIndexPath I use an if let to pass the value from the cell's score property to the the tableData.
When I first try to send the indexPath of that tableData over to the ScoreController sometimes it's nil even though the cell's score property definitely has a value (I used to break points to check). If I select any of the very first few tableView cells that are visible they will have a nil value for the score property. However if I scroll further down through the cells and back up then those same cells will no longer have a nil score property.
What I found out was the if let statement was running before the Firebase code was pulled so the score property was nil for first few cells that are on scene. The odd thing is everything works fine once I start scrolling.
How can I pass a value pulled directly from a cell to the tableView's didSelectRow?
PlayerModel:
class PlayerModel{
name:String?
score:String?
randomPower:String?
}
TableViewCell SubClass:
class PlayerCell:UITableViewCell{
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var scoreLabel: UILabel!
#IBOutlet weak var randomPowerLabel: UILabel!
internal var score: String?{
didSet{
print("**********\(score ?? "*********")")
}
}
override func prepareForReuse() {
super.prepareForReuse()
nameLabel.text = " "
scoreLabel.text = " "
}
func getScoreDataFromFirebase(){
let scoreRef = usersRef?.child("score")
scoreRef?.observe( .value, with: {
(snapshot) in
for child in snapshot.children{
let user = child as! DataSnapshot
for player in user.children{
let eachPlayer = player as! DataSnapshot
if let dict = eachPlayer.value as? [String:Any]{
let name = dict["name"] as? String ?? " "
let score = dict["score"] as? String ?? " "
self.nameLabel.text = name
self.scoreLabel.text = score
self.score = score
}
}
}
}
}
}
TableView:
class PlayerController: UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak fileprivate var tableView: UITableView!
var players = [PlayerModel]() // this array is populated with data from a previous vc. The number of players in the array are the same exact number of players that's pulled from the getScoreDataFromFirebase() function
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return players.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerCell", for: indexPath) as! PlayerCell
let cellData = players[indexPath.row]
cellData.randomPowerLabel.text = cellData.power
cell.getScoreDataFromFirebase()
if let score = cell.score{
cellData.score = score
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let indexPath = tableView.indexPathForSelectedRow else { return }
let scoreVC = storyboard?.instantiateViewController(withIdentifier: "ScoreController") as! ScoreController
scoreVC.score = players[indexPath.row].score
}
You can achieve this using delegation :
Create a protocol
protocol UpdateValueDelegate: class {
func changeValue(score: String, row: Int)
}
Your UIViewController should look like this :
PlayController : UITableViewDataSource, UITableViewDelegate, UpdateValueDelegate
{
var scoreDict:[String:String] = [:]
//
//
func changeValue(score: String, row: Int)
{
self.scoreDict["\(row)"] = score
}
}
In cellForRowAtIndexPath set cell.delegate = self and cell.row = indexPath.row
Your UITableViewCell should look like this :
class PlayerCell:UITableViewCell: UITableViewCell
{
weak var delegate: UpdateValueDelegate?
var row: Int?
//
//
}
Finally pass score from getScoreDataFromFirebase by calling delegate function:
func getScoreDataFromFirebase()
{
//
//
delegate. changeValue(score: localScore, row: self.row)
}
Now you have the value in your viewController from where it can be easily passed to didSelectRow using the global dictionary ** scoreDict**.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
var score = self.scoreDict["\(indexPath.row)"]
// Use score
}

UITableView: reloadRows(at:) takes two hits for table to be updated and scroll every time

I have a table view (controller: MetricsViewController) which gets updated from a CoreData database. I have used prototype cells (MetricsViewCell) which I have customized for my needs. It contains a segmented control, a UIView (metricsChart, which is used to display a chart - animatedCircle), and some UILabels.
MetricsViewCell:
class MetricsViewCell: UITableViewCell {
var delegate: SelectSegmentedControl?
var animatedCircle: AnimatedCircle?
#IBOutlet weak var percentageCorrect: UILabel!
#IBOutlet weak var totalPlay: UILabel!
#IBOutlet weak var metricsChart: UIView! {
didSet {
animatedCircle = AnimatedCircle(frame: metricsChart.bounds)
}
}
#IBOutlet weak var recommendationLabel: UILabel!
#IBOutlet weak var objectType: UISegmentedControl!
#IBAction func displayObjectType(_ sender: UISegmentedControl) {
delegate?.tapped(cell: self)
}
}
protocol SelectSegmentedControl {
func tapped(cell: MetricsViewCell)
}
MetricsViewController:
class MetricsViewController: FetchedResultsTableViewController, SelectSegmentedControl {
func tapped(cell: MetricsViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
tableView.reloadRows(at: [indexPath], with: .none)
}
}
var container: NSPersistentContainer? = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer { didSet { updateUI() } }
private var fetchedResultsController: NSFetchedResultsController<Object>?
private func updateUI() {
if let context = container?.viewContext {
let request: NSFetchRequest<Object> = Object.fetchRequest()
request.sortDescriptors = []
fetchedResultsController = NSFetchedResultsController<Object>(
fetchRequest: request,
managedObjectContext: context,
sectionNameKeyPath: "game.gameIndex",
cacheName: nil)
try? fetchedResultsController?.performFetch()
tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Object Cell", for: indexPath)
if let object = fetchedResultsController?.object(at: indexPath) {
if let objectCell = cell as? MetricsViewCell {
objectCell.delegate = self
let request: NSFetchRequest<Object> = Object.fetchRequest()
...
...
}
}
}
return cell
}
When a user selects one of the segments in a certain section's segmented control, MetricsViewController should reload the data in that particular row. (There are two sections with one row each). Hence, I've defined a protocol in MetricsViewCell to inform inform my controller on user action.
Data is being updated using FetchedResultsTableViewController - which basically acts as a delegate between CoreData and TableView. Everything is fine with that, meaning I am getting the correct data into my TableView.
There are two issues:
I have to tap segmented control's segment twice to reload the data in the row where segmented control was tapped.
The table scrolls back up and then down every time a segment from segmented control is selected.
Help would be very much appreciated. I've depended on this community for a lot of issues I've faced during the development and am thankful already :)
For example, in Animal Recognition section, I have to hit "Intermediate" two times for its row to be reloaded (If you look closely, the first time I hit Intermediate, it gets selected for a fraction of second, then it goes back to "Basic" or whatever segment was selected first. Second time when I hit intermediate, it goes to Intermediate). Plus, the table scroll up and down, which I don't want.
Edit: Added more context around my usage of CoreData and persistent container.
Instead of using indexPathForRow(at: <#T##CGPoint#>) function to get the indexPath object of cell you can directly use indexPath(for: <#T##UITableViewCell#>) as you are receiving the cell object to func tapped(cell: MetricsViewCell) {} and try to update your data on the UI always in main thready as below.
func tapped(cell: MetricsViewCell) {
if let lIndexPath = table.indexPath(for: <#T##UITableViewCell#>){
DispatchQueue.main.async(execute: {
table.reloadRows(at: lIndexPath, with: .none)
})
}
}
Your UISegmentedControl are reusing [Default behaviour of UITableView].
To avoid that, keep dictionary for getting and storing values.
Another thing, try outlet connection as Action for UISegmentedControl in UIViewController itself, instead of your UITableViewCell
The below code will not reload your tableview when you tap UISegmentedControl . You can avoid, delegates call too.
Below codes are basic demo for UISegmentedControl. Do customise as per your need.
var segmentDict = [Int : Int]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0...29 // number of rows count
{
segmentDict[i] = 0 //DEFAULT SELECTED SEGMENTS
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SOTableViewCell
cell.mySegment.selectedSegmentIndex = segmentDict[indexPath.row]!
cell.selectionStyle = .none
return cell
}
#IBAction func mySegmentAcn(_ sender: UISegmentedControl) {
let cellPosition = sender.convert(CGPoint.zero, to: tblVw)
let indPath = tblVw.indexPathForRow(at: cellPosition)
segmentDict[(indPath?.row)!] = sender.selectedSegmentIndex
print("Sender.tag ", indPath)
}

Looping through phone numbers, creating a custom view and passing it the phone number in swift

I'm having trouble creating a view programatically inside a for loop from another controller. The parent controller is a tableviewcell and I'm looping through a bunch of phone numbers inside a CNContact object. For each phone number the contact has I wish to create my custom view and add it to the tableviewcell and have it stack vertically.
So far I managed to create the view and add it to the tableviewcell but wasn't able to pass the data. It's the passing of the data from one controller to another that I'm struggling with.
Here is my code:
ContactListTableViewCell.swift
import UIKit
import Contacts
class ContactListTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var phonenumberView: UIView!
func configureCell(contact: CNContact) {
titleLabel.text = "\(contact.givenName) \(contact.familyName)"
for phoneNumber in contact.phoneNumbers {
let view = self.createContactListTableViewTelephoneRow(telephone: phoneNumber)
self.phonenumberView.addSubview(view)
}
}
func createContactListTableViewTelephoneRow(telephone: Any) -> UIView {
let controller = ContactListTableViewTelephoneRow()
let view = UINib(nibName: "ContactListTableViewTelephoneRow", bundle: nil).instantiate(withOwner: controller, options: nil)[0] as! UIView
return view
}
}
contactListTableViewCell prototype inside Main.storyboard
ContactListTableViewTelephoneRow.swift
class ContactListTableViewTelephoneRow: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var telephoneLabel: UILabel!
#IBOutlet weak var telephoneTypeLabel: UILabel!
func setData(telephoneLabelText: String, telephoneTypeLabelText: String) {
telephoneLabel?.text = telephoneLabelText
telephoneTypeLabel?.text = telephoneTypeLabelText
}
}
ContactListTableViewTelephoneRow.xib
Any help would be much appreciated. Thank you.
Simple way to pass data is you need to crate object in your second controller and pass data from first controller
let vc = self.storyboard!.instantiateViewController(withIdentifier: "Secondcontroller") as! Secondcontroller
vc.yourObject = object //To pass
self.present(tabvc, animated: true, completion: nil) // or push
You will need to cast the view you create using UNib.[...] and pass the data directly to it:
func createContactListTableViewTelephoneRow(telephone: CNLabeledValue<CNPhoneNumber>) -> UIView {
let nib = UINib(nibName: "ContactListTableViewTelephoneRow", bundle: nil)
let root = nib.instantiate(withOwner: nil, options: nil)[0]
let view = root as! ContactListTableViewTelephoneRow
view.setData(telephoneLabelText: telephone.value.stringValue,
telephoneTypeLabelText: telephone.label!) // make sure `telephone.label!` is correct – I never compiled it
return view
}
Note that I adjusted the signature of createContactListTableViewTelephoneRow(telephone:).
But as an advise overall: I would solve your UI problem in a very different way.
Background: UITableViews heavily reuses (queues/dequeues) cells so that scroll performance is acceptable. Although I assume you use the APIs of UITableViewDataSource correctly loading nibs inside the your cells can become a performance bottleneck very fast.
I would advise against having variable number of ContactListTableViewTelephoneRow in your cell. Instead make it a subclass of UITableViewCell as well. Your view controller of course must handle at least two different types of cells in this case. You can use different sections to still keep the logic fairly easy. Here is a full example: (you would of course need to adjust styling)
import Contacts
import UIKit
class ContactListTelephoneTableViewCell: UITableViewCell {
#IBOutlet weak var telephoneLabel: UILabel!
#IBOutlet weak var telephoneTypeLabel: UILabel!
func configureCell(telephone: CNLabeledValue<CNPhoneNumber>) {
telephoneLabel.text = telephone.value.stringValue
telephoneTypeLabel.text = telephone.label!
}
}
class ContactListTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
func configureCell(contact: CNContact) {
titleLabel.text = "\(contact.givenName) \(contact.familyName)"
}
}
class DataSource: NSObject, UITableViewDataSource {
var contacts: [CNContact]!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts[section].phoneNumbers.count + 1 // one extra for given and family name
}
func numberOfSections(in tableView: UITableView) -> Int {
return contacts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
return self.tableView(tableView, nameCellForRowAt: indexPath)
} else {
return self.tableView(tableView, phoneCellForRowAt: indexPath)
}
}
func tableView(_ tableView: UITableView, nameCellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "name", for: indexPath) as! ContactListTableViewCell
cell.configureCell(contact: contacts[indexPath.section])
return cell
}
func tableView(_ tableView: UITableView, phoneCellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "phone", for: indexPath) as! ContactListTelephoneTableViewCell
let contact = contacts[indexPath.section]
let telephone = contact.phoneNumbers[indexPath.row - 1] // minus one for given and family name
cell.configureCell(telephone: telephone)
return cell
}
}

Swift: I want to know what is the index path row of the button that i clicked?

I have a custom cell class given below:
class SizeAndQuantityCellView:UITableViewCell
{
#IBOutlet weak var imageview: UIImageView!
#IBOutlet weak var plusButton4x4: UIButton!
#IBOutlet weak var plusButton4x6: UIButton!
#IBOutlet weak var plusButton5x7: UIButton!
#IBOutlet weak var plusButton8x10: UIButton!
#IBOutlet weak var minusButton4x4: UIButton!
#IBOutlet weak var minusButton4x6: UIButton!
#IBOutlet weak var minusButton5x7: UIButton!
#IBOutlet weak var minusButton8x10: UIButton!
#IBOutlet weak var quantity4x4: UILabel!
#IBOutlet weak var quantity4x6: UILabel!
#IBOutlet weak var quantity5x7: UILabel!
#IBOutlet weak var quantity8x10: UILabel!
let sizeAndQuantityController = SizeAndQuantityController()
#IBAction func plusButtonClick(sender: UIButton)
{
let btnTag:Int = sender.tag
let tableView = sender.superview!.superview?.superview as! UITableView
let cellRow = tableView.indexPathForCell(self)?.row
sizeAndQuantityController.plusButtonClick(btnTag,cellRow: cellRow!)
}
#IBAction func minusButtonClick(sender: UIButton)
{
let btnTag:Int = sender.tag
let tableView = sender.superview!.superview?.superview as! UITableView
let cellRow = tableView.indexPathForCell(self)?.row
sizeAndQuantityController.plusButtonClick(btnTag,cellRow: cellRow!)
}
}
What i want to do is when i click the plus button the quantity should increase by one and when i click the minus button it should decrease by one.
Here's my controller class for that:
class SizeAndQuantityController
{
func plusButtonClick(tag:Int,cellRow:Int)
{
switch tag
{
case 13:
let quant = quantity4x4[cellRow]
quantity4x4[cellRow] = quant+1
break;
case 14:
let quant = quantity4x6[cellRow]
quantity4x6[cellRow] = quant+1
break;
case 15:
let quant = quantity5x7[cellRow]
quantity5x7[cellRow] = quant+1
break;
case 16:
let quant = quantity8x10[cellRow]
quantity8x10[cellRow] = quant+1
break;
default:
break
}
}
func minusButtonClick(tag:Int,cellRow:Int)
{
switch tag
{
case 17:
let quant = quantity4x4[cellRow]
quantity4x4[cellRow] = quant-1
break;
case 18:
let quant = quantity4x6[cellRow]
quantity4x6[cellRow] = quant-1
break;
case 19:
let quant = quantity5x7[cellRow]
quantity5x7[cellRow] = quant-1
break;
case 20:
let quant = quantity8x10[cellRow]
quantity8x10[cellRow] = quant-1
break;
default:
break
}
}
i have given different tags to all the buttons.
when i run the app it gives me the following error: "Could not cast value of type UITableViewWrapperView to UITableView" at the line where i set my tableview.
Doing sender.superview!.superview?.superview as! UITableView is very dangerous. In the transition between iOS6 and iOS7, an extra layer was actually introduced and that kind of call failed.
Rather just have a property rowIndex in cell, which you set in your cellForRowAtIndexPath. For Example:
class SizeAndQuantityCellView:UITableViewCell
{
var rowIndex: Int = 0
...
}
In your TableViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableViewCell", for: indexPath) as! SizeAndQuantityCellView
cell.rowIndex = indexPath.row
...
return cell
}
From your code, it is not clear where quantity4x4[cellRow], for example, fits in but it seems to me that a Delegation Pattern might also be handy. I.o.w. Create a delegate protocol for SizeAndQuantityCellView and let your ViewController be the delegate of SizeAndQuantityCellView. When the buttons is tapped, fire an event to the delegate. That way your ViewController can handle the logic upon the pressing of the buttons.
A more sofisticated approach, involves the use of extensions and bitwise operator. Simplifying, you can use the tag property built-in with every UIButton, to store the whole value of of and IndexPath (that is identified by a row and a section) by packing it using bitwise operators and shifting.
Once the value is stored, you can use the computed property technique by extending your UIButton class and returning a new IndexPath that is created by unpacking the original values.
Below there's a simple extension that do the job:
extension UIButton {
func packing(low:Int, high:Int) -> Int {
//With the packing function we force our Packed number to be a 64 bit one
//we shift the high part 32bits to the left and OR the resulting value with the low part
return ((high << 32) | low)
}
func unpackHigh(packed:Int) -> Int {
//Unpacking the high part involve swifting to right the
//number in order to zero'ing all the non relevant bits.
return packed >> 32
}
func unpackLow(packed:Int) -> Int {
//Unpacking the low part involve masking the whole packed number with the max value allowed for an Int.
//note that using the Int.max function does not work as expected, returning a compile error.
//Maybe, it's a bug of compiler.
let mask = 2147483647
return mask & packed
}
//since we cannot use stored property on extensions, we need to compute realtime, every time the
//right value of our indexPath.
var indexPath:IndexPath {
get {
return IndexPath(row: unpackLow(packed: self.tag), section: unpackHigh(packed: self.tag))
}
set {
self.tag = packing(low: newValue.row, high: newValue.section)
}
}
}
and here you can find a simple application on a prototype cellForRowAtIndexPath:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let aCell = tableView.dequeueReusableCell(withIdentifier: "reuseCell") as! CustomTableViewCell
...
aCell.aButton.indexPath = indexPath
...
return aCell
}
note that you need to pass, after the dequeue, the right indexPath to the cell, in order to trigger the extension methods.

Add Label To Cell on Certain Rows only

I have a lot of rows with a few different layouts - that all works fine. Now I want to add a Custom UILabel on just some of the rows. I know that there is a "problem" working with a reuse identifier, so the UITableView will try to reuse my UILabel on the next Cells again. To prevent this, I've checked a lot of suggestions here and tried it this way:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
var cell = tableView .dequeueReusableCellWithIdentifier(myQuestions.getQuestion(indexPath.row).qTemplate, forIndexPath: indexPath) as CustomTableViewCell
// if there is a saved Notice, add this to this Cell
if(myQuestions.getQuestion(indexPath.row).qNotice != nil) {
cell.addNoticeToCell("This is a Test")
}
return cell
}
And my CustomTableViewCell class is:
class CustomTableViewCell: UITableViewCell {
#IBOutlet var LabelCellTitle: UILabel!
#IBOutlet var TextView: UITextView!
#IBOutlet weak var LabelCellContent: UILabel!
var noticeButton:UIButton!
func addNoticeToCell(noticeText: String) {
noticeButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
noticeButton.frame = CGRectMake(330,44,600,44)
noticeButton.titleLabel.textColor = UIColor.blueColor()
noticeButton.tag = 100
noticeButton.setTitle(noticeText, forState: UIControlState.Normal)
self.contentView.addSubview(noticeButton)
}
override func prepareForReuse() {
println("CELL BEFORE REUSE")
if(self.contentView.subviews.count > 0) {
for mySubView in self.contentView.subviews {
if mySubView.tag == 100 {
mySubView.removeFromSuperview()
....
It works now as expected - but I don't know if its a good idea to go through all subviews. Is there a better way? I would add 2-3 UIImageViews on certain cells too, and would create it with the same procedure.
In prepareForReuse(), why not just use the property you added to your cell class to remove it rather than enumerating all of the subviews like so:
var noticeButton:UIButton?
override func prepareForReuse() {
super.prepareForReuse()
if let notice = self.noticeButton {
notice.removeFromSuperview()
self.notice = nil
}
}

Resources