Implementing UIStackView inside UICollectionViewCell - swift

I am trying to display user ratings on the collectionview as follows with using two images, half.png and full.png.
But even though I hard coded with 1 star to test, but sometimes it shows more than 1 star during the scroll. It keeps increasing.
I wonder how to handle this issue?
CollectionViewCell
import UIKit
class CollectionCell: UICollectionViewCell {
#IBOutlet var ratingView: UIStackView!
var rating : Float!
{
didSet {
self.ratingView.addArrangedSubview(addRating())
}
}
func addRating() -> UIStackView
{
let stackView = UIStackView()
stackView.axis = .horizontal
let oneStarImage = UIImage(named: "full_star")
let halfStarImage = UIImage(named: "half_star")
//hard coded
var value = 1.0
while true {
value -= 1
if value >= 0 {
print("Add 1 star")
let imageView = UIImageView()
imageView.image = oneStarImage
stackView.addArrangedSubview(imageView)
} else if value == -0.5 {
print("Add half a star")
let imageView = UIImageView()
imageView.image = halfStarImage
stackView.addArrangedSubview(imageView)
break
}
else {
break
}
}
return stackView
}
}
CollectionViewController
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! CollectionViewCell
cell.rating = 1.0
return cell
}

As UICollectionViewCell is reused, every time you set the rating, a new view is added to the UIStackView. Therefore, you should reset the UIStackView before adding the images. Also, it maybe unnecessary to create a UIStackView every time you add a rating. You can reuse the ratingView instead. So, the complete code should be:
var rating : Float!
{
didSet {
self.addRating()
}
}
func addRating()
{
// Empty the ratingView first
ratingView.arrangedSubviews.forEach { $0.removeFromSuperview() }
let oneStarImage = UIImage(named: "full_star")
let halfStarImage = UIImage(named: "half_star")
//hard coded
var value = 1.0
while true {
value -= 1
if value >= 0 {
print("Add 1 star")
let imageView = UIImageView()
imageView.image = oneStarImage
ratingView.addArrangedSubview(imageView)
} else if value == -0.5 {
print("Add half a star")
let imageView = UIImageView()
imageView.image = halfStarImage
ratingView.addArrangedSubview(imageView)
break
}
else {
break
}
}
}

And also you can use for reused UICollectionViewCell or UITableViewCell,
override func prepareForReuse() {
super.prepareForReuse()
self.ratingView = UIStackView()
}
override func to prepare cell to reuse. However you should re init your components that you assign values in cell.

Related

Table View Design overlapping

I am new to swift .I want to display the records with image view in table view cell . I have defined the property with leadingAnchor , trailingAnchor, widthAnchor, heightAnchor with content view . But when I run the app it overlapping the view .
Here is the code in cell .
import UIKit
class PeopleCell: UITableViewCell {
static let identifier = "PeopleCell"
let containerView:UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.clipsToBounds = true // this will make sure its children do not go out of the boundary
return view
}()
let profileImageView:UIImageView = {
let img = UIImageView()
img.contentMode = .scaleAspectFill // image will never be strecthed vertially or horizontally
img.translatesAutoresizingMaskIntoConstraints = false // enable autolayout
img.layer.cornerRadius = 35
img.clipsToBounds = true
return img
}()
let firstnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 20)
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let lastnameTitleLabel:UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 14)
label.textColor = .white
label.backgroundColor = #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)
label.layer.cornerRadius = 5
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(profileImageView)
containerView.addSubview(firstnameTitleLabel)
containerView.addSubview(lastnameTitleLabel)
self.contentView.addSubview(containerView)
profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:70).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:70).isActive = true
containerView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo:self.profileImageView.trailingAnchor, constant:10).isActive = true
containerView.trailingAnchor.constraint(equalTo:self.contentView.trailingAnchor, constant:-10).isActive = true
containerView.heightAnchor.constraint(equalToConstant:40).isActive = true
firstnameTitleLabel.topAnchor.constraint(equalTo:self.containerView.topAnchor).isActive = true
firstnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
firstnameTitleLabel.trailingAnchor.constraint(equalTo:self.containerView.trailingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
lastnameTitleLabel.topAnchor.constraint(equalTo:self.firstnameTitleLabel.bottomAnchor).isActive = true
lastnameTitleLabel.leadingAnchor.constraint(equalTo:self.containerView.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureCell(firstName: String, lastName: String) {
firstnameTitleLabel.text = "Firstname :\(firstName)"
lastnameTitleLabel.text = "Lastname : \(lastName)"
}
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
}
}
}
Here is the view controller code .
import UIKit
import Combine
class PeopleViewController: UIViewController {
var coordinator: PeopleBaseCoordinator?
init(coordinator: PeopleBaseCoordinator) {
super.init(nibName: nil, bundle: nil)
self.coordinator = coordinator
title = "People"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private let viewModel = ViewModel()
private var subscribers = Set<AnyCancellable>()
var activityIndicator = UIActivityIndicatorView(style: .medium)
private lazy var tableView: UITableView = {
let tableview = UITableView()
tableview.translatesAutoresizingMaskIntoConstraints = false
tableview.dataSource = self
tableview.prefetchDataSource = self
tableview.showsVerticalScrollIndicator = false
tableview.register(PeopleCell.self, forCellReuseIdentifier: PeopleCell.identifier)
return tableview
}()
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator.startAnimating()
setUpUI()
setUpBinding()
self.activityIndicator.stopAnimating()
// Do any additional setup after loading the view.
}
private func setUpUI() {
view.backgroundColor = .white
title = "People List "
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo:view.safeAreaLayoutGuide.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo:view.safeAreaLayoutGuide.leftAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo:view.safeAreaLayoutGuide.rightAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
// Creating constrain for Indecator
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
private func setUpBinding() {
viewModel
.$peoples
.receive(on : RunLoop.main)
.sink { [weak self ] _ in
self?.tableView.reloadData()
}
.store(in: &subscribers)
viewModel.getPeople()
}
}
extension PeopleViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.peoples.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: PeopleCell.identifier, for: indexPath) as? PeopleCell
else { return UITableViewCell() }
let row = indexPath.row
let people = viewModel.peoples[row]
cell.configureCell(firstName: people.firstName, lastName: people.lastName)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
extension PeopleViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.getPeople()
}
}
Here is the result .
This is rather tricky because it seems your constraints are fine, assuming that your tableview height is 100, but the screenshot tableview cells seem a little shorter than 100. Let's assume the cell height is 100 correct.
I suggest you try configuring the imageView (and other views) in override func layoutSubViews(), which is a function that renders whenever the contentView's bound change. It should also be noted that better practice is where the imageSize is relative to the cell/contentView's frame instead of hardcoded values.
So it should look like
import UIKit
class PeopleCell: UITableViewCell {
let profileImageView:UIImageView = {
let img = UIImageView()
return img
}()
override func layoutSubviews() {
super.layoutSubviews()
profileImageView.translatesAutoresizingMaskIntoConstraints = false profileImageView.centerYAnchor.constraint(equalTo:self.contentView.centerYAnchor).isActive = true
profileImageView.leadingAnchor.constraint(equalTo:self.contentView.leadingAnchor, constant:10).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant:self.frame.width * 0.7).isActive = true
//You may want to try with other type of contentMode such as aspectFit, etc
profileImageView.contentMode = .scaleAspectFill
profileImageView.layer.cornerRadius = self.frame.width / 2
profileImageView.clipsToBounds = true
}
//If above doesn't work, you may want to look into the imageConfiguration function you made and ensure that contentMode is applied properly.
func configureImageCell(row: Int, viewModel: ViewModel) {
profileImageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.profileImageView.image = image
self?.profileImageView.contentMode = .scaleAspectFill
}
}
If all of the above code doesn't work, try to find the profileImageView size values by using breakpoints or ViewHierarchy within Xcode. To check the height of image or cell itself, they should be sufficient for you to find clues to resolve the issue.
All the best.

Duplicating UIViews in StackView when scrolling TableView Swift

Issue: When I scroll the tableView, my configureCell() method appends too many views to the stackView inside the cell.
When the cell is first displayed, and I press the UIButton, the stack is unhidden and first shows the right amount of views, after scrolling, the amount is duplicated.
prepareForReuse() is empty right now. I want to keep the stackView unHidden after scrolling.
I set the heightAnchor for the UIView as it is programmatic layout.
Expected Outcome: User taps on the button, the cell stackView is unhidden and the cell expands to chow the uiviews related to the cell.
When I call it in the cellForRowAt, nothing happens. Because im not sure how to modify the method for IndexPath.row.
protocol DataDelegate: AnyObject {
func displayDataFor(_ cell: TableViewCell)
}
class TableViewCell: UITableViewCell {
#IBOutlet weak var stackView: UIStackView! {
didSet {
stackView.isHidden = true
}
}
#IBOutlet button: UIButton!
var model = Model()
var detailBool: Bool = false
#IBAction func action(_ sender: Any) {
self.claimsDelegate?.displayClaimsFor(self)
detailBool.toggle()
}
func configureCellFrom(model: Model) {
if let addedData = model.addedData {
if addedData.count > 1 {
for data in addedData {
let dataView = DataView()
dataView.heightAnchor.constraint(equalToConstant: 65).isActive = true
dataView.dataNumber.text = data.authDataNumber
self.stackView.addArrangedSubview(dataView)
}
}
}
}
}
How would I call this in cellForRowAt, so its only created the correct amount of uiviews and not constantly adding?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.dataDelegate = self
cell.configureCellFrom(model: model[indexPath.row])
//if let addedData = model.addedData {
//if addedData.count > 1 {
//for data in addedData {
//let dataView = DataView()
//dataView.heightAnchor.constraint(equalToConstant: 65).isActive = true
//dataView.dataNumber.text = data.authDataNumber
//self.stackView.addArrangedSubview(dataView)
// }}}
cell.layoutIfNeeded()
cell.selectionStyle = .none
return cell
}
extension ViewController: DataDelegate {
func displayDataFor(_ cell: TableViewCell) {
if tableView.indexPath(for: cell) != nil {
switch cell.detailBool {
case true:
UIView.performWithoutAnimation {
tableView.beginUpdates()
cell.detailArrow.transform = CGAffineTransform(rotationAngle:.pi)
cell.stackView.isHidden = false
tableView.endUpdates()
}
case false:
UIView.performWithoutAnimation {
tableView.beginUpdates()
cell.stackView.isHidden = true
cell.detailArrow.transform = CGAffineTransform.identity
tableView.endUpdates()
}
}
}
}
}
If I understand correctly, you would like to create an expandable TableView? If yes you can do it a lot of different ways, but you have to change your approach totally. Please refer LBTA approach:
LBTA video
My favourite the Struct approach, where you create a struct and you can save the complication with the 2D array:
Struct stackoverflow link

UITapGestureRecognizer with UIImageView in Table View Cell not working Swift

In my Table View Controller I'm requesting data from my backend. Then adding the data to an array with a view model for each row from the request.
let getNotifications = GETNotificationsByUserID(user_id: user_id)
getNotifications.getNotifications { notifications in
self.notifications = notifications.map { notification in
let ret = NotificationViewModel()
ret.mainNotification = notification
return ret
}
class NotificationViewModel {
var mainNotification: Notifications? {}
}
struct Notifications: Codable {
var supporter_picture:String?
}
In the same table view controller, I'm then adding each item from the array to a variable in my table view cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath) as! NotificationCell
cell.user_image.isUserInteractionEnabled = true
let item = self.notifications[indexPath.item]
cell.viewModel = item
return cell
}
Downloading the image from the data from my table view cell variable and setting my UIImageView image.
UITableViewCell
class NotificationCell: UITableViewCell {
var viewModel: NotificationViewModel? {
didSet {
if let item = viewModel {
self.username.text = item.mainNotification?.supporter_username
item.supporterImageDownloader = DownloadImage()
item.supporterImageDownloader?.imageDidSet = { [weak self] image in
self?.user_image.image = image
}
if let picture = item.mainNotification?.supporter_picture {
item.supporterImageDownloader?.downloadImage(urlString: picture)
} else {
self.user_image.image = UIImage(named: "profile-placeholder-user")
}
}
}
}
}
The UIImageView is created from a lazy closure variable. The lazy closure variable also holds a UITapGestureRecognizer.
UITableViewCell
lazy var user_image: UIImageView = {
let image = UIImageView()
let gesture = UITapGestureRecognizer()
gesture.addTarget(self, action: #selector(userImageClicked(_:)))
gesture.numberOfTapsRequired = 1
gesture.numberOfTouchesRequired = 1
image.addGestureRecognizer(gesture)
image.isUserInteractionEnabled = true
return image
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.isUserInteractionEnabled = true
addSubview(user_image)
user_imageContraints()
}
func user_imageContraints() {
user_image.translatesAutoresizingMaskIntoConstraints = false
user_image.topAnchor.constraint(equalTo:topAnchor, constant: 8).isActive = true
user_image.leadingAnchor.constraint(equalTo:leadingAnchor, constant: 8).isActive = true
user_image.heightAnchor.constraint(equalToConstant: 40).isActive = true
user_image.widthAnchor.constraint(equalToConstant: 40).isActive = true
}
#objc func userImageClicked(_ sender: UITapGestureRecognizer) {
print("image clicked")
}
For some reason when my UIImageView is clicked it doesn't do anything.
The problem is this line:
addSubview(user_image)
Change it to:
contentView.addSubview(user_image)
You must never add any views directly to a cell — only to its contentView.

Swift - saveContext(), tableView, scrollView, and reloadRows issue?

I have tableView that uses a NSFetchedResultsController to populate data. When clicking on a cell, it takes you to a detailViewController of that object. And the following two properties are pushed forward with prepare(for:).
var coreDataStack: CoreDataStack!
var selectedGlaze: Glaze?
Inside the detailView, I have 2 cells. The first is cell that contains a scrollView with an array of images:
import UIKit
protocol SwipedRecipeImageViewDelegate: class {
func recipeImageViewSwiped(_ cell: RecipePhotoTableViewCell, selectInt: Int)
}
class RecipePhotoTableViewCell: UITableViewCell, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
// -
var imagesArray: [Data] = []
var selectedImageData: Int = 0
// -
weak var delegate: SwipedRecipeImageViewDelegate?
// -
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = .clear
self.selectionStyle = .none
scrollView.delegate = self
scrollView.isUserInteractionEnabled = false // Allows didSelectAtRow:
contentView.addGestureRecognizer(scrollView.panGestureRecognizer) // Allows Scrolling
}
override func layoutSubviews() {
super.layoutSubviews()
setImages()
setOffsetX(pageNumber: selectedImageData)
}
func configureCell(section: Int, row: Int, images: [RecipeImage], arrayInt: Int, delegate: SwipedRecipeImageViewDelegate) {
self.delegate = delegate
selectedImageData = arrayInt
for image in images {
guard let imageData = image.recipeImageData else { return }
imagesArray.append(imageData)
}
}
#IBAction func pageChanged(_ sender: UIPageControl) {
setOffsetX(pageNumber: sender.currentPage)
}
func setOffsetX(pageNumber: Int) {
pageControl.currentPage = pageNumber
let offsetX = contentView.bounds.width * CGFloat(pageNumber)
DispatchQueue.main.async {
UIView.animate(withDuration: 0.2, delay: 0, options: UIView.AnimationOptions.curveEaseOut, animations: {
self.scrollView.contentOffset.x = offsetX
}, completion: nil)
}
}
func setImages() {
// Set Page Count:
pageControl.numberOfPages = imagesArray.count
// Set Frame For ImageViews + Scroll View:
for index in 0..<imagesArray.count {
let imageView = UIImageView()
imageView.frame.size = contentView.bounds.size
imageView.frame.origin.x = contentView.bounds.width * CGFloat(index)
imageView.image = UIImage(data: imagesArray[index])
imageView.contentMode = .scaleAspectFill
imageView.layer.masksToBounds = true // Limits Frame Size
scrollView.addSubview(imageView)
}
// Set ScrollView Size:
scrollView.contentSize = CGSize(width: (contentView.bounds.width * CGFloat(imagesArray.count)), height: contentView.bounds.height)
scrollView.delegate = self
}
// Set Page Number:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
self.pageControl.currentPage = Int(pageNumber)
delegate?.recipeImageViewSwiped(self, selectInt: pageControl.currentPage)
}
The second cell contains a stackView with some labels to display data that the image shows. It accepts a lot of parameters and then sets the textColor and changes some labels. Nothing too exciting so I didn't include the code.
DetailViewController:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellForRowAt: ", indexPath)
switch indexPath.section {
case sectionImage: // Section 0:
guard
let images = selectedGlaze?.glazeImage,
let glazeImageSelected = selectedGlaze?.glazeImageSelected // This is a Double
else { return returnDefaultCell() }
let imageArray = images.allObjects as! [RecipeImage] // Takes NSSet of relational data and changes it into an Array to be passed into the image cell.
let imageSelected = Int(glazeImageSelected) // Double Converted to Int
switch indexPath.row {
case 0:
let cell = returnRecipeImageCell()
return configureRecipeImageCell(cell: cell, for: indexPath, imagesArray: imageArray, imageSelected: imageSelected)
case 1:
let cell = returnAtmosphereCell()
return configureAtmosphereCell(cell: cell, for: indexPath, imagesArray: imageArray, imageSelected: imageSelected)
default: return returnDefaultCell()
}
}
}
SwipedRecipeImageViewDelegate:
func recipeImageViewSwiped(_ cell: RecipePhotoTableViewCell, selectInt: Int) {
selectedGlaze?.glazeImageSelected = Double(selectInt)
coreDataStack.saveContext()
DispatchQueue.main.async { //
self.tableView.beginUpdates() //
let row1: IndexPath = [0,1] //
self.tableView.reloadRows(at: [row1], with: .automatic) //
self.tableView.endUpdates() //
}
}
The Issue:
The issue I'm having is reloading the second cell to be updated with the correct information after the recipeImageViewSwiped() is called. Seen here: https://imgur.com/a/fIYfehf
This happens when the code inside the DispatchQueue.main.async block is active. When the block is comment out, this happens: https://imgur.com/a/fYUVZKH - Which is what I'd expect. (Other than the cell at [0,1] isn't updated).
Specifically, when the tableView reloads row [0,1], cellForRowAt() only gets called on that row, [0,1]. But I'm not sure why the cell at [0,0], with the image, flicks back to the original image shown in the scrollView.
Goal:
My goal is to have the cell with the scrollView not flicker after being swiped on. But also to save the context, so that the object can save which image in the array is selected. And then to update/reload the second cell with the new information the image that's selected, so it can update it's labels correctly.
EDIT:
Removing the following in layoutSubviews() has this affect: https://imgur.com/a/vwrZfus - Which looks like it's mostly working. But still has a strange animation.
override func layoutSubviews() {
super.layoutSubviews()
setImages()
// setOffsetX(pageNumber: selectedImageData)
}
EDIT 2:
This looks like its entirely an issue with setting up the cell's view. Along with layout Subviews.
EDIT 3:
I added a Bool: hasSetLayout and a switch inside of layoutSubviews() and it appears to be working as I want. - However if any one still has any information to help me understand this issue, I'd appreciate it.
var hasSetLayout = false
override func layoutSubviews() {
super.layoutSubviews()
switch hasSetLayout {
case false: setImages(selectedPhoto: selectedImageData)
default: break
}
}
try to reload row without animation :
UIView.performWithoutAnimation {
tableView.reloadRows(at: [indexPath], with: .none)
}

How to print value in UILabel from the values of TableView dynamically

I'm having a tableView with a custom cell, whenever I click the checkbox button the value inside the cell increases i.e.,(0 to 1) in cell, and on uncheck value decreases, that works perfectly. But whenever I try to print those values from the cell to a UILabel outside tableView, the values are not changing.
This is the below code I have Used
var data = [[String: AnyObject]]()
func getDetails() {
let paymentURL = paymentListURL + String(28) + "&student_id=" + String(33)
Alamofire.request(paymentURL).responseJSON { (response) in
if ((response.result.value) != nil) {
var jsonVar = JSON(response.result.value!)
print(jsonVar)
if let da = jsonVar["types"].arrayObject {
self.data = da as! [[String:AnyObject]]
}
if self.data.count > 0 {
self.tableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TabCell
cell.checkB.tag = indexPath.row
let ip = data[indexPath.row]
cell.nameText.text = ip["title"] as? String
if cell.nameText.text == "Mandatory testing" {
cell.checkB.checkState = .checked
cell.backgroundColor = UIColor.lightGray
cell.checkB.backgroundColor = UIColor.lightGray
}
if ip["mandatory"] as? String == "yes" {
moneyText.text = ip["amount"] as? String
//moneyText is UILabel outside Tableview
cell.amountValue.text = ip["amount"] as? String
cell.checkB.isEnabled = false
} else {
moneyText.text = "0"
if cell.amountValue.text == "1"{
print("ONE")
}
}
return cell
}
func didPressButton(_ tag: Int) {
let indexPath = IndexPath.init(row: 0, section: 0)
let cell = tableView.cellForRow(at: indexPath) as! TabCell
moneyText.text = String(cell.someValue)
}
And for TableviewCell I Used
protocol TabCellDelegate {
func didPressButton(_ tag: Int)
}
class TabCell: UITableViewCell {
#IBOutlet weak var checkB: M13Checkbox!
#IBOutlet weak var nameText: UILabel!
#IBOutlet weak var amountValue: UILabel!
var someValue: Int = 0 {
didSet {
amountValue.text = "\(someValue)"
}
}
#IBAction func checkBAction(_ sender: M13Checkbox) {
cellDelegate?.didPressButton(sender.tag)
if checkB.checkState == .checked {
someValue += 1
} else if checkB.checkState == .unchecked {
someValue -= 1
}
}
}
I tried first adding those values from cell to an Array, and then adding all the values in array and printing in UILabel, but the values are not changing, it was only incrementing.i.e., even after unchecking the checkbox the value is increasing.
I tried even using protocol it did not work for me
Any Help will be appreciated.
You are updating someValue from the checkBAction handler inside the TabCell. The property didSet handler will then update the amountValue label. This is the reason, why the cell's label is being updated.
You do not have any code that updates the moneyText after someValue changed. You only set moneyText.text from tableView(_:cellForRow:), but this is called when a cell is being displayed, maybe multiple times after scrolling etc.
You could do the following:
Create a delegate property inside the cell (use a custom protocol as type)
Set the controller to be that delegate
When the value changes, call a function of that delegate (e.g. call the controller)
Inside that, update the moneyText
As an idea (might not compile because I don't have all your classes):
protocol MyTabCellProtocol {
func checkboxChanged(_ checkbox:M13Checkbox, atRow:Integer)
}
class TabCell: UITableViewCell {
weak delegate:MyTabCellProtocol?
// ...
#IBAction
func checkBAction(_ sender: M13Checkbox) {
if checkB.checkState == .checked {
someValue += 1
} else if checkB.checkState == .unchecked {
someValue -= 1
}
delegate?.checkboxChanged(self, checkB.tag)
}
}
class MyController : UIViewController, MyTabCellProtocol {
func checkboxChanged(_ checkbox:M13Checkbox, atRow:Integer) {
moneyText.text = "\(checkbox.someValue)"
}
}
But if I think further, I would suggest to refactor the whole code a little. The problem I see is that your action handler inside the cell does update the someValue property of the cell, but does not update the outside model (ip["amount"]). I think what you should do is:
Inside the cell's checkBAction, just call the delegate and provide the information about the row that has been modified (self.checkB.tag) and the check state. Do not update the amountValue here!
In the delegate implementation, update the model ip["amount"]
Call reloadRows(at:with:) of the table view to refresh the cell
Then, cellForRow is automatically being called, in which you then update the cell and the outer label.