How to make checklist in UITableView - Swift? - ios

I made an app with UITableView. I want to show a tick mark in the left when cell is touched, and to be hidden when again is touched. I used some code and It's not showing as I wanted. The tick is showing on the right not on the left of screen.
This is how I want to make:
This is how it is:
And here is code that I used:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.allowsMultipleSelection = true
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = UITableViewCellAccessoryType.Checkmark
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = UITableViewCellAccessoryType.None
}
In conclusion, first problem is that the tick is showing on the right of the cell and not on the left. And the other problem, it won't untick (hide when cell is pressed again)
Thanks.

Try this
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "DhikrTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! GoalsTableViewCell
cell.tickButton.addTarget(self, action: #selector(GoalsViewController.toggleSelcted(_:)), forControlEvents: UIControlEvents.TouchUpInside)
cell.tickButton.tag = indexPath.row
// Fetches the appropriate meal for the data source layout.
let workout = workouts[indexPath.row]
let number = numbers[indexPath.row]
if workout.isSelected {
cell.tickButton.setImage(UIImage(named: "Ticked Button"), forState: UIControlState.Normal)
} else {
cell.tickButton.setImage(UIImage(named: "Tick Button"), forState: UIControlState.Normal)
}
cell.nameLabel.text = workout.name
cell.numberLabel.text = number.number
return cell
}
func toggleSelcted(button: UIButton) {
let workout = workouts[button.tag]
workout.isSelected = !workout.isSelected
myTableView.reloadData()
}

To achieve this behavior with default UITableViewCell, you may need to set table view to edit mode.
Try the following code in your viewDidLoad.
self.tableView.allowsMultipleSelectionDuringEditing = true
self.tableView.setEditing(true, animated: false)
This will show tick mark on the left side, and also this will untick (hide) when cell is pressed again.
Edited:
If you need more customization, Create a custom table view cell and handle the selection/tick mark manually.
This will be your cell.swift.
class CustomCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var tickImageView: UIImageView!
//Handles the cell selected state
var checked: Bool! {
didSet {
if (self.checked == true) {
self.tickImageView.image = UIImage(named: "CheckBox-Selected")
}else{
self.tickImageView.image = UIImage(named: "CheckBox-Normal")
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
checked = false
self.layoutMargins = UIEdgeInsetsZero
self.separatorInset = UIEdgeInsetsZero
}
The datasource array is,
let list = ["Title 1", "Title 2", "Title 2", "Title 4"]
The view controller didLoad will be like
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
listView.registerNib(UINib(nibName: "OptionsSelectionCell", bundle: nil), forCellReuseIdentifier: "SelectionCell")
}
The view controller table delegate methods will be like,
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SelectionCell") as! OptionsSelectionCell
cell.titleLabel.text = list[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! OptionsSelectionCell
cell.checked = !cell.checked
}
}

Related

UITableView changing image and title position cell by cell

I was wondering if there any possible way to create a table view with this style:
I have a dictionary contains title and image values, I need to create a cell one Image-Right / Title-Left and next vice versa. How can achieve something like this?
You can do it by setAffineTransform in this way:
• build up your tableView with one prototype cell that has an image in left and a label in right
• then do this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier", for: indexPath) as! YourTableViewCell
if (indexPath.row % 2 == 0) {
cell.contentView.layer.setAffineTransform(CGAffineTransform(scaleX: -1, y: 1))
cell.YourImage.layer.setAffineTransform(CGAffineTransform(scaleX: -1, y: 1))
cell.YourLabel.layer.setAffineTransform(CGAffineTransform(scaleX: -1, y: 1))
}
// do what ever you want ...
return cell
}
also the best solution is defining 2 prototype cells but in your case this is a tricky and fast way to achieve your goal.
Yes, you can use a table view to achieve your requirement. you will need to follow the following steps.
Method 1:
Create two table view cell XIB's one with left side label and right side image, the second one is with left side image and right side image.
Keep same class of both the XIB's you have created but with different identifiers.
In your Table view cellForRowAtIndexPath method implement following logic.
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasourceArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row % 0 == 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "RightLabelTableViewCell", for: indexPath) as! CustomTablViewCell
cell.model = datasourceArray[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "LeftLabelTableViewCell", for: indexPath) as! CustomTablViewCell
cell.model = datasourceArray[indexPath.row]
return cell
}
}
}
Note: You can use one class for TableViewCell with a different
identifier and design your xib's accordingly.
Method 2:
Flip your table view cell's content view in a such a way that they will swap in your UI.
add the following code into your cellForRowAtIndexPath and also add else part of it because cell for a row may behave weirdly because of dequeing:
extension UIView {
/// Flip view horizontally.
func flipX() {
transform = CGAffineTransform(scaleX: -transform.a, y: transform.d)
}
}
Usage:
cell.contentView.flipX()
cell.yourImage.flipX()
cell.youImageName.flipX()
Don't forget to add else part in cellForRowAt method.
There are actually many ways of doing this:
Create 2 cells. Have 2 cells like OddTableViewCell and EvenTableViewCell. You can choose with index path which to use in cellForRow method like:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row%0 == 0) {
let cell = tableView.dequeueReusableCell(withIdentifier: "EvenTableViewCell", for: indexPath) as! EvenTableViewCell
cell.model = dataArray[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "OddTableViewCell", for: indexPath) as! OddTableViewCell
cell.model = dataArray[indexPath.row]
return cell
}
}
}
Have a single cell but duplicate views so you have 2 labels and 2 image views. Then hide them as you need to:
class MyCell: UITableViewCell {
#IBOutlet private var leftImageView: UIImageView?
#IBOutlet private var rightImageView: UIImageView?
#IBOutlet private var leftLabel: UILabel?
#IBOutlet private var rightLabel: UILabel?
var userImage: UIImage? {
didSet {
refresh()
}
}
var userName: String? {
didSet {
refresh()
}
}
var imageOnLeft: Bool = false {
didSet {
refresh()
}
}
func refresh() {
leftImageView?.image = imageOnLeft ? userImage : nil
leftImageView?.isHidden = !imageOnLeft
rightImageView?.image = imageOnLeft ? nil : userImage
rightImageView?.isHidden = imageOnLeft
leftLabel?.text = imageOnLeft ? nil : userName
leftLabel?.isHidden = imageOnLeft
rightLabel?.text = imageOnLeft ? userName : nil
rightLabel?.isHidden = !imageOnLeft
}
}
Have a single cell with stack view. Add a label and image view onto the stack view. You can change order of items in stack view. Some promising answer can be found here. The rest should be pretty similar to the second solution.
(4.) Also you could just use a collection view and have a label cell and an image cell.
Create one cell with 2 image and 2 label left and right
when you went to left side image that time hide right side image same as in label.
cell
import UIKit
class TestTableViewCell: UITableViewCell {
#IBOutlet weak var lbl_left: UILabel!
#IBOutlet weak var lbl_right: UILabel!
#IBOutlet weak var img_right: UIImageView!
#IBOutlet weak var img_left: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func configure_cell(left:Bool)
{
if left{
img_left.isHidden = true
img_right.isHidden = false
lbl_left.isHidden = false
lbl_right.isHidden = true
self.img_right.image = UIImage(named: "testimg")
}else{
img_left.isHidden = false
img_right.isHidden = true
lbl_left.isHidden = true
lbl_right.isHidden = false
self.img_left.image = UIImage(named: "testimg")
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
ViewController
extension ViewController:UITableViewDataSource,UITableViewDelegate
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as? TestTableViewCell
if (indexPath.row + 1) % 2 == 0 {
cell?.configure_cell(left: true)
} else {
cell?.configure_cell(left: false)
}
return cell!
}
}

UITableView Custom Cell button selects other buttons out of the main view?

On my custom tableviewcell I have a button which the user can press which toggles between selected and unselected. This is just a simple UIButton, this button is contained with my customtableviewcell as an outlet.
import UIKit
class CustomCellAssessment: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var dateTaken: UILabel!
#IBOutlet weak var id: UILabel!
#IBOutlet weak var score: UILabel!
#IBOutlet weak var button: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func selectButton(_ sender: UIButton) {
if sender.isSelected{
sender.isSelected = false
}else{
sender.isSelected = true
}
}
}
The strange thing is, when I press a button on say the first cell it then selects a button 8 cells down on another cell (out of the view) in the same tableview. Each cell has its own button but it is as if using dequeReusableCell is causing the system to behave this way. Why is it doing this and is it to do with the way that UIbuttons work on tableviewcells?
Thanks
Each cell has its own button but it is as if using dequeReusableCell is causing the system to behave this way.
Wrong. UITableViewCells are reusable, so if your tableView has 8 cells visible, when loading the 9th, the cell nr 1 will be reused.
Solution: You need to keep track of the state of your cells. When the method cellForRowAtIndexPath is called, you need to configure the cell from scratch.
You could have in your ViewController an small array containing the state of the cells:
var cellsState: [CellState]
and store there the selected state for each indexPath. Then in the cellForRowAtIndexPath, you configure the cell with the state.
cell.selected = self.cellsState[indexPath.row].selected
So, an overview of I would do is:
1 - On the cellForRowAtIndexPath, I would set
cell.button.tag = indexPath.row
cell.selected = cellsState[indexPath.row].selected
2 - Move the IBAction to your ViewController or TableViewController
3 - When the click method is called, update the selected state of the cell
self.cellsState[sender.tag].selected = true
Remember to always configure the whole cell at cellForRowAtIndexPath
Edit:
import UIKit
struct CellState {
var selected:Bool
init(){
selected = false
}
}
class MyCell: UITableViewCell {
#IBOutlet weak var button: UIButton!
override func awakeFromNib() {
self.button.setTitleColor(.red, for: .selected)
self.button.setTitleColor(.black, for: .normal)
}
}
class ViewController: UIViewController, UITableViewDataSource {
var cellsState:[CellState] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//Add state for 5 cells.
for _ in 0...5 {
self.cellsState.append(CellState())
}
}
#IBAction func didClick(_ sender: UIButton) {
self.cellsState[sender.tag].selected = true
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellsState.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MyCell
cell.button.isSelected = self.cellsState[indexPath.row].selected
cell.button.tag = indexPath.row
return cell
}
}
When you tap the button, you're setting the selected state of the button. When the cell gets reused, the button is still selected - you're not doing anything to reset or update that state.
If button selection is separate to table cell selection, then you'll need to keep track of the index path(s) of the selected buttons as part of your data model and then update the selected state of the button in tableView(_: cellForRowAt:).
A table view will only create as many cells as it needs to display a screen-and-a-bits worth of information. After that, cells are reused. If a cell scrolls off the top of the screen, the table view puts it in a reuse queue, then when it needs to display a new row at the bottom as you scroll, it pulls that out of the queue and gives it to you in tableView(_: cellForRowAt:).
The cell you get here will be exactly the same as the one you used 8 or so rows earlier, and it's up to you to configure it completely. You can do that in cellForRow, and you also have an opportunity in the cell itself by implementing prepareForReuse, which is called just before the cell is dequeued.
In your tableView(_: cellForRowAt:) take action for button click and add target
cell.button.addTarget(self, action: #selector(self.selectedButton), for: .touchUpInside)
At the target method use below lines to get indexPath
func selectedButton(sender: UIButton){
let hitPoint: CGPoint = sender.convert(CGPoint.zero, to: self.tableView)
let indexPath: NSIndexPath = self.tableView.indexPathForRow(at: hitPoint)! as NSIndexPath
}
Then do your stuff by using that indexPath.
Actually your method can not find in which indexPath button is clicked that's why not working your desired button.
One way to approach your problem is as follow
1. First create a protocol to know when button is clicked
protocol MyCellDelegate: class {
func cellButtonClicked(_ indexPath: IndexPath)
}
2. Now in your cell class, you could do something like following
class MyCell: UITableViewCell {
var indexPath: IndexPath?
weak var cellButtonDelegate: MyCellDelegate?
func configureCell(with value: String, atIndexPath indexPath: IndexPath, selected: [IndexPath]) {
self.indexPath = indexPath //set the indexPath
self.textLabel?.text = value
if selected.contains(indexPath) {
//this one is selected so do the stuff
//here we will chnage only the background color
backgroundColor = .red
self.textLabel?.textColor = .white
} else {
//unselected
backgroundColor = .white
self.textLabel?.textColor = .red
}
}
#IBAction func buttonClicked(_ sender: UIButton) {
guard let delegate = cellButtonDelegate, let indexPath = indexPath else { return }
delegate.cellButtonClicked(indexPath)
}
}
3. Now in your controller. I'm using UItableViewController here
class TheTableViewController: UITableViewController, MyCellDelegate {
let cellData = ["cell1","cell2","cell3","cell4","cell2","cell3","cell4","cell2","cell3","cell4","cell2","cell3","cell4","cell2","cell3","cell4","cell2","cell3","cell4"]
var selectedIndexpaths = [IndexPath]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellData.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 55.0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
cell.cellButtonDelegate = self
cell.configureCell(with: cellData[indexPath.row], atIndexPath: indexPath, selected: selectedIndexpaths)
return cell
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30.0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! MyCell
cell.buttonClicked(UIButton())
}
func cellButtonClicked(_ indexPath: IndexPath) {
if selectedIndexpaths.contains(indexPath) {
//this means cell has already selected state
//now we will toggle the state here from selected to unselected by removing indexPath
if let index = selectedIndexpaths.index(of: indexPath) {
selectedIndexpaths.remove(at: index)
}
} else {
//this is new selection so add it
selectedIndexpaths.append(indexPath)
}
tableView.reloadData()
}
}

TextField in tableView cell is not outputting text to the console

I have created a tableView with two different labels and one textfield. Depending on the indexPath in which is selected the labels will display different text according to the array. I have created a CocoTouch Class file and made it type TableViewCell.
TableViewCell.swift
import UIKit
class Driver_Navigation_TableViewCell: UITableViewCell {
#IBOutlet weak var orderTextField: UITextField!
#IBOutlet weak var adressLabel: UILabel!
#IBOutlet weak var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
ViewController.swift
class ViewController: UIViewController, MGLMapViewDelegate, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
var allCellsText = [String]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Driver_Navigation_TableViewCell
cell.adressLabel.text = passengersAdress[indexPath.row]
cell.nameLabel.text = passengersName[indexPath.row]
cell.orderTextField.placeholder = "\(indexPath.row + 1)."
return(cell)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath as IndexPath) as! Driver_Navigation_TableViewCell
cell.orderTextField.tag = indexPath.row
cell.orderTextField.delegate = self // theField is your IBOutlet UITextfield in your custom cell
return cell
}
func textFieldDidEndEditing(textField: UITextField) {
allCellsText.append(textField.text!)
print(allCellsText)
}
}
You cant have duplicate UITableViewDataSource and UITableViewDelegate methods it won't work like that.
Also in your code you have not set the delegate for the textField in both the cellForRowAtIndexPath() methods.
If you want to have two table view in a single controller try the following
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! Driver_Navigation_TableViewCell
if tableView == self.tableView1 {
cell.adressLabel.text = passengersAdress[indexPath.row]
cell.nameLabel.text = passengersName[indexPath.row]
cell.orderTextField.placeholder = "\(indexPath.row + 1)."
}
else {
cell.orderTextField.tag = indexPath.row
}
cell.orderTextField.delegate = self
return(cell)
}
Create outlets for the Table View

Text getting overlapped in uitableview cells in iOS swift

I have created a prototype UITableViewCell on storyboard.It contains 1 ImageView, 3 labels to display the content. All the controls have outlets in corresponding subclass of UITableViewCell. I display messages which can be multi line. So I had to add following methods
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
The text of the labels is getting overlapped and it happens whenever I scroll the tableview. It is only happening with either first 2 rows or the last row.But as i searched on google, I found that it can happen on any row any time. Here's my code of the extension which is commonly written.
extension ConversationViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = selectedConversation?.msgArrayWithBodyText.count {
return count
}
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(conversationMessageIdentifier, forIndexPath: indexPath) as! ConversationMessageTableViewCell
let currMsg = selectedConversation.msgArrayWithBodyText[indexPath.row]
cell.userFullNameLabel.text = currMsg.senderName
cell.dateLabel.text = selectedConversation.getShortDateForMessage(currMsg.timeSent)
cell.messageBodyLabel.text = currMsg.bodyText //Constants.testString
let imageUrl = "\(Constants.serverUrl)/api/users/\(currMsg.senderId)/profilePicture"
if let item = ImageCache.sharedInstance.objectForKey(imageUrl) as? CacheableItem {
print("\(imageUrl) found in cache, expiresAt: \(item.expiresAt)")
cell.userImageView.image = UIImage(data: item)
} else {
cell.userImageView.image = UIImage(named: "Profile")
self.imageDownLoadingQueue.addOperationWithBlock({
if let url = NSURL(string: imageUrl) {
if let purgableData = NSPurgeableData(contentsOfURL: url) {
if let downloadedImage = UIImage(data: purgableData) {
print("cache: \(imageUrl)")
let item = CacheableItem()
item.setData(purgableData)
ImageCache.sharedInstance.setObject(item, forKey: imageUrl)
NSOperationQueue.mainQueue().addOperationWithBlock({
if let updateCell = tableView.cellForRowAtIndexPath(indexPath) as? ConversationMessageTableViewCell {
updateCell.userImageView.image = downloadedImage
}
})
}
}
}
})
}
//cell.clipsToBounds = true
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if cell.respondsToSelector(Selector("setSeparatorInset:")) {
cell.separatorInset = UIEdgeInsetsZero
}
if cell.respondsToSelector(Selector("setLayoutMargins:")) {
cell.layoutMargins = UIEdgeInsetsZero
}
if cell.respondsToSelector(Selector("setPreservesSuperviewLayoutMargins:")) {
cell.preservesSuperviewLayoutMargins = false
}
cell.backgroundColor = UIColor.clearColor()
cell.backgroundView = UIView(frame:cell.frame)
if(cell.backgroundView?.layer.sublayers?.count > 1){
cell.backgroundView?.layer.sublayers?.removeAtIndex(0)
}
let layer = self.getGradientLayer(indexPath.row)
cell.backgroundView?.layer.insertSublayer(layer, atIndex: 0)
}
}
And here's the subclass of UITableViewCell i have written for the protyped cell.
class ConversationMessageTableViewCell: UITableViewCell {
#IBOutlet weak var userImageView: UIImageView!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var userFullNameLabel: UILabel!
#IBOutlet weak var messageBodyLabel: UILabel!
#IBOutlet weak var messageBodyLabelHeightConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
userImageView.layer.borderColor = UIColor.clearColor().CGColor
userImageView.layer.borderWidth = 1.0
userImageView.layer.cornerRadius = (userImageView.frame.height/2)
userImageView.clipsToBounds = true
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
Here's the edited screen shot (I have blackened out the name and faces)
Help required!
I can see long text not getting messed up,but it only happens with small texts.
Basically, this only happens when i scroll the tableview up and down few times. All the text is displayed properly when tableview loads, but then messes up and then clears out for a while and again messes up.
I found the solution to this problem.
There is an option in story board which says Clear Graphics Context which basically clears the graphics for the label before redrawing it.

Image added to UITableViewCell appears in wrong rows when scrolling

I'm adding an image to a table view row (actually, I seem to be adding it to the row's cell) when selecting it (and removing when selecting it again). The table view consists of prototype cells.
This works but when I scroll around and get back to the row I had previously selected, the image would be in another row. Also, the image appears in other rows as well.
My guess is this happens because the cells are re-used when scrolling.
Here's the code of a little sample project:
import UIKit
class MyTableViewController: UITableViewController {
// Using integers for simplicity, should work with strings, too.
var numbers = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<50 {
numbers.append(i)
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TestCell", forIndexPath: indexPath)
cell.textLabel?.text = "\(numbers[indexPath.row] + 1)"
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numbers.count
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
if let myImage = curCell.viewWithTag(10) as? MyImage {
myImage.removeFromSuperview()
} else {
let myImage = myImage()
myImage.tag = 10
cell.addSubview(myImage)
}
}
I need to have the image stay in the correct row, also when coming back to this view controller. What's the correct way to tackle this?
Any advice much appreciated!
EDIT: I've tried to implement matt's answer but I seem to be missing something, as the problem is still the same.
EDIT 2: Updated, working as intended now.
import UIKit
class ListItem {
var name: String!
var showsImage = false
init(name: String) {
self.name = name
}
}
class MyTableViewController: UITableViewController {
var listItems = [ListItem]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<50 {
let listItem = ListItem(name: "row \(i)")
listItems.append(listItem)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TestCell", forIndexPath: indexPath)
cell.textLabel?.text = "\(listItems[indexPath.row].name)"
if listItems[indexPath.row].showsImage {
let myImage = myImage
myImage.tag = 10
cell.addSubview(myImage)
} else {
if let myImage = cell.viewWithTag(10) as? myImage {
myImage.removeFromSuperview()
listItems[indexPath.row].showsImage = false
}
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listItems.count
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
if let myImage = cell.viewWithTag(10) as? myImage {
myImage.removeFromSuperview()
listItems[indexPath.row].showsImage = false
} else {
listItems[indexPath.row].showsImage = true
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
}
}
EDIT 3: As matt suggested, here's an alternative solution to the code above which subclasses UITableViewCell instead of using a tag for the image.
import UIKit
class MyTableViewCell: UITableViewCell {
var myImage = MyImage()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
myImage.hidden = true
addSubview(myImage)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ListItem {
var name: String!
var showsImage = false
init(name: String) {
self.name = name
}
}
class MyTableViewController: UITableViewController {
var listItems = [ListItem]()
override func viewDidLoad() {
super.viewDidLoad()
for i in 0..<50 {
let listItem = ListItem(name: "row \(i)")
listItems.append(listItem)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listItems.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = MyTableViewCell(style: .Default, reuseIdentifier: "TestCell")
cell.textLabel?.text = "\(listItems[indexPath.row].name)"
cell.myImage.hidden = !(listItems[indexPath.row].showsImage)
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! MyTableViewCell
listItems[indexPath.row].showsImage = cell.myImage.hidden
cell.myImage.hidden = !cell.myImage.hidden
}
}
The problem is that cells are reused in other rows. When they are, cellForRowAtIndexPath is called again. But when it is, you are supplying no information about the image for that row.
The solution: fix your model (i.e. the info you consult in cellForRowAtIndexPath) so that it knows about the image. In didSelect, do not modify the cell directly. Instead, modify the model and reload the cell.

Resources