I am trying to show the default selected cell with a different button image. But could not show it when I show the languages page. I am trying to check if it is selected or not. If selected call setupSelected and if not selected call setup function.
But when I chose language I always see my tableview with no selection after all. setSelected works well and the image of the selected cells is changing.
Language
enum Language: String, CaseIterable {
case tr = "tr"
case eng = "en"
case az = "az"
var title: String {
switch self {
case .tr: return "settings.language.turkish".localized
case .eng: return "settings.language.english".localized
case .az: return "settings.language.azerbeijan".localized
}
}
var code: String {
switch self {
case .tr: return "tr"
case .eng: return "en"
case .az: return "az-AZ"
}
}
}
My viewModel
final class LanguageViewModel {
var locales: [Language] = [.tr, .eng, .az] //.ar
var currentLanguage = UserDefaultsUtil.getDeviceLanguage()
var languages: [LanguageModel] = []
func getLanguageTitles() {
languages = [
LanguageModel(language: .tr, isSelected: currentLanguage == locales[0].code ? true : false),
LanguageModel(language: .eng, isSelected: currentLanguage == locales[1].code ? true : false),
LanguageModel(language: .az, isSelected: currentLanguage == locales[2].code ? true : false)
]
}
}
My model
struct LanguageModel {
let language: Language
var isSelected: Bool
}
My ViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return languageCell(indexPath: indexPath)
}
private func languageCell(indexPath: IndexPath) -> LanguageTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: LanguageTableViewCell.identifier, for: indexPath) as! LanguageTableViewCell
if cell.isSelected {
cell.setupSelected(languageTitles[indexPath.row])
return cell
} else {
cell.setup(languageTitles[indexPath.row])
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: LanguageTableViewCell.identifier, for: indexPath) as! LanguageTableViewCell
tableView.deselectRow(at: indexPath, animated: true)
let item = languageTitles[indexPath.row]
cell.setupSelected(item)
tableView.reloadData()
}
My cell
#IBOutlet weak var empty: UIImageView!
#IBOutlet weak var title: UILabel!
var currentLanguage = UserDefaultsUtil.getDeviceLanguage()
static let identifier = String(describing: LanguageTableViewCell.self)
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setup(_ languageTitles: LanguageModel) {
title.label(textStr: languageTitles.language.rawValue, textColor: KSColor.neutral700.getColor(), textFont: UIFont.sfProTextMedium(size: 15), fontSize: 15, lineSpacing: -0.16, paragraphStyle: NSMutableParagraphStyle())
}
func setupSelected(_ languageTitles: LanguageModel) {
setSelected(true, animated: true)
title.label(textStr: languageTitles.language.rawValue, textColor: KSColor.neutral700.getColor(), textFont: UIFont.sfProTextMedium(size: 15), fontSize: 15, lineSpacing: -0.16, paragraphStyle: NSMutableParagraphStyle())
self.empty.image = UIImage(named: "icon_done_bold")
self.empty.makeRounded()
self.empty.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
self.empty.setImageColor(color: KSColor.neutral100.getColor())
self.empty.backgroundColor = KSColor.ocean500Base.getColor()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
self.empty.image = UIImage(named: "icon_done_bold")
self.empty.makeRounded()
self.empty.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
self.empty.setImageColor(color: KSColor.neutral100.getColor())
self.empty.backgroundColor = KSColor.ocean500Base.getColor()
} else {
self.empty.transform = CGAffineTransform(scaleX: 1, y: 1)
self.empty.image = UIImage(named: "icon_radio_unselected")
self.empty.setImageColor(color: KSColor.neutral100.getColor())
self.empty.backgroundColor = .white
}
}
}extension LanguageTableViewCell: Reusable, NibLoadable { }
this is how I see my tableview. In fact, tr is selected.
You need
1- First remove ? : it will give same result
LanguageModel(language: .tr, isSelected: currentLanguage == locales[0].code),
LanguageModel(language: .eng, isSelected: currentLanguage == locales[1].code),
LanguageModel(language: .az, isSelected: currentLanguage == locales[2].code)
2- Don't depend on cell's isSelected property as cells are dequeued when you scroll ,so using it will give you wrong states so instead of
if cell.isSelected {
cell.setupSelected(languageTitles[indexPath.row])
return cell
} else {
cell.setup(languageTitles[indexPath.row])
return cell
}
Do
let item = languageTitles[indexPath.row]
if item.isSelected {
cell.setupSelected(item)
return cell
} else {
cell.setup(item)
return cell
}
3- Remove override func setSelected( and move its implementation inside table didSelectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = languageTitles[indexPath.row]
let value = item.isSelected
// Edit isSelected property and refresh table
languageTitles.forEach { $0.isSelected = false }
item.isSelected = !value
tableView.reloadData()
}
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I'm trying to make expandable UITableView and make a simple rotate animation to UIImage in UITableViewCell from didSelectRowAt when I click the cell the image rotate 180° clockwise and when I click the cell again it will rotate -180° back to its normal state but the animation not working and it has a bug for the first time to click on cell the arrow not rotate I must click in twice to make it rotate as the gif.
TestModel:
struct Titles {
var opened = Bool()
var title = String()
var sectionData = [String]()
}
ViewControllerView:
class ViewControllerView: UIView {
var data = [Titles]()
override init(frame: CGRect) {
super.init(frame: frame)
layoutUI()
data = [
Titles(opened: false, title: "Title1", sectionData: ["Cell1", "Cell2", "Cell3"]),
Titles(opened: false, title: "Title2", sectionData: ["Cell1", "Cell2", "Cell3"]),
Titles(opened: false, title: "Title3", sectionData: ["Cell1", "Cell2", "Cell3"])
]
}
lazy var recipesTableView: UITableView = {
let recipesTableView = UITableView()
recipesTableView.register(TableViewCell.self, forCellReuseIdentifier: "TableViewCell")
recipesTableView.register(TableViewCellTwo.self, forCellReuseIdentifier: "TableViewCellTwo")
return recipesTableView
}()
}
extension ViewControllerView: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if data[section].opened == true {
return data[section].sectionData.count + 1
} else {
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
cell.title.text = data[indexPath.section].title
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCellTwo", for: indexPath) as! TableViewCellTwo
cell.title.text = data[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if data[indexPath.section].opened == true {
data[indexPath.section].opened = false
let section = IndexSet.init(integer: indexPath.section)
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
UIView.animate(withDuration: 1, animations: {
cell.arrowImage.transform = CGAffineTransform.init(rotationAngle: CGFloat.pi)
})
tableView.reloadSections(section, with: .none)
} else {
data[indexPath.section].opened = true
let section = IndexSet.init(integer: indexPath.section)
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
UIView.animate(withDuration: 1, animations: {
cell.arrowImage.transform = CGAffineTransform.identity
})
tableView.reloadSections(section, with: .none)
}
}
}
TableViewCell:
class TableViewCell: UITableViewCell {
lazy var arrowImage: UIImageView = {
var arrow = UIImageView(image: UIImage(systemName: "arrowtriangle.down.fill"))
arrow.tintColor = .black
arrow.translatesAutoresizingMaskIntoConstraints = false
return arrow
}()
}
First we declare an enum with available chevron directions.
enum ChevronDirection: Double {
case up = -180
case down = 0
}
Also we could add an extension to make working with radians much easier
extension Double {
var degreesToRadians: CGFloat {
return CGFloat(self) * (CGFloat(Double.pi) / 180.0)
}
}
Then you should create a UITableViewHeaderFooterView header
class AnyNameHeader: UITableViewHeaderFooterView {
#IBOutlet weak var imgChevron: UIImageView!
var tapDelegate: DelegateToGetNotified?
var currentSection: Int!
func configure(_ text: String, section: Int, isExpanded: Bool) {
currentSection = section
self.set(isExpanded: isExpanded, animated: false)
}
func set(isExpanded: Bool, animated: Bool) {
if isExpanded {
setChevronDirection(ChevronDirection.up, animated: animated)
} else {
setChevronDirection(ChevronDirection.down, animated: animated)
}
}
func setChevronDirection(_ direction: ChevronDirection, animated: Bool) {
if animated {
UIView.animate(withDuration: 0.4, delay: 0.0, options: UIViewAnimationOptions(), animations: { [weak self] in
self?.imgChevron.transform = CGAffineTransform(rotationAngle: direction.rawValue.degreesToRadians)
}, completion: nil)
} else {
self.imgChevron.transform = CGAffineTransform(rotationAngle: direction.rawValue.degreesToRadians)
}
}
//Note: You can add your way to detect user tap over header for me i add a button over the header
#IBAction func headerAction() {
tapDelegate?.didTapHeader(sectionIndex: currentSection)
}
} // End of header
Then we goes to the Final part in our View controller
class MyViewController: UIViewController {
var expandedSectionIndex : Int? = nil {
didSet{
tableView.beginUpdates()
// Collapse previously expanded section
if let oldValue = oldValue {
tableView.deleteRows(at: indexPathsFor(section: oldValue), with: UITableViewRowAnimation.top)
if let header = tableView.headerView(forSection: oldValue) as? AnyNameHeader {
header.set(isExpanded: false, animated: true)
}
}
// Don't continue to Expand new section if already collapsing opened section
if let oldValue = oldValue, let sectionIndex = expandedSectionIndex, sectionIndex == oldValue {
expandedSectionIndex = nil
tableView.endUpdates()
return
}
// Expand new section
if let expandedSectionIndex = expandedSectionIndex {
tableView.insertRows(at: indexPathsFor(section: expandedSectionIndex), with: UITableViewRowAnimation.top)
if let header = tableView.headerView(forSection: expandedSectionIndex) as? AnyNameHeader {
header.set(isExpanded: true, animated: true)
}
}
tableView.endUpdates()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isExpandedSection(section: section) {
return data[section].sectionData.count
} else {
return 0
}
}
}
// MARK: Expanded Section
extension MyViewController {
/// Returns Indexpaths to be Inserted or Removed for a given section Index
fileprivate func indexPathsFor(section: Int) -> [IndexPath] {
var newIndexPaths = [IndexPath]()
for index in 0 ..< data[section].sectionData.count {
let newIndexPath = IndexPath(row: index , section: section)
newIndexPaths.append(newIndexPath)
}
return newIndexPaths
}
fileprivate func isExpandedSection(section: Int) -> Bool {
return expandedSectionIndex == section
}
}
extension MyViewController: DelegateToGetNotified {
func didTapHeader(sectionIndex: Int) {
expandedSectionIndex = sectionIndex
}
}
I have 2 ViewControllers and each ViewController have a UITableView.
In the MainViewController I have few rows and I want to add for each row different Tags from the second ViewController.
My tags are saved in a Dictionary (I don't know if is the best way but I was thinking that maybe I will avoid to append a tag twice using a Dict instead of Array).
The problem is that I don't append correctly the selected tags and I don't know how I should do it.
Here I've created a small project which reflect my issue: https://github.com/tygruletz/AppendTagsToCells
Here is the code for Main VC:
class ChecklistVC: UIViewController {
#IBOutlet weak var questionsTableView: UITableView!
//Properties
lazy var itemSections: [ChecklistItemSection] = {
return ChecklistItemSection.checklistItemSections()
}()
var lastIndexPath: IndexPath!
var selectedIndexPath: IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
questionsTableView.reloadData()
}
}
extension ChecklistVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let itemCategory = itemSections[section]
return itemCategory.checklistItems.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return itemSections.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "checklistCell", for: indexPath) as! ChecklistCell
let itemCategory = itemSections[indexPath.section]
let item = itemCategory.checklistItems[indexPath.row]
cell.delegate = self
cell.configCell(item)
cell.vehicleCommentLabel.text = item.vehicleComment
cell.trailerCommentLabel.text = item.trailerComment
cell.tagNameLabel.text = item.vehicleTags[indexPath.row]?.name
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goChecklistAddComment" {
let addCommentVC = segue.destination as! ChecklistAddCommentVC
addCommentVC.delegate = self
}
if segue.identifier == "goChecklistAddTag" {
let checklistAddTag = segue.destination as! ChecklistAddTagVC
checklistAddTag.indexForSelectedRow = self.selectedIndexPath
checklistAddTag.tagsCallback = { result in
print("result: \(result)")
let item = self.itemSections[self.lastIndexPath.section].checklistItems[self.lastIndexPath.row]
item.vehicleTags = result
}
}
}
}
Here is the code for Tags ViewController:
class ChecklistAddTagVC: UIViewController {
// Interface Links
#IBOutlet weak var tagsTitleLabel: UILabel!
#IBOutlet weak var tagsTableView: UITableView!
// Properties
var tagsDictionary: [Int: Tag] = [:]
var tagsAdded: [Int:Tag] = [:]
var tagsCallback: (([Int:Tag]) -> ())?
var indexForSelectedRow: IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
tagsTableView.tableFooterView = UIView()
tagsDictionary = [
1: Tag(remoteID: 1, categoryID: 1, name: "Tag1", colour: "red"),
2: Tag(remoteID: 2, categoryID: 1, name: "Tag2", colour: "blue"),
3: Tag(remoteID: 3, categoryID: 1, name: "Tag3", colour: "orange"),
4: Tag(remoteID: 4, categoryID: 1, name: "Tag4", colour: "black")
]
print("Received index for SelectedRow: \(indexForSelectedRow ?? IndexPath())")
}
}
extension ChecklistAddTagVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tagsDictionary.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "defectAndDamageTagCell", for: indexPath) as! ChecklistAddTagCell
cell.configCell()
cell.delegate = self
cell.tagNameLabel.text = tagsDictionary[indexPath.row + 1]?.name.capitalized
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}
extension ChecklistAddTagVC: ChecklistAddTagCellDelegate{
// When the user press Add Tag then will be added in a dictionary and sent to ChecklistVC using a callback closure.
func addTagBtnPressed(button: UIButton, tagLabel: UILabel) {
if button.currentTitle == "+"{
button.setTitle("-", for: UIControl.State.normal)
tagLabel.textColor = UIColor.orange
tagsAdded = [0: Tag(remoteID: 1, categoryID: 1, name: tagLabel.text ?? String(), colour: "red")]
print(tagsAdded[0]?.name ?? String())
tagsCallback?(tagsAdded)
}
else{
button.setTitle("+", for: UIControl.State.normal)
tagLabel.textColor = UIColor.black
tagsAdded.removeValue(forKey: 0)
print(tagsAdded)
tagsCallback?(tagsAdded)
}
}
}
Here is a capture with my issue:
Thank you for reading this !
I fix it !
The solution is below. Also you can find the completed project at this link:
https://github.com/tygruletz/AppendCommentsToCells
MainVC:
class ChecklistVC: UIViewController {
#IBOutlet weak var questionsTableView: UITableView!
//Properties
lazy var itemSections: [ChecklistItemSection] = {
return ChecklistItemSection.checklistItemSections()
}()
var lastIndexPath: IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
questionsTableView.reloadData()
}
}
extension ChecklistVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let itemCategory = itemSections[section]
return itemCategory.checklistItems.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return itemSections.count
}
// Set the header of each section
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let checklistItemCategory = itemSections[section]
return checklistItemCategory.name.uppercased()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "checklistCell", for: indexPath) as! ChecklistCell
let itemCategory = itemSections[indexPath.section]
let item = itemCategory.checklistItems[indexPath.row]
cell.delegate = self
cell.configCell(item)
cell.vehicleCommentLabel.text = item.vehicleComment
cell.trailerCommentLabel.text = item.trailerComment
let sortedTagNames = item.vehicleTags.keys.sorted(by: {$0 < $1}).compactMap({ item.vehicleTags[$0]})
print("Sorted tag names: \(sortedTagNames.map {$0.name})")
let joinedTagNames = sortedTagNames.map { $0.name}.joined(separator: ", ")
cell.tagNameLabel.text = joinedTagNames
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goChecklistAddComment" {
let addCommentVC = segue.destination as! ChecklistAddCommentVC
addCommentVC.delegate = self
}
if segue.identifier == "goChecklistAddTag" {
let addTagVC = segue.destination as! ChecklistAddTagVC
addTagVC.delegate = self
addTagVC.addedTags = itemSections[lastIndexPath.section].checklistItems[lastIndexPath.row].vehicleTags
}
}
}
extension ChecklistVC: ChecklistCellDelegate {
func tapGestureOnCell(_ cell: ChecklistCell) {
showOptionsOnCellTapped(questionsTableView.indexPath(for: cell)!)
}
func showOptionsOnCellTapped(_ indexPath: IndexPath){
let addComment = UIAlertAction(title: "📝 Add Comment", style: .default) { action in
self.lastIndexPath = indexPath
self.performSegue(withIdentifier: "goChecklistAddComment", sender: nil)
}
let addTag = UIAlertAction(title: "🏷 Add Tag ⤵", style: .default) { action in
self.showOptionsForAddTag(indexPath)
}
let actionSheet = configureActionSheet()
actionSheet.addAction(addComment)
actionSheet.addAction(addTag)
self.present(actionSheet, animated: true, completion: nil)
}
// A menu from where the user can choose to add tags for Vehicle or Trailer
func showOptionsForAddTag(_ indexPath: IndexPath){
self.lastIndexPath = indexPath
let addVehicleTag = UIAlertAction(title: "Add Vehicle tag", style: .default) { action in
self.performSegue(withIdentifier: "goChecklistAddTag", sender: nil)
}
let addTrailerTag = UIAlertAction(title: "Add Trailer tag", style: .default) { action in
self.performSegue(withIdentifier: "goChecklistAddTag", sender: nil)
}
let actionSheet = configureActionSheet()
actionSheet.addAction(addVehicleTag)
actionSheet.addAction(addTrailerTag)
self.present(actionSheet, animated: true, completion: nil)
}
func configureActionSheet() -> UIAlertController {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
actionSheet.addAction(cancel)
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad ){
actionSheet.popoverPresentationController?.sourceView = self.view
actionSheet.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
actionSheet.popoverPresentationController?.permittedArrowDirections = []
}
return actionSheet
}
}
// Receive Comments from ChecklistAddCommentVC using the Delegate Pattern
extension ChecklistVC: ChecklistAddCommentDelegate {
func receiveVehicleComment(vehicleComment: String?, trailerComment: String?) {
let item = itemSections[lastIndexPath.section].checklistItems[lastIndexPath.row]
item.vehicleComment = vehicleComment ?? String()
item.trailerComment = trailerComment ?? String()
questionsTableView.reloadData()
}
}
// Receive Tags from ChecklistAddTagVC using the Delegate Pattern
extension ChecklistVC: ChecklistAddTagVCDelegate{
func receiveAddedTags(tags: [Int : Tag]) {
let item = self.itemSections[self.lastIndexPath.section].checklistItems[self.lastIndexPath.row]
item.vehicleTags = tags
}
}
AddTagsVC:
protocol ChecklistAddTagVCDelegate {
func receiveAddedTags(tags: [Int: Tag])
}
class ChecklistAddTagVC: UIViewController {
// Interface Links
#IBOutlet weak var tagsTableView: UITableView!
// Properties
var tagsDictionary: [Int: Tag] = [:]
var addedTags: [Int: Tag] = [:]
var delegate: ChecklistAddTagVCDelegate?
var indexPathForBtn: IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
tagsTableView.tableFooterView = UIView()
tagsDictionary = [
1: Tag(remoteID: 1, categoryID: 1, name: "Tag1", color: "red"),
2: Tag(remoteID: 2, categoryID: 1, name: "Tag2", color: "blue"),
3: Tag(remoteID: 3, categoryID: 1, name: "Tag3", color: "orange"),
4: Tag(remoteID: 4, categoryID: 1, name: "Tag4", color: "black")
]
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Added tags: \(addedTags.map {$1.name})")
setupButtons()
tagsTableView.reloadData()
}
}
extension ChecklistAddTagVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tagsDictionary.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "defectAndDamageTagCell", for: indexPath) as! ChecklistAddTagCell
cell.configCell()
cell.delegate = self
cell.tagNameLabel.text = tagsDictionary[indexPath.row + 1]?.name.capitalized
indexPathForBtn = indexPath
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}
extension ChecklistAddTagVC: ChecklistAddTagCellDelegate{
// When the user press Add Tag then will be added in a dictionary and sent to ChecklistVC using a callback closure.
func addTagBtnPressed(button: UIButton, tagLabel: UILabel) {
let buttonPosition: CGPoint = button.convert(CGPoint.zero, to: tagsTableView)
let indexPath = tagsTableView.indexPathForRow(at: buttonPosition)
let indexPathForBtn: Int = indexPath?.row ?? 0
let tag: Tag = tagsDictionary[indexPathForBtn + 1] ?? Tag(remoteID: 0, categoryID: 0, name: String(), color: String())
if button.currentTitle == "+"{
button.setTitle("-", for: UIControl.State.normal)
tagLabel.textColor = UIColor.orange
// Add selected tag to Dictionary when the user press +
addedTags[tag.remoteID] = tag
}
else{
button.setTitle("+", for: UIControl.State.normal)
tagLabel.textColor = UIColor.black
// Delete selected tag from Dictionary when the user press -
addedTags.removeValue(forKey: tag.remoteID)
}
// Send the Dictionary to ChecklistVC
if delegate != nil{
delegate?.receiveAddedTags(tags: addedTags)
}
print("\n ****** UPDATED DICTIONARY ******")
print(addedTags.map {"key: \($1.remoteID) - name: \($1.name)"})
}
// Setup the state of the buttons and also the color of the buttons to be orange if that Tag exist in `addedTags` dictionary.
func setupButtons(){
for eachAddedTag in addedTags {
if eachAddedTag.value.remoteID == tagsDictionary[1]?.remoteID {
print(eachAddedTag)
}
}
}
}
And here is how looks now:
Can you try to handle tableview:didselectrowatindexpath instead of using the segue and on didselectrowatindexpath show the add tag vc.
I am implementing an expandable and collapsable table cell on the click of a button in the custom table cell. I have tried the following code but it expands only single cell at a time. That is if I click a cell it expands but if I click on another cell it expands and the already expanded cell collapses.
var selectedIndexPath: IndexPath?
func configure(cell: MyProposalCustomCell, forRowAtIndexPath indexPath: IndexPath) {
let pool = myProposalsDetails[indexPath.row]
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
cell.proposalNumber.text = pool.proposalNumber
cell.pickUpLocation.text = pool.pickupLocation
cell.dropLocation.text = pool.dropLocation
cell.journeyType.text = pool.journeyType
cell.firstShiftOnwardTime.text = pool.firstPickupTime
cell.firstShiftReturnTime.text = pool.firstEndTime
if let numberOfInterests = pool.noOfInterest {
cell.numberOfInterest.text = String(numberOfInterests)
}
if let operatingDays = pool.operatingDays {
cell.daysOfOperation.attributedText = Utility.getProposedOperatingDays(operatingDays: operatingDays)
}
cell.expandCollapse.tag = indexPath.row
cell.expandCollapse.addTarget(self, action: #selector(expandTableCell(sender:)), for: .touchUpInside)
if selectedIndexPath == indexPath {
UIView.animate(withDuration: 0.3, animations: {
cell.backgroundColor = CustomColor.selectedBackground.color
cell.bottomView.backgroundColor = CustomColor.selectedBackground.color
cell.expandCollapse.setImage(UIImage(named: "collapse_arrow.png"), for: .normal)
if let proposedStartDate = pool.startDate {
let propStartDate = Date(timeIntervalSince1970: proposedStartDate)
cell.proposedStartDate.text = Utility.getFormattedDate(date: propStartDate)
cell.proposedStartDateTxt.text = NSLocalizedString("Proposed start date", comment: "")
}
cell.returnTime.alpha = 0.0
})
} else {
UIView.animate(withDuration: 0.3, animations: {
cell.backgroundColor = UIColor.white
cell.expandCollapse.setImage(UIImage(named: "down_arrow.png"), for: .normal)
cell.proposedStartDateTxt.text = NSLocalizedString("Journey type", comment: "")
cell.bottomView.backgroundColor = UIColor.white
cell.proposedStartDate.text = pool.journeyType
cell.returnTime.isHidden = false
})
}
}
This is the expandable button action:
func expandTableCell(sender: UIButton) {
let indexPath = IndexPath(row: sender.tag, section: 0)
if selectedIndexPath == indexPath {
selectedIndexPath = nil
} else {
let previousSelIndex = selectedIndexPath
selectedIndexPath = indexPath
if let previousSelectedIndexPath = previousSelIndex {
if tripStatus.tripType != .splitShift {
if let previousSelectedCell = myProposals.cellForRow(at: previousSelectedIndexPath) as? MyProposalCustomCell {
configure(cell: previousSelectedCell, forRowAtIndexPath: previousSelectedIndexPath)
}
} else {
if let previousSelectedCell = myProposals.cellForRow(at: previousSelectedIndexPath) as? MyProposalSplitShiftCell {
configureSplitShift(cell: previousSelectedCell, forRowAtIndexPath: previousSelectedIndexPath)
}
}
}
}
updateSelectedCell(indexPath: indexPath)
myProposals.beginUpdates()
myProposals.endUpdates()
}
func updateSelectedCell(indexPath: IndexPath) {
if tripStatus.tripType != .splitShift {
if let selectedCell = myProposals.cellForRow(at: indexPath) as? MyProposalCustomCell {
configure(cell: selectedCell, forRowAtIndexPath: indexPath)
}
} else {
if let selectedCell = myProposals.cellForRow(at: indexPath) as? MyProposalSplitShiftCell {
configureSplitShift(cell: selectedCell, forRowAtIndexPath: indexPath)
}
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let headingHeight: CGFloat = 128
let detailsHeight: CGFloat = 80
let splitShiftDetailsHeight: CGFloat = 215
switch tripStatus.tripType {
case .oneWayTrip, .roundTrip:
if selectedIndexPath != nil && indexPath.compare(selectedIndexPath! as IndexPath) == ComparisonResult.orderedSame {
return headingHeight + detailsHeight
}
return headingHeight
case .splitShift:
if selectedIndexPath != nil && indexPath.compare(selectedIndexPath! as IndexPath) == ComparisonResult.orderedSame {
return headingHeight + splitShiftDetailsHeight
}
return headingHeight
}
}
I want to get multiple cells to be expanded. How to achieve this?
This is very straight forward, instead of taking selectedIndexPath try to keep one variable in your custom UITableViewCell class something like,
class ExpandableCell: UITableViewCell {
var isExpanded: Bool = false
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 moreButtonTapped(_ sender: Any) {
}
}
And then try to toggle the isExpanded boolean flag, and try to perform your func updateSelectedCell(indexPath: IndexPath) {} action.
class CalenderCell: UITableViewCell {
#IBOutlet var lblDay: UILabel!
#IBOutlet var lblRest: UILabel!
#IBOutlet var imgCompleted: UIImageView!
override func awakeFromNib()
{
super.awakeFromNib()
}
func updateCell(exerciseobject : Exercise)
{
let resttext = NSLocalizedString("restday", comment: "")
let day = NSLocalizedString("day", comment: "")
let dayexercise = "\(day) \(exerciseobject.exerciseDayID)"
if !exerciseobject.exerciseRestDay
{
lblDay.text = dayexercise
if exerciseobject.exerciseDayStatus
{
imgCompleted.isHidden = false
}
else
{
imgCompleted.isHidden = true
}
lblRest.isHidden = true
}
else
{
lblDay.isHidden = true
imgCompleted.isHidden = true
lblRest.text = resttext
viewWithTag(100)?.backgroundColor = UIColor(red:1.00, green:0.21, blue:0.28, alpha:1.0)
}
}
}
The above is my custom cell class and the below is my tableview class.
When I try to build the app everything is working perfect but whenever I fast scroll the data is mismatching or same data appering in more cells.
I tried:
prepareforreuse()
{
//clearing label and images here
}
in my custom cell class but still getting the fast scrollview data mismatch
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if let cell = calenderTable.dequeueReusableCell(withIdentifier: "CalenderCell", for: indexPath) as? CalenderCell
{
cell.updateCell(exerciseobject: days[indexPath.row])
return cell
}
else
{
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return days.count
}
Either use prepareForReuse and reset your controls to default values or make sure to update all the state for every path in the updateCell method.
e.g.
func updateCell(exerciseobject : Exercise)
{
let resttext = NSLocalizedString("restday", comment: "")
let day = NSLocalizedString("day", comment: "")
let dayexercise = "\(day) \(exerciseobject.exerciseDayID)"
if !exerciseobject.exerciseRestDay
{
lblDay.text = dayexercise
lblDay.isHidden = false
if exerciseobject.exerciseDayStatus
{
imgCompleted.isHidden = false
}
else
{
imgCompleted.isHidden = true
}
lblRest.isHidden = true
viewWithTag(100)?.backgroundColor = nil
}
else
{
lblDay.isHidden = true
imgCompleted.isHidden = true
lblRest.text = resttext
lblRest.isHidden = false
viewWithTag(100)?.backgroundColor = UIColor(red:1.00, green:0.21, blue:0.28, alpha:1.0)
}
}
i have a textfield, when the user tap on it, a table1 appear with a list of countries currency, then the user choose a currency and add it to table2, i can add one currency , when i add another one to table2 the app crash with the following error: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'request for rect at invalid index path ( {length = 2, path = 0 - 1})'
class CurrencySecondStep: UIViewController ,UITableViewDataSource, UITableViewDelegate,UITextFieldDelegate {
#IBOutlet weak var tableViewAddedByUser: UITableView!
#IBOutlet weak var amountToBeExchanged: UILabel!
#IBOutlet weak var tableViewFor: UITableView!
#IBOutlet weak var textFieldFor: UITextField!
//cell identifier
let cellReuseIdentifier = "cell"
//struct
struct Currency {
var country = String()
var currencyCode = String()
var currencyFlag = UIImage()
}
var addedCurrencyByUserArray = [Currency]()
var currencyArr = [
Currency(country: "United Arab Emirates Dirham", currencyCode: "AED",currencyFlag: UIImage(named:"United-Arab-Emirates")!),
Currency(country: "US Dollar", currencyCode: "USD",currencyFlag: UIImage(named:"United-States")!),
Currency(country: "Afghan Afghani (1927–2002)", currencyCode: "AFA",currencyFlag: UIImage(named:"Afghanistan")!),
Currency(country: "Albania Lek", currencyCode: "ALL",currencyFlag: UIImage(named:"Albania")!),
Currency(country: "Algerian Dinar", currencyCode: "DZD",currencyFlag: UIImage(named:"Algeria")!),
Currency(country: "Angolan Kwanza", currencyCode: "AOA",currencyFlag: UIImage(named:"Angola")!),
Currency(country: "Argentine Peso", currencyCode: "ARS",currencyFlag: UIImage(named:"Argentina")!),
Currency(country: "Armenian Dram", currencyCode: "AMD",currencyFlag: UIImage(named:"Armenia")!)
]
//searched results
var filteredCurrenciesOffer = [Currency]()
//currency code to be sent to google finance
var currencyCodeOffer = ""
var currencyPassed = ""
var countryChoice = ""
var countryCodeChoice = ""
var countryflagChoice = UIImage()
#IBAction func textFieldChanged(_ sender: AnyObject) {
tableViewFor.isHidden = true
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableViewFor.delegate = self
tableViewFor.dataSource = self
tableViewFor.isHidden = true
textFieldFor.delegate = self
textFieldFor.addTarget(self, action: #selector(textFieldActive), for: UIControlEvents.touchDown)
textFieldFor.addTarget(self, action:#selector(textFieldDidChange) , for: UIControlEvents.editingChanged)
//hide or show keyboard
textFieldFor.addTarget(self, action: #selector(textFieldShouldReturn), for: UIControlEvents.touchDown)
amountToBeExchanged.text = currencyPassed
//table added By User
tableViewAddedByUser.delegate = self
tableViewAddedByUser.dataSource = self
tableViewAddedByUser.isHidden = true
}
when the user press add Button to insert the second element the app crash
#IBAction func addPressed(_ sender: Any) {
if(textFieldFor.text == ""){
displayError(title: "Currency Type",message:"Please Pick Your Currency")
}else{
tableViewAddedByUser.isHidden = false
addedCurrencyByUserArray.append(Currency(country: countryChoice, currencyCode: countryCodeChoice,currencyFlag: countryflagChoice))
tableViewAddedByUser.beginUpdates()
let insert = IndexPath(row: addedCurrencyByUserArray.count - 1, section: 0)
tableViewAddedByUser.insertRows(at: [insert], with: .automatic)
tableViewAddedByUser.endUpdates()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: TextField Functions
func textFieldDidEndEditing(_ textField: UITextField) {
filteredCurrenciesOffer = currencyArr.filter { currencyGiven in
return currencyGiven.country.lowercased().contains(textFieldFor.text!.lowercased())
}
//reload table based on the user pick
tableViewFor.reloadData()
}
func textFieldDidChange(textField: UITextField) {
//update search based on user search
filteredCurrenciesOffer = currencyArr.filter { currencyGiven in
return currencyGiven.country.lowercased().contains(textFieldFor.text!.lowercased())
}
tableViewFor.reloadData()
}
// Toggle the tableView visibility when click on textField
func textFieldActive(textField: UITextField) {
tableViewFor.isHidden = !tableViewFor.isHidden
}
//hide keyboard
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
// MARK: TableView Functions
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count:Int?
if (tableView == self.tableViewFor && textFieldFor.text != ""){
count = filteredCurrenciesOffer.count
}else if(tableView == self.tableViewFor && textFieldFor.text == ""){
count = currencyArr.count
}
if(tableView == self.tableViewAddedByUser){
count = addedCurrencyByUserArray.count
print("number of rows")
print(count!)
}
return count!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableViewFor.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
var currency: Currency
if (tableView == self.tableViewFor){
if ( textFieldFor.text != "") {
currency = filteredCurrenciesOffer[indexPath.row]
} else {
currency = currencyArr[indexPath.row]
}
cell.flag.image = currency.currencyFlag
cell.country.text = currency.country
cell.currencyCode.text = currency.currencyCode
}
if (tableView == self.tableViewAddedByUser){
currency = addedCurrencyByUserArray[indexPath.row]
cell.flag.image = currency.currencyFlag
cell.country.text = currency.country
cell.currencyCode.text = currency.currencyCode
}
return cell
}
// MARK: UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var currency: Currency
if(tableView == tableViewFor){
if textFieldFor.text != "" {
currency = filteredCurrenciesOffer[indexPath.row]
} else {
currency = currencyArr[indexPath.row]
}
//add image to currency text field
let leftImageView = UIImageView()
leftImageView.image = currency.currencyFlag
let leftView = UIView()
leftView.addSubview(leftImageView)
leftView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
leftImageView.frame = CGRect(x: 10, y: 10, width: 20, height: 20)
textFieldFor.leftViewMode = .always
textFieldFor.leftView = leftView
//add country to currency text field
textFieldFor.text = currency.country
countryChoice = currency.country
countryCodeChoice = currency.currencyCode
countryflagChoice = currency.currencyFlag
currencyCodeOffer = currency.currencyCode
tableViewFor.isHidden = true
textFieldFor.endEditing(true)
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0.0
}
//hide the table and keyboard
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
guard let touch:UITouch = touches.first else
{
return;
}
if touch.view != tableViewFor
{
textFieldFor.endEditing(true)
tableViewFor.isHidden = true
}
}
func displayError(title:String,message:String){
// Create the alert controller
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
// Create the actions
let retryButton = UIAlertAction(title: "Ok", style: UIAlertActionStyle.cancel) {
UIAlertAction in
NSLog("Cancel Pressed")
}
// Add the actions
alertController.addAction(retryButton)
}
}
I think the problem happens in your
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
On the first line you always dequeue a cell from the tableViewFor even when you need a cell for tableViewAddedByUser.
Try replacing the first line with this which will dequeue a cell from the tableView sent in the parameters instead:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell
If that doesn't work then try without specifying the indexPath like so:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell