i am new to swift and now i'm trying to create a dropdown table on storyboard using cocoapods. i have followed all the tutorial from the cocoapods & i guess there is something wrong with my code here.
Please give me an enlightened here why it won't work where i have declared the dropdown.
Thankyou for helping, appreciate it guys.
import iOSDropDown
class ViewController: UIViewController {
#IBOutlet weak var DropDownMenu: DropDown!
let dropDown = DropDown()
let view = UIView()
dropDown.anchorView = view
dropDown.optionArray = ["option 1", "option 2", "option 3"]
override func viewDidLoad() {
super.viewDidLoad()
let dropDown = DropDown()
let view = UIView()
dropDown.anchorView = view // UIView or UIButton
// For set direction .top, .bottom or .any(for automatic)
dropDown.direction = .any
dropDown.optionArray = ["data 1", "data 2", "data 3"]
// for custom cell you can set xib
dropDown.cellNib = UINib(nibName: "MyCell", bundle: nil)
// select item from dropdown
dropDown.selectionAction = { [unowned self] (index: Int, item: String) in
print("Selected item: \(item) at index: \(index)")
}
// if you want to set custom width then you can set.
dropDownLeft.width = 200
dropDown.show() // For Display Dropdown
dropDown.hide() // For hide Dropdown
}
for more detail and features you can refer https://github.com/AssistoLab/DropDown
import iOSDropDown
class ViewController: UIViewController,UITextFieldDelegate {
#IBOutlet weak var dropDown: DropDown!
override func viewDidLoad() {
super.viewDidLoad()
// The list of array to display. Can be changed dynamically
dropDown.optionArray = ["Option 1", "Option 2", "Option 3"]
//Its Id Values and its optional
dropDown.optionIds = [1,23,54,22]
// The the Closure returns Selected Index and String
dropDown.didSelect{(selectedText , index ,id) in
print("Selected String: \(selectedText) \n index: \(index)")
self.dropDown.hideList()
self.dropDown.text = selectedText
}
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
dropDown.showList()
}
}
you can refer this link https://cocoapods.org/pods/iOSDropDown
Step #1 : Add Pod and install
pod 'DropDown'
Step #2: Adding a drop-down in UIButton Tap
import UIKit
import DropDown
class ViewController: UIViewController {
let dropDown = DropDown()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func tapChooseItems(_ sender: UIButton) {
dropDown.dataSource = ["option 1", "option 2", "option 3"]
dropDown.anchorView = sender
dropDown.bottomOffset = CGPoint(x: 0, y: sender.frame.size.height)
dropDown.show()
dropDown.selectionAction = { [weak self] (index: Int, item: String) in
guard let _ = self else { return }
sender.setTitle(item, for: .normal)
}
}
}
Step #3: Adding a drop-down inside UITableView
import UIKit
import DropDown
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
let dropDown = DropDown()
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Result: \(indexPath.row+1): "
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
dropDown.dataSource = ["option 1", "option 2", "option 3"]
dropDown.anchorView = cell
dropDown.bottomOffset = CGPoint(x: 0, y: cell.frame.size.height)
dropDown.backgroundColor = .gray
dropDown.show()
dropDown.selectionAction = { [weak self] (index: Int, item: String) in
guard let _ = self else { return }
cell.textLabel?.text = "Result: \(indexPath.row+1): \(item)"
}
}
}
}
Write the initialization code in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
let dropDown = DropDown()
let view = UIView()
dropDown.anchorView = view
dropDown.optionArray = ["option 1", "option 2", "option 3"]
}
Related
Below is my app in the simulator. It has two CollectionViews each with multiple UIButtons. I currently have a segue for UIButtons: "Good Work" and "Nice Try" in the 'firstData' string you see below, in the code.
Regardless of which one I press, however, it opens to the same view linked to identifer "Good Work." I am at a loss of how to get this to open to their own pages.
Code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
class FirstCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var buttonOne: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
func commonInit() {
guard buttonOne != nil else { return }
buttonOne.titleLabel!.font = UIFont(name: "Marker Felt", size: 20)
buttonOne.layer.cornerRadius = 10
buttonOne.clipsToBounds = true
buttonOne.layer.borderWidth = 1.0
buttonOne.layer.borderColor = UIColor.white.cgColor
}
}
class SecondCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var buttonTwo: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
func commonInit() {
guard buttonTwo != nil else { return }
buttonTwo.titleLabel!.font = UIFont(name: "Marker Felt", size: 20)
buttonTwo.layer.cornerRadius = 10
buttonTwo.clipsToBounds = true
buttonTwo.layer.borderWidth = 1.0
buttonTwo.layer.borderColor = UIColor.white.cgColor
}
}
class TwoCollectionsViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var firstCV: UICollectionView!
#IBOutlet weak var secondCV: UICollectionView!
#IBAction func Buttons(_ sender: Any) {
if let btn = sender as? UIButton {
print(btn.restorationIdentifier!)
guard let button = sender as? UIButton else { return }
guard let id = Int(button.restorationIdentifier!) else {return}
if id < firstData.count {
performSegue(withIdentifier: "Good Work", sender: btn)
}
else {
performSegue(withIdentifier: "Nice Try", sender: btn)
}
print(button.restorationIdentifier!)
}
}
let firstData: [String] = [
"Good Work", "Nice Try", "Btn 3", "Btn 4", "Btn 5", "Btn 6"
]
let secondData: [String] = [
"Second 1", "Second 2", "Second 3", "Second 4", "Second 5", "Second 6"
]
override func viewDidLoad() {
super.viewDidLoad()
firstCV.dataSource = self
firstCV.delegate = self
secondCV.dataSource = self
secondCV.delegate = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// if it's the First Collection View
if collectionView == firstCV {
return firstData.count
}
// it's not the First Collection View, so it's the Second one
return secondData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == firstCV {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCell", for: indexPath) as! FirstCollectionViewCell
cell.buttonOne.setTitle(firstData[indexPath.item], for: []) //allows for button title change in code above
cell.buttonOne.restorationIdentifier = "\(indexPath.row)"
return cell
}
// it's not the First Collection View, so it's the Second one
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCell", for: indexPath) as! SecondCollectionViewCell
cell.buttonTwo.setTitle(secondData[indexPath.item], for: [])
cell.buttonTwo.restorationIdentifier = "\(indexPath.row + firstData.count)"
return cell
}
}
The reason you only get to "Good Work" ...
In cellForItemAt you are doing this:
cell.buttonOne.restorationIdentifier = "\(indexPath.row)"
That sets the .restorationIdentifier for "Good Work" button to 0 (Zero) and the .restorationIdentifier for "Nice Try" button to 1.
Then, in #IBAction func Buttons(_ sender: Any) {:
if id < firstData.count {
performSegue(withIdentifier: "Good Work", sender: btn)
}
else {
performSegue(withIdentifier: "Nice Try", sender: btn)
}
Since firstData.count equals 6, and the id from the buttons will be either 0 or 1, the are, of course, each less-than-6.
Using your approach, you would likely want to do this:
if id == 0 {
performSegue(withIdentifier: "Good Work", sender: btn)
}
else if id == 1 {
performSegue(withIdentifier: "Nice Try", sender: btn)
}
I have a custom cell that has 2 labels, myLabel and numLabel, and a stepper. I have my custom cell in a Swift file and XIB file. I want when I click + or - button on the stepper, my numLabel change with the value of the stepper. I don't know how to pass the stepper value to the viewController where I have my tableView. Later want to save the stepper value to CoreDate how can I do that?. I'm just a beginner. Thank you for helping.
MyCell.swift
import UIKit
class MyCell: UITableViewCell {
static let identifier = "MyCell"
static func nib() -> UINib {
return UINib(nibName: "MyCell", bundle: nil)
}
public func configure(with name: String, number: String) {
myLabel.text = name
numLabel.text = number
}
#IBOutlet var myLabel: UILabel!
#IBOutlet var numLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
table.register(MyCell.nib(), forCellReuseIdentifier: MyCell.identifier)
table.delegate = self
table.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.identifier, for: indexPath) as! MyCell
cell.configure(with: "Item 1", number: "1")
return cell
}
}
My Screen Shot
You can do this easily with a "callback" closure:
class MyCell: UITableViewCell {
static let identifier: String = "MyCell"
#IBOutlet var myStepper: UIStepper!
#IBOutlet var numLabel: UILabel!
#IBOutlet var myLabel: UILabel!
// "callback" closure - set my controller in cellForRowAt
var callback: ((Int) -> ())?
public func configure(with name: String, number: String) {
myLabel.text = name
numLabel.text = number
}
#IBAction func stepperChanged(_ sender: UIStepper) {
let val = Int(sender.value)
numLabel.text = "\(val)"
// send value back to controller via closure
callback?(val)
}
static func nib() -> UINib {
return UINib(nibName: "MyCell", bundle: nil)
}
}
Then, in cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.identifier, for: indexPath) as! MyCell
cell.configure(with: "Item 1", number: "1")
// set the "callback' closure
cell.callback = { (val) in
print("Stepper in cell at \(indexPath) changed to: \(val)")
// do what you want when the stepper value was changed
// such as updating your data array
}
return cell
}
Use a delegate for a generic approach. This allows flexibility in how your cell interacts with the tableview, and enables type checking as you would expect from Swift.
Typically, for a UITableView, you would have an array of data that drives the content of the cells. In your case, let's assume that it's MyStruct (inside your view controller):
struct MyStruct {
let name: String
var value: Int
}
var myStructs: [ MyStruct ] = [
MyStruct( name: "Name 1", value: 1 ),
MyStruct( name: "Name 2", value: 2 ),
MyStruct( name: "Name 3", value: 3 ) ]
Create MyCellDelegate, and place in it whatever methods that you require to communicate changes from the cell to the view controller. For example:
protocol MyCellDelegate: class {
func didSet( value: Int, for myStructIndex: Int )
}
class MyCell: UITableViewCell {
weak var delegate: MyCellDelegate!
var myStructIndex: Int!
...
}
For your table view, assign the delegate when dequeuing the cell, and implement the protocol.
class ViewController: MyCellDelegate, UITableViewDelegate, UITableViewDataSource {
func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyCell.identifier, for: indexPath) as! MyCell
let myStruct = myStructs[indexPath.row] // You may want to ensure that you are in bounds
cell.delegate = self
cell.myStructIndex = indexPath.row
cell.configure( with: myStruct.name, number: myStruct.value )
return cell
}
func didSet( value: Int, for myStructIndex: Int ) {
// Now MyViewController sees the change.
myStructs[myStructIndex].value = value
}
}
Lastly, in your MyCell, whenever the value changes, for example in your stepper, invoke:
#IBAction func stepperChanged( _ sender: UIStepper ) {
let integerValue = Int( sender.value.round() )
numLabel.text = "\(integerValue)"
// Tell the view controller about the change: what happened, and to what cell.
self.delegate.didSet( value: integerValue, for: self.myStructIndex )
}
I am trying to create a tableview where once a tableview cell is selected, it passes the name of the cell to a new view controller. However, when trying to change the organizationName I receive the error, 'Cannot assign value of type 'Organizations?' to type 'String'. How do I fix this so that the rowTitle of the tableview cell is then displayed as the organizationName on the next viewController?
Here is my code for the first view controller.
import UIKit
struct Organizations {
var sectionTitle = String()
var rowTitles = [String]()
}
class SearchOrganizationsViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var searchOrganizations: Organizations?
var organizations = [Organizations(sectionTitle: "section 1", rowTitles: ["organization 1", "organization 2", "organization 3"]),
Organizations(sectionTitle: "section 2", rowTitles: ["organization 1", "organization 2"]),
Organizations(sectionTitle: "section 3", rowTitles: ["organization 1"])
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
extension SearchOrganizationsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "searchCell")
cell?.textLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
cell?.textLabel?.numberOfLines = 3
if searching {
cell?.textLabel?.text = self.searchArray[indexPath.section].rowTitles[indexPath.row]
} else {
cell?.textLabel?.text = self.organizations[indexPath.section].rowTitles[indexPath.row]
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedOrganizations = organizations[indexPath.row]
performSegue(withIdentifier: "organizationDetailSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? OrganizationsDetailViewController {
destination.organization = selectedOrganizations
}
}
}
Here is my code for the second view controller.
import UIKit
class OrganizationsDetailViewController: UIViewController {
#IBOutlet weak var organizationNameLabel: UILabel!
var organization: Organizations? = nil {
didSet { self.organizationNameLabel.text = organization?.sectionTitle}
} // error is here
var organizationName: String = ""
override func viewDidLoad() {
super.viewDidLoad()
organizationNameLabel.text = organizationName
}
}
The error is telling you why. You’re trying to assign the organization to the variable that wants a string.
You want to assign it to the actual organization inside the detailController
Or assign the string value to one of the row titles or whatever value you want to show.
organizationName = selectedOrganization.rowTitles[indexPath.row]
For a cleaner detail I would use the didSetoption inside your detail view controller
Class DetailViewController:UIViewController {
var organization:Organizations? = nil
var selectedRow: Int = 0
func viewDidLoad() {
super.viewDidLoad()
setupLabel()
}
private func setupLabel() {
guard let org = self.organization else {
print(“no organization set”)
return
}
self.organizationLabel.text = org.rowTitles[selectedRow]
}
}
Then in your tableView VC I would just assign the selected organization to the variable in the detail
So you could add another variable that holds the selected row alongside your selectedOrganization in your tableView controller in your didSelectRow func, set the variable to the selected row and you should be good to go
var selectedRow = 0
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? OrganizationsDetailViewController {
destination.organization= selectedOrganizations
destination.selectedRow = selectedRow
}
}
Right now I have a program that displays different organizations in a tableview. When an organization is selected, then it displays a screen that passes the name of that specific organization. However, the problem is that when I click on "organization 2" of "section 1" I get the results that would be displayed in "organization 1" of "section 1". How would I fix this so that, when I click "organization 2" of "section 1" it displays the correct information?
Here is my code for the first view controller.
import UIKit
struct Organizations {
var sectionTitle = String()
var rowTitles = [String]()
}
class SearchOrganizationsViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var searchOrganizations: Organizations?
var selectedRow = 0
var organizations = [Organizations(sectionTitle: "section 1", rowTitles: ["organization 1", "organization 2", "organization 3"]),
Organizations(sectionTitle: "section 2", rowTitles: ["organization 1", "organization 2"]),
Organizations(sectionTitle: "section 3", rowTitles: ["organization 1"])
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
extension SearchOrganizationsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "searchCell")
cell?.textLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
cell?.textLabel?.numberOfLines = 3
if searching {
cell?.textLabel?.text = self.searchArray[indexPath.section].rowTitles[indexPath.row]
} else {
cell?.textLabel?.text = self.organizations[indexPath.section].rowTitles[indexPath.row]
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedOrganizations = organizations[indexPath.section]
performSegue(withIdentifier: "organizationDetailSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? OrganizationsDetailViewController {
destination.organization = selectedOrganizations
destination.selectedRow = selectedRow
}
}
Here is my code for the second view controller.
import UIKit
class OrganizationsDetailViewController: UIViewController {
#IBOutlet weak var organizationNameLabel: UILabel!
var organization: Organizations? = nil
var selecterRow: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
setupLabel()
}
private func setupLabel() {
guard let org = self.organization else { return }
self.organizationNameLabel.text = org.rowTitles[selectedRow]
}
}
Modify your didSelectRow to this to fix your mentioned issue:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedOrganizations = searching ? searchArray[indexPath.section] : organizations[indexPath.section]
selectedRow = indexPath.row
performSegue(withIdentifier: "organizationDetailSegue", sender: self)
}
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.