TableView inside a custom UITableViewCell not appearing for all of the cells of that custom cellviewtype - ios

I am trying to create a table of services such that, if service has a couple of sub-services, then the cell associated with that service then has another table view showing those sub-services under the said service.
I tried implementing such a table by looking at the example shown in the link: table within a tableviewcell
I am posting related source codes associated with the tableview
BookingServiceChargeViewCell.swift
import UIKit
import PineKit
import SwiftMoment
class BookingServiceChargeViewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
var service : Service? = nil
var subServices : [Service] = []
let content = PineCardView()
var cover = UIImageView()
let serviceName = PineLabel.Bold(text: " ... ")
var itemIndex = -1
var chosen = false
var parentView : OnboardingChosenServicesViewController? = nil
var anchor = UIView()
let table = UITableView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
layout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layout() {
self.addSubview(content)
content.snp.makeConstraints { (make) in
make.top.left.right.equalTo(self).inset(5)
make.bottom.equalTo(self)
}
layoutContent()
}
func layoutContent() {
content.addSubviews([cover, serviceName])
self.cover.image = UIImage(named: "gray-card")
cover.clipsToBounds = true
cover.snp.makeConstraints { (make) in
make.width.equalTo(content).multipliedBy(0.15)
make.left.equalTo(content).offset(10)
make.top.equalTo(content.snp.top).offset(15)
make.size.equalTo(50)
}
serviceName.textColor = UIColor.black
serviceName.font = Config.Font.get(.Bold, size: 17.5)
serviceName.snp.makeConstraints { (make) in
make.centerY.equalTo(cover)
make.left.equalTo(cover.snp.right).offset(20)
}
table.delegate = self
table.dataSource = self
table.register(BookingSubServicesChargeViewCell.self, forCellReuseIdentifier: "cell")
table.separatorStyle = .none
self.content.addSubview(table)
table.snp.makeConstraints { (make) in
make.top.equalTo(self.cover.snp.bottom).offset(15)
make.left.equalTo(self.cover.snp.right).offset(10)
make.right.equalTo(self.content.snp.right).offset(-10)
make.height.equalTo(450)
}
}
func configure(_ service: Service, subServices: [Service], index: Int, parentView: OnboardingChosenServicesViewController) {
self.service = service
self.subServices = subServices
self.itemIndex = index
self.parentView = parentView
if (self.service!.defaultImage != nil){
ImageLoader.sharedLoader.imageForUrl(urlString: self.service!.defaultImage!) { (image, url) in
self.cover.image = image
}
}
self.serviceName.text = self.service!.name!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.subServices.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! BookingSubServicesChargeViewCell
cell.configure(self.subServices[indexPath.row], index: indexPath.row, parentView: self.parentView!)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
}
BookingSubServicesChargeViewCell.swift
import UIKit
import PineKit
import SwiftMoment
class BookingSubServicesChargeViewCell: UITableViewCell {
var service : Service? = nil
let content = PineCardView()
var cover = UIImageView()
let serviceName = PineLabel.Bold(text: " ... ")
var itemIndex = -1
var chosen = false
var parentView : OnboardingChosenServicesViewController? = nil
var anchor = UIView()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
layout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func layout() {
self.addSubview(content)
content.snp.makeConstraints { (make) in
make.top.left.right.equalTo(self).inset(5)
make.bottom.equalTo(self)
}
layoutContent()
}
func layoutContent() {
content.addSubviews([cover, serviceName])
self.cover.image = UIImage(named: "gray-card")
cover.clipsToBounds = true
cover.snp.makeConstraints { (make) in
make.width.equalTo(content).multipliedBy(0.15)
make.left.equalTo(content).offset(10)
make.top.equalTo(content.snp.top).offset(15)
make.size.equalTo(50)
}
serviceName.textColor = UIColor.black
serviceName.font = Config.Font.get(.Bold, size: 17.5)
serviceName.snp.makeConstraints { (make) in
make.centerY.equalTo(cover)
make.left.equalTo(cover.snp.right).offset(20)
}
self.anchor = self.serviceName
}
func configure(_ service: Service, index: Int, parentView: OnboardingChosenServicesViewController) {
self.service = service
self.itemIndex = index
self.parentView = parentView
if (self.service!.defaultImage != nil){
ImageLoader.sharedLoader.imageForUrl(urlString: self.service!.defaultImage!) { (image, url) in
self.cover.image = image
}
}
self.serviceName.text = self.service!.name!
}
}
Here a couple of screenshots of the situation that has arisen:-
As you can see in the screenshots, some of the tables which have sub-services are not being shown, but sometimes they are being shown.
Can someone tell me what is it that I am missing here? What changes am I supposed to make in the source codes shown above?
FYI: I am not using storyboards in any way and I do not know what nib files are. I have constructed this programmatically and I am hoping that I can get a code snippet based solution as soon as possible.
Thanks.

In BookingServiceChargeViewCell class, call self.table.reloadData() at the end of configure method as below.
func configure(_ service: Service, subServices: [Service], index: Int, parentView: OnboardingChosenServicesViewController) {
self.service = service
self.subServices = subServices
self.itemIndex = index
self.parentView = parentView
if (self.service!.defaultImage != nil){
ImageLoader.sharedLoader.imageForUrl(urlString: self.service!.defaultImage!) { (image, url) in
self.cover.image = image
}
}
self.serviceName.text = self.service!.name!
self.table.reloadData()
}

Related

UITableView CustomCell Reuse (ImageView in CustomCell)

I'm pretty new to iOS dev and I have an issue with UITableViewCell.
I guess it is related to dequeuing reusable cell.
I added an UIImageView to my custom table view cell and also added a tap gesture to make like/unlike function (image changes from an empty heart(unlike) to a filled heart(like) as tapped and reverse). The problem is when I scroll down, some of the cells are automatically tapped. I found out why this is happening, but still don't know how to fix it appropriately.
Below are my codes,
ViewController
import UIKit
struct CellData {
var title: String
var done: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = models[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.textLabel?.text = model.title
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadData()
}
}
TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
let mainVC = ViewController()
static let identifier = "TableViewCell"
let likeImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(likeImage)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImage.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
likeImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
likeImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
if likeImage.image == UIImage(systemName: "heart.fill"){
likeImage.image = UIImage(systemName: "heart")
likeImage.tintColor = .darkGray
} else {
likeImage.image = UIImage(systemName: "heart.fill")
likeImage.tintColor = .systemRed
}
}
}
This gif shows how it works now.
enter image description here
I've tried to use "done" property in CellData structure to capture the status of the uiimageview but failed (didn't know how to use that in the correct way).
I would be so happy if anyone can help this!
You've already figured out that the problem is cell reuse.
When you dequeue a cell to be shown, you are already setting the cell label's text based on your data:
cell.textLabel?.text = model.title
you also need to tell the cell whether to show the empty or filled heart image.
And, when the user taps that image, your cell needs to tell the controller to update the .done property of your data model.
That can be done with either a protocol/delegate pattern or, more commonly (particularly with Swift), using a closure.
Here's a quick modification of the code you posted... the comments should give you a good idea of what's going on:
struct CellData {
var title: String
var done: Bool
}
class ShinViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(ShinTableViewCell.self, forCellReuseIdentifier: ShinTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ShinTableViewCell.identifier, for: indexPath) as! ShinTableViewCell
let model = models[indexPath.row]
cell.myLabel.text = model.title
// set the "heart" to true/false
cell.isLiked = model.done
// closure
cell.callback = { [weak self] theCell, isLiked in
guard let self = self,
let pth = self.tableView.indexPath(for: theCell)
else { return }
// update our data
self.models[pth.row].done = isLiked
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class ShinTableViewCell: UITableViewCell {
// we'll use this closure to communicate with the controller
var callback: ((UITableViewCell, Bool) -> ())?
static let identifier = "TableViewCell"
let likeImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let myLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// we'll load the heart images once in init
// instead of loading them every time they change
var likeIMG: UIImage!
var unlikeIMG: UIImage!
var isLiked: Bool = false {
didSet {
// update the image in the image view
likeImageView.image = isLiked ? likeIMG : unlikeIMG
// update the tint
likeImageView.tintColor = isLiked ? .systemRed : .darkGray
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// make sure we load the heart images
guard let img1 = UIImage(systemName: "heart"),
let img2 = UIImage(systemName: "heart.fill")
else {
fatalError("Could not load the heart images!!!")
}
unlikeIMG = img1
likeIMG = img2
// add label and image view
contentView.addSubview(myLabel)
contentView.addSubview(likeImageView)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImageView.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
// let's use the "built-in" margins guide
let g = contentView.layoutMarginsGuide
// image view bottom constraint
let bottomConstraint = likeImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
// this will avoid auto-layout complaints
bottomConstraint.priority = .required - 1
NSLayoutConstraint.activate([
// constrain label leading
myLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
// center the label vertically
myLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// constrain image view trailing
likeImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// constrain image view to 30 x 30
likeImageView.widthAnchor.constraint(equalToConstant: 30),
likeImageView.heightAnchor.constraint(equalTo: likeImageView.widthAnchor),
// constrain image view top
likeImageView.topAnchor.constraint(equalTo: g.topAnchor),
// activate image view bottom constraint
bottomConstraint,
])
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
// toggle isLiked (true/false)
isLiked.toggle()
// inform the controller, so it can update the data
callback?(self, isLiked)
}
}

Swift shapeLayer.fillColor not work in cell contentView

I have a chartView and a customized tableViewcell.
And I try to use chartView add on my tableviewcell.
This ViewController is child ViewController.
I push to this ViewController, and I want to call tapIndexAction(index: 0) to hightlight my bar of index 0.
I also use "tableview didEndDisplaying" can't fill color.
What's wrong with me?
Thanks.
class ChartView: UIView {
let publishSub = PublishSubject<Int?>()
var isAlreadyInit = false
var chartInfo:ChartInfo?
init(info:ChartInfo?, frame:CGRect = CGRect.zero) {
self.chartInfo = info
super.init(frame:frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
if !self.isAlreadyInit {
if self.frame.width > 0 {
self.isAlreadyInit = true
self.drawChart()
}
}
}
func drawChart() {
if let tmpCharInfo = self.chartInfo {
let chartLayer = tmpChartInfo.getChartDrawLayer()
chartLayer.frame = self.bounds
chartLayer.drawChart()
self.layer.addSublayer(chartLayer)
}
}
func tapIndexAction(index: Int) {
if let chartLayer = (self.layer.sublayers?.last) as? ChartLayer {
chartLayer.tapIndex(index: index)
self.publishSub.onNext(index)
}
}
class ChartLayer: CALayer {
var drawInfo:ChartInfo
var touchFrame:[CGRect] = [CGRect]()
var drawLayers:[CAShapeLayer] = [CAShapeLayer]()
var tapIndex:Int?
init(info:ChartDrawInfo) {
self.drawInfo = info
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func tapIndex(index:Int) {
self.tapIndex = index
for idx in 0..<self.touchFrame.count {
let shapeLayer = self.drawLayers[idx]
if idx == index {
shapeLayer.fillColor = UIColor.red.cgColor //Not work in "tableview didEndDisplaying"
}else {
shapeLayer.fillColor = UIColor.blue.cgColor
}
}
}
}
class BarChartTableViewCell: UITableViewCell {
var barChartView: ChartView?
var chartInfo:ChartInfo?
func initCell(drawInfo: ChartInfo?) {
self.chartInfo = drawInfo
if self.barChartView != nil {
for view in self.contentView.subviews {
view.snp.removeConstraints()
view.removeFromSuperview()
}
}
self.barChartView = ChartView(drawInfo: self.chartInfo)
self.contentView.addSubview(barChartView)
barChartView?.snp.makeConstraints({ (make) in
make.left.equalTo(13)
make.top.equalTo(updateTimeLabel.snp.bottom)
make.right.equalToSuperview()
make.height.equalTo(202)
})
}
}
class ViewController: UITableViewDelegate, UITableViewDataSource {
let tableview = UITableView()
tableview.datasource = self
tableview.delegate = self
//numberOfRowInSection return 1
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
if let chartCell = cell as? BarChartTableViewCell {
chartCell.barChartView?.tapIndexAction(index: 0)
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let barChartCell = tableView.dequeueReusableCell(withIdentifier: "BarChartTableViewCell", for: indexPath) as! BarChartTableViewCell
barChartCell.initCell(drawInfo: chartInfo)
return barChartCell
}
}

Value of has no member

I tried to get a CustomCell trough watching a Youtube Tutorial but im getting this problem at my Tableview.It says
Value of type 'CustomCell' has no member 'mainImage'. I hope some of you can help me to solve this problem.
The mainImage I want to get in my Cell is in my CustomCell.swift.
My TableViewController:
import UIKit
struct CellData {
let image : UIImage?
let message : String?
}
var data = [CellData]()
class TableViewController : UITableViewController{
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
data = [CellData.init(image: #imageLiteral(resourceName: "Bildschirmfoto 2018-09-20 um 22.17.11"), message: "The Avengers")]
self.tableView.register(CustomCell.self, forCellReuseIdentifier: "custom")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "custom") as! CustomCell
cell.mainImage = data[indexPath.row].image
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
<#code#>
}
}
My CustomCell
import Foundation
import UIKit
var message : String?
var mainImage : UIImage?
var messageView: UITextView = {
var textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
var mainImageView: UIImageView = {
var imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
class CustomCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier:reuseIdentifier)
self.addSubview(mainImageView)
self.addSubview(messageView)
mainImageView.leftAnchor.constraint(equalTo:self.leftAnchor).isActive = true
mainImageView.topAnchor.constraint(equalTo:self.topAnchor).isActive = true
mainImageView.bottomAnchor.constraint(equalTo:self.bottomAnchor).isActive = true
mainImageView.widthAnchor.constraint(equalTo:self.heightAnchor).isActive = true
messageView.leftAnchor.constraint(equalTo:mainImageView.rightAnchor).isActive = true
messageView.rightAnchor.constraint(equalTo:self.rightAnchor).isActive = true
messageView.bottomAnchor.constraint(equalTo:self.bottomAnchor).isActive = true
messageView.topAnchor.constraint(equalTo:self.topAnchor).isActive = true
}
override func layoutSubviews() {
super.layoutSubviews()
if let message = message {
messageView.text = message
}
if let image = mainImage {
mainImageView.image = image
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You have to put your subviews into the cell class:
class CustomCell: UITableViewCell {
var messageView: UITextView = {
var textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
return textView
}()
var mainImageView: UIImageView = {
var imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
...
}

Creating a TableView Programmatically with multiple cell in swift

As I am creating a TableView programmatically with multiple cell and in each cell having UICollectionView, UITableView, UITableView.... but I am not able to find the error and every time when I run the program it Shows
" Command failed due to signal: Segmentation fault: 11".
Has any one created this type of UI using coding.
New in Swift so forgive for errors.
// ViewController.swift
// Json Parsing in Swift
//
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UITableViewController {
var dataArray = Array<JSON>()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Alamofire.request(.GET, "http://104.131.162.14:3033/api/ios/detail").validate().responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
print("JSON: \(json)")
var trafficJson = json["traffic_partners"]
trafficJson["type"] = "Traffic"
self.dataArray.append(trafficJson)
var newsJson = json["news"]
newsJson["type"] = "News"
self.dataArray.append(newsJson)
var categoryJson = json["category"]
categoryJson["type"] = "Category"
self.dataArray.append(categoryJson)
var topFreeApps = json["top_free_apps"]
topFreeApps["type"] = "TopApps"
self.dataArray.append(topFreeApps)
var topSites = json["top_sites"]
topSites["type"] = "TopSites"
self.dataArray.append(topSites)
var trendingVideos = json["tranding_video"]
trendingVideos["type"] = "TrendingVideos"
self.dataArray.append(trendingVideos)
var sports = json["sports"]
sports["type"] = "Sports"
self.dataArray.append(sports)
var jokes = json["jokes"]
jokes["type"] = "jokes"
self.dataArray.append(jokes)
print(self.dataArray[0]["detail"][0].object)
print(self.dataArray[2]["detail"].object)
self.tableView.reloadData()
}
case .Failure(let error):
print(error)
}
}
tableView.registerClass(MyCell.self, forCellReuseIdentifier: "cellId")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("cellId", forIndexPath: indexPath) as! MyCell
myCell.nameLabel.text = dataArray[indexPath.row]["type"].string
if (dataArray[indexPath.row]["type"].string == "News") {
myCell.newsArray = dataArray[indexPath.row]["detail"].arrayObject
}
myCell.myTableViewController = self
return myCell
}
}
class MyCell: UITableViewCell {
var newsArray :NSMutableArray=[]
var myTableViewController: ViewController?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let newsTableView: UITableView = {
let newsTV = UITableView(frame:UIScreen.mainScreen().bounds, style: UITableViewStyle.Plain)
newsTV.registerClass(NewsTableViewCell.self, forCellReuseIdentifier: "NewsTableViewCell")
return newsTV
}()
func tableView(newsTableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsArray.count
}
func tableView(newsTableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = newsTableView.dequeueReusableCellWithIdentifier("cellId", forIndexPath: indexPath) as! NewsTableViewCell
myCell.nameLabel.text = newsArray[indexPath.row]["title"].string
myCell.myTableViewController = myTableViewController
return myCell
}
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Sample Item"
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.boldSystemFontOfSize(14)
return label
}()
func setupViews() {
addSubview(newsTableView)
addSubview(nameLabel)
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": nameLabel]))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": newsTableView]))
}
func handleAction() {
}
}

UIButton inside custom UITableViewCell causes button to be selected in multiple cells

The goal of the UITableView is to display names with a custom styled checkbox beside it. But somehow when I tap the checkbox, multiple checkboxes in the UITableView get selected.
I've googled and implemented multiple solutions from different questions but none of them seem to work.
Custom UITableViewCell
import UIKit
class NameTableViewCell: UITableViewCell {
var name = UILabel()
let checkboxImage_unchecked = UIImage(named: "cellViewCheckbox_unchecked")
let checkboxImage_checked = UIImage(named: "cellViewCheckbox_checked")
let checkbox:UIButton
weak var delegate:HandleCellInteractionDelegate?
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
self.checkbox = UIButton(frame: CGRectMake(35, 16, self.checkboxImage_unchecked!.size.width/2, self.checkboxImage_unchecked!.size.height/2))
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = UIColor.transparant()
self.checkbox.setBackgroundImage(self.checkboxImage_unchecked, forState: .Normal)
self.checkbox.setBackgroundImage(self.checkboxImage_checked, forState: .Selected)
self.name = UILabel(frame: CGRectMake(97,18,200, 30))
self.addSubview(self.name)
self.addSubview(self.checkbox)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func checkboxPushed(sender:AnyObject){
self.checkbox.selected = !self.checkbox.selected
self.delegate?.didPressButton(self)
}
In the protocol I declare a method didPressButton as a callback for when the button is pressed (the delegate needs to implement it)
Protocol
import UIKit
protocol HandleCellInteractionDelegate:class {
func didPressButton(cell:NameTableViewCell)
}
The ViewController
import UIKit
class NameViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, HandleCellInteractionDelegate{
var nameView:NameView {
get {
return self.view as! NameView
}
}
var names:[Name]?
var firstLetters:[String]?
let cellIdentifier = "nameCell"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//self.view.backgroundColor = UIColor.darkSalmon()
}
override func loadView() {
let bounds = UIScreen.mainScreen().bounds
self.view = NameView(frame: bounds)
if(NSUserDefaults.standardUserDefaults().objectForKey("gender") !== nil){
self.loadJSON()
self.getFirstLetters();
self.nameView.tableView.delegate = self;
self.nameView.tableView.dataSource = self;
self.nameView.tableView.registerClass(NameTableViewCell.classForCoder(), forCellReuseIdentifier: "nameCell")
self.nameView.tableView.separatorStyle = .None
self.nameView.tableView.rowHeight = 66.5 }
}
func loadJSON(){
let path = NSBundle.mainBundle().URLForResource("names", withExtension: "json")
let jsonData = NSData(contentsOfURL: path!)
self.names = NamesFactory.createFromJSONData(jsonData!, gender: NSUserDefaults.standardUserDefaults().integerForKey("gender"))
}
func getFirstLetters(){
var previous:String = ""
for name in self.names! {
let firstLetter = String(name.name.characters.prefix(1))
if firstLetter != previous {
previous = firstLetter
self.firstLetters?.append(firstLetter)
print(firstLetter)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (self.names!.count)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return nameCellAtIndexPath(indexPath)
}
func nameCellAtIndexPath(indexPath:NSIndexPath) -> NameTableViewCell {
let cell = self.nameView.tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! NameTableViewCell
self.setNameForCell(cell, indexPath: indexPath)
cell.delegate = self;
cell.checkbox.addTarget(cell, action: "checkboxPushed:", forControlEvents: .TouchUpInside)
return cell
}
func setNameForCell(cell:NameTableViewCell, indexPath:NSIndexPath) {
let item = self.names![indexPath.row] as Name
cell.name.text = item.name
}
func didPressButton(cell: NameTableViewCell) {
print(cell)
}
}
What am I doing wrong & how can I fix it?
You must have to save the check box state. Add one more attribute say checkboxStatus to your Model(Name).I hope you will have indexpath.row.
After this line in nameCellAtIndexPath
cell.delegate = self;
set the tag like this
cell.tag = indexPath.row;
In the delegate method,
func didPressButton(cell: NameTableViewCell) {
print(cell)
let item = self.names![cell.tag] as Name
item.checkboxstatus = cell.checkbox.selected
}
Just one crucial change,
func nameCellAtIndexPath(indexPath:NSIndexPath) -> NameTableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! NameTableViewCell
self.setNameForCell(cell, indexPath: indexPath)
cell.delegate = self;
cell.tag = indexPath.row;
cell.checkbox.addTarget(cell, action: "checkboxPushed:", forControlEvents: .TouchUpInside)
let item = self.names![cell.tag] as Name
cell.checkbox.selected = item.checkboxstatus
return cell
}
Cheers!
This happens because the UITableViewCell are being reused.
You changed the cell when you press the checkbox, you need to keep track of that in your data source model.
In cellForRowAtIndexPath you have to add a condition to check if that checkbox was checked or not. Then you display the appropriate view accordingly.
Edit:
In your example you need to check in nameCellAtIndexPath if the checkbox is checked or not and then display it correct.

Resources