I have problem with indexPath.row. Inside the cellForRowAt this lines work perfect:
let cell = tableView.dequeueReusableCell(withIdentifier: "MenuViewCell", for: indexPath) as! MenuViewCell
let item = converters[indexPath.row]
cell.ConverterName.text = item.converterName
cell.converterImage.image = item.converterImg
But when i try to implement it inside viewdidload i've got an error:
"Use of unresolved identifier 'indexPath'"
And code:
struct converter {
let converterName: String
let converterImg: UIImage
}
var converters = [converter(converterName: "Converter of time", converterImg: #imageLiteral(resourceName: "time")),
converter(converterName: "Converter of pressure", converterImg: #imageLiteral(resourceName: "davlenie")),
converter(converterName: "Converter of speed", converterImg: #imageLiteral(resourceName: "skorost")),
converter(converterName: "Converter of distance", converterImg: #imageLiteral(resourceName: "dlinna"))]
override func viewDidLoad() {
super.viewDidLoad()
let item = converters[indexPath.row]
let itemSort = item.converterName
self.converters = itemSort.sorted { $0 < $1 }
self.tableView.reloadData()
}
viewDidLoad is not passed an IndexPath parameter like cellForRowAt is.
IndexPath is used to identify either the current cell to be built or selected. You do not need it in viewDidLoad. You have 4 converters in the array and can select any you like whenever you need to just by using its index.
Related
I have a button in my cell that if the user holds for a certain length of time it will trigger a popup. I am having trouble passing the cell data with the long press button.
Heres how I submit and pass data with a regular tap...
cell.addButton.tag = (indexPath as NSIndexPath).row
cell.addButton.addTarget(self, action: #selector(Dumps.addAction(_:)), for: UIControl.Event.touchUpInside)
.
#IBAction func addAction(_ sender: Any) {
let tag = (sender as AnyObject).tag
let cell = tableView.cellForRow(at: IndexPath.init(row: tag!, section: 0)) as! DumpsCell01
codeData = cell.codeField.text! }
The above works fine.
Heres how I submit the button with the long press gesture. Its passing nil through _sender I think
cell.deleteButton.tag = (indexPath as NSIndexPath).row
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(Dumps.deleteAction(_:)))
cell.deleteButton.addGestureRecognizer(longGesture)
.
#objc func deleteAction(_ sender: UIGestureRecognizer){
let tag = (sender as AnyObject).tag
let cell = tableView.cellForRow(at: IndexPath.init(row: tag!, section: 0)) as! DumpsCell01
cell.codeLabel.backgroundColor = UIColor.red }
How would I pass the data through this method?
You should be using the tag of the UIButton instead of the UILongPressGestureRecognizer as you have done above.
func deleteAction(_ sender: UILongPressGestureRecognizer) {
guard let tag = (sender.view as? UIButton)?.tag else { return }
let cell = tableView.cellForRow(at: IndexPath(row: tag, section: 0)) as? DumpsCell01
cell?.codeLabel.backgroundColor = .red
}
Note: I've also avoided force unwrapping as you should too through-out the project.
so basically when the like button is tapped its suppose to change the number of likes in the tableview but what's happening is it makes a duplicate cell at the bottom of the tableview array and its with the new number of likes ik im doing something wrong but im not sure what if there's a Better way to do this please I would appreaciate it being shown to me
import UIKit
import Firebase
class motivationviewcontroller : UIViewController,UITableViewDataSource ,UITableViewDelegate{
var motivationThoughts = [motivationDailyModel]()
var Mous = motivationDailyModel()
var tableview : UITableView!
override func viewDidLoad() {
print("the user logged in is \( String(describing: Auth.auth().currentUser?.email))")
tableview = UITableView(frame: view.bounds, style: .plain)
tableview.backgroundColor = UIColor.white
view.addSubview(tableview)
var layoutGuide : UILayoutGuide!
layoutGuide = view.safeAreaLayoutGuide
let cellNib = UINib(nibName: "dailyMotivationTableViewCell", bundle: nil)
tableview.register(cellNib, forCellReuseIdentifier: "DailyThoughtCELL")
tableview.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableview.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableview.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableview.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableview.dataSource = self
tableview.delegate = self
loaddailymotivation()
//listener()
self.tableview.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
//loaddailymotivation()
self.tableview.reloadData()
}
//======================================================================
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
motivationThoughts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DailyThoughtCELL", for: indexPath) as? dailyMotivationTableViewCell
cell!.generateCellsforDailymotivation(_MotivationdataMODEL: self.motivationThoughts[indexPath.row])
return cell!
}
func loaddailymotivation() {
FirebaseReferece(.MotivationDAILY).addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) { // this line means if the chage that happened in the document was equal to added something
let data = diff.document.data()
print("we have\(snapshot.documents.count) documents in this array")
let dailyMotivationID = data["objectID"] as! String
let dailymotivationTitle = data["Motivation title"] as! String //calls the data thats heald inside of motivation title in firebase
let dailyMotivationScripture = data["daily motivation scripture"] as! String //calls the data thats heald inside of Motivation script in firebase
let dailyMotivationNumberOfLikes = data["Number of likes in daily motivation post"]as! Int
let MdataModel = motivationDailyModel(RealMotivationID: dailyMotivationID, RealmotivationTitle: dailymotivationTitle, RealmotivationScrip: dailyMotivationScripture, RealmotivationNumberOfLikes: dailyMotivationNumberOfLikes)
self.motivationThoughts.append(MdataModel)
}
//===== //=====
if (diff.type == .modified) {
print("Modified data: \(diff.document.data())")
let newdata = diff.document.data()
let dailyMotivationID = newdata["objectID"] as! String
let dailymotivationTitle = newdata["Motivation title"] as! String //calls the data thats heald inside of motivation title in firebase
let dailyMotivationScripture = newdata["daily motivation scripture"] as! String //calls the data thats heald inside of Motivation script in firebase
let dailyMotivationNumberOfLikes = newdata["Number of likes in daily motivation post"]as! Int
let MdataModel = motivationDailyModel(RealMotivationID: dailyMotivationID, RealmotivationTitle: dailymotivationTitle, RealmotivationScrip: dailyMotivationScripture, RealmotivationNumberOfLikes: dailyMotivationNumberOfLikes)
self.motivationThoughts.append(MdataModel)
// here you will receive if any change happens in your data add it to your array as you want
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}
}
I don't know if loaddailymotivation() is called again after viewDidLoad, but in that method you're doing self.motivationThoughts.append(MdataModel) which is supposed to to exactly what you said it's doing.
I thinkyou need a method that identifies the item that needs to be modified in the array, and replace/modify it.
If you add a new object, you have one more element in your table
First of all, I'm a fairly new to Swift and I've been looking for a good solution to handle Previous, Next and Done buttons on a keyboard across different custom cells in UITableView. I've looked at the various solutions on Stack Overflow but none of them fit 100% for what I need.
My tableview has one field (UITextField, UITextView, etc.) per row and need a generic way to move from one cell to the next. Some of the solutions don't account for scenarios where the next cell might be offscreen.
I've come up with a solution which I'll post as an answer. Feel free to comment on ways to improve if you have suggestions!
Please check this library. Simple and effective. You just to need to install via cocoa pods and single line code in appDelegate
pod 'IQKeyboardManagerSwift'
https://github.com/hackiftekhar/IQKeyboardManager
In App delegate
IQKeyboardManager.sharedManager().enable = true
For my custom cells, I have a base class as a foundation since I'm creating everything programmatically. It looks like this:
class BaseTableViewCell: UITableViewCell {
weak var delegate: BaseTableViewCellDelegate? = nil
var indexPath: IndexPath? = nil
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
fatalError("BaseTableViewCell setupViews not overridden")
}
func handleBecomeFirstResponser() {
// handle in derived class
}
func handleResignFirstResponder() {
// handle in derived class
}
}
Also, I've got a delegate for this base class that looks like this:
protocol BaseTableViewCellDelegate: class {
func cellEdited(indexPath: IndexPath)
func cellPreviousPressed(indexPath: IndexPath)
func cellNextPressed(indexPath: IndexPath)
func cellNeedsResize(indexPath: IndexPath)
}
// Using extension to provide default implementation for previous/next actions
// This makes then optional for a cell that doesn't need them
extension BaseTableViewCellDelegate {
func cellPreviousPressed(indexPath: IndexPath) {}
func cellNextPressed(indexPath: IndexPath) {}
func cellNeedsResize(indexPath: IndexPath) {}
}
I'm using an extension for a pure-swift mechanism to make the previous and next implementations optional in case we have a situation where we don't need these buttons.
Then, in my BaseTableViewCell class, I've got a function to setup the keyboard toolbar like this (below). I've got another function to support a UITextView as well (there might be a better way to do this; not sure).
func setupKeyboardToolbar(targetTextField: UITextField, dismissable: Bool, previousAction: Bool, nextAction: Bool) {
let toolbar: UIToolbar = UIToolbar()
toolbar.sizeToFit()
var items = [UIBarButtonItem]()
let previousButton = UIBarButtonItem(image: UIImage(imageLiteralResourceName: "previousArrowIcon"), style: .plain, target: nil, action: nil)
previousButton.width = 30
if !previousAction {
previousButton.isEnabled = false
} else {
previousButton.target = self
previousButton.action = #selector(toolbarPreviousPressed)
}
let nextButton = UIBarButtonItem(image: UIImage(imageLiteralResourceName: "nextArrowIcon"), style: .plain, target: nil, action: nil)
nextButton.width = 30
if !nextAction {
nextButton.isEnabled = false
} else {
nextButton.target = self
nextButton.action = #selector(toolbarNextPressed)
}
items.append(contentsOf: [previousButton, nextButton])
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard))
items.append(contentsOf: [spacer, doneButton])
toolbar.setItems(items, animated: false)
targetTextField.inputAccessoryView = toolbar
}
Here are the associated action routines:
func toolbarPreviousPressed() {
if delegate != nil && indexPath != nil {
delegate?.cellPreviousPressed(indexPath: indexPath!)
}
}
func toolbarNextPressed() {
if delegate != nil && indexPath != nil {
delegate?.cellNextPressed(indexPath: indexPath!)
}
}
In my view controller where I have the tableview, my cellForRowAt function has this code:
let cell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell") as! TextFieldCell
let addressItem = (item as! XXXXXXAddressViewModelItem)
cell.textField.placeholder = addressItem.placeHolderText
cell.textField.text = addressItem.getValue(row: 0)
cell.indexPath = indexPath
cell.delegate = self
cell.setupKeyboardToolbar(targetTextField: cell.textField, dismissable: true, previousAction: false, nextAction: true)
return cell
Here is how I handle the delegate methods for the previous and next buttons being pressed:
func cellPreviousPressed(indexPath: IndexPath) {
// Resign the old cell
let oldCell = tableView.cellForRow(at: indexPath) as! BaseTableViewCell
oldCell.handleResignFirstResponder()
// Scroll to previous cell
let tempIndex = IndexPath(row: indexPath.row, section: indexPath.section - 1)
tableView.scrollToRow(at: tempIndex, at: .middle, animated: true)
// Become first responder
let cell = tableView.cellForRow(at: tempIndex) as! BaseTableViewCell
cell.handleBecomeFirstResponser()
}
func cellNextPressed(indexPath: IndexPath) {
// Resign the old cell
let oldCell = tableView.cellForRow(at: indexPath) as! BaseTableViewCell
oldCell.handleResignFirstResponder()
// Scroll to next cell
let tempIndex = IndexPath(row: indexPath.row, section: indexPath.section + 1)
self.tableView.scrollToRow(at: tempIndex, at: .middle, animated: true)
// Become first responder for new cell
let cell = self.tableView.cellForRow(at: tempIndex) as! BaseTableViewCell
cell.handleBecomeFirstResponser()
}
Finally, in my cell class derived from BaseTableViewCell, I override the handleBecomeFirstResponder and handleResignFirstResponder like so:
override func handleBecomeFirstResponder() {
textField.becomeFirstResponder()
}
override func handleResignFirstResponder() {
textField.resignFirstResponder()
}
On a related note, I handle the keyboard show and hide notifications by using insets on the tableview:
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, (keyboardFrame?.height)!, 0)
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, (keyboardFrame?.height)!, 0)
and:
self.tableView.contentInset = UIEdgeInsets.zero
self.tableView.scrollIndicatorInsets = UIEdgeInsets.zero
This took me a lot of trial-and-error to get this right and not seriously convolute my view controller with code that should be in the cell class.
I'm always looking for better ways to do this. Let me know your thoughts!
I'm building a custom interface for the user to enter preference settings in my app. I'm using expandable rows following an example I found at AppCoda. I've reworked that example to use Swift 3/4 and to use cell information from code rather than read from a plist.
I'm having a problem with the way some cell content appears on the screen. The rows that expand and collapse contain textfields to allow user entry. There are four such rows in the example code below.
When an entry is made in one of those cells, it may or may not cause the last-entered value to appear in all four cells when they are expanded. The 'extra' text will even overwrite the information that belongs there.
I've tried everything I can think of to get rid of this offending text but I'm banging my head against the wall. What am I missing?
FWIW, I am now looking at similar solutions elsewhere. Here's one I like quite a bit:
https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section
This one looks interesting but is not in Swift:
https://github.com/singhson/Expandable-Collapsable-TableView
Same comment:
https://github.com/OliverLetterer/SLExpandableTableView
This looks very interesting - well supported - but I haven't had time to investigate:
https://github.com/Augustyniak/RATreeView
A similar request here:
Expand cell when tapped in Swift
A similar problem described here, but I think I'm already doing what is suggested?
http://www.thomashanning.com/the-most-common-mistake-in-using-uitableview/
Here is my table view controller code. I believe the problem is in the...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
... function, but for the life of me I can't see it.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
test = defineCellProps() // This loads my hard-coded cell properties into array "test"
configureTableView()
}
func configureTableView() {
loadCellDescriptors()
tblExpandable.delegate = self
tblExpandable.dataSource = self
tblExpandable.tableFooterView = UIView(frame: CGRect.zero)
tblExpandable.register(UINib(nibName: "NormalCell", bundle: nil), forCellReuseIdentifier: "idCellNormal")
tblExpandable.register(UINib(nibName: "TextfieldCell", bundle: nil), forCellReuseIdentifier: "idCellTextfield") // There are additional cell types that are not shown and not related to the problem
}
func loadCellDescriptors() { // Puts the data from the "test" array into the format used in the original example
for section in 0..<ACsections.count {
var sectionProps = findDict(matchSection: ACsections[section], dictArray: test)
cellDescriptors.append(sectionProps)
}
cellDescriptors.remove(at: 0) // Removes the empty row
getIndicesOfVisibleRows()
tblExpandable.reloadData() // The table
}
func getIndicesOfVisibleRows() {
visibleRowsPerSection.removeAll()
for currentSectionCells in cellDescriptors { // cellDescriptors is an array of sections, each containing an array of cell dictionaries
var visibleRows = [Int]()
let rowCount = (currentSectionCells as AnyObject).count as! Int
for row in 0..<rowCount { // Each row is a table section, and array of cell dictionaries
var testDict = currentSectionCells[row]
if testDict["isVisible"] as! Bool == true {
visibleRows.append(row)
} // Close the IF
} // Close row loop
visibleRowsPerSection.append(visibleRows)
} // Close section loop
} // end the func
func getCellDescriptorForIndexPath(_ indexPath: IndexPath) -> [String: AnyObject] {
let indexOfVisibleRow = visibleRowsPerSection[indexPath.section][indexPath.row]
let cellDescriptor = (cellDescriptors[indexPath.section])[indexOfVisibleRow]
return cellDescriptor as [String : AnyObject]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let currentCellDescriptor = getCellDescriptorForIndexPath(indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: currentCellDescriptor["cellIdentifier"] as! String, for: indexPath) as! CustomCell
cell.textLabel?.text = nil
cell.detailTextLabel?.text = nil
cell.textField?.placeholder = nil
if currentCellDescriptor["cellIdentifier"] as! String == "idCellNormal" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
cell.textLabel?.text = primaryTitle as? String
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = secondaryTitle as? String
}
}
else if currentCellDescriptor["cellIdentifier"] as! String == "idCellTextfield" {
if let primaryTitle = currentCellDescriptor["primaryTitle"] {
if primaryTitle as! String == "" {
cell.textField.placeholder = currentCellDescriptor["secondaryTitle"] as? String
cell.textLabel?.text = nil
} else {
cell.textField.placeholder = nil
cell.textLabel?.text = primaryTitle as? String
}
}
if let secondaryTitle = currentCellDescriptor["secondaryTitle"] {
cell.detailTextLabel?.text = "some text"
}
cell.detailTextLabel?.text = "some text"
// This next line, when enabled, always puts the correct row number into each cell.
// cell.textLabel?.text = "cell number \(indexPath.row)."
}
cell.delegate = self
return cell
}
Here is the CustomCell code with almost no changes by me:
import UIKit
protocol CustomCellDelegate {
func textfieldTextWasChanged(_ newText: String, parentCell: CustomCell)
}
class CustomCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
let bigFont = UIFont(name: "Avenir-Book", size: 17.0)
let smallFont = UIFont(name: "Avenir-Light", size: 17.0)
let primaryColor = UIColor.black
let secondaryColor = UIColor.lightGray
var delegate: CustomCellDelegate!
override func awakeFromNib() {
super.awakeFromNib() // Initialization code
if textLabel != nil {
textLabel?.font = bigFont
textLabel?.textColor = primaryColor
}
if detailTextLabel != nil {
detailTextLabel?.font = smallFont
detailTextLabel?.textColor = secondaryColor
}
if textField != nil {
textField.font = bigFont
textField.delegate = self
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func prepareForReuse() { // I added this and it did not help
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if delegate != nil {
delegate.textfieldTextWasChanged(textField.text!, parentCell: self)
}
return true
}
}
OMG, I'm slapping my palm to my forehead. There is one very important line missing from this code from above:
override func prepareForReuse() {
super.prepareForReuse()
textLabel?.text = nil
detailTextLabel?.text = nil
textField?.placeholder = nil
}
Can you see what's missing?
textField?.text = nil
That's all it took! I was mucking about with the label but not the textfield text itself.
I have a collection view cell that passes data to a detailed view controller. When the cell is clicked, it segues into a view controller with more details. In the cells, I have a button, when the button is clicked, it also segues into a detailed view controller but a different view controller from when the cell is clicked.
This is what my didselect function looks like.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "details" {
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
if let indexPaths = self.CollectionView!.indexPathsForSelectedItems{
let vc = segue.destination as! BookDetailsViewController
let cell = sender as! UICollectionViewCell
let indexPath = self.CollectionView!.indexPath(for: cell)
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
let Booked = post["title"] as? String
let Authors = post["Author"] as? String
let ISBNS = post["ISBN"] as? String
let Prices = post["Price"] as? String
let imageNames = post["image"] as? String
let imagesTwo = post["imageTwo"] as? String
let imagesThree = post["imageThree"] as? String
let imagesFour = post["imageFour"] as? String
let imagesFive = post["imageFive"] as? String
vc.Booked = Booked
vc.Authors = Authors
vc.ISBNS = ISBNS
vc.Prices = Prices
vc.imageNames = imageNames
vc.imagesTwo = imagesTwo
vc.imagesThree = imagesThree
vc.imagesFour = imagesFour
vc.imagesFive = imagesFive
print(indexPath?.row)
} }
if segue.identifier == "UsersProfile" {
if let indexPaths = self.CollectionView!.indexPathsForSelectedItems{
let vc = segue.destination as! UsersProfileViewController
let cell = sender as! UICollectionViewCell
let indexPath = self.CollectionView!.indexPath(for: cell)
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
let username = post["username"] as? String
let userpicuid = post["uid"] as? String
vc.username = username
vc.userpicuid = userpicuid
print(indexPath?.row)
}}}
For if the segue == User's Profile I get an error in the let cell = line. My button in the cell was created in the cellForItemAt collection view function
let editButton = UIButton(frame: CGRect(x: 106, y: 171, width: 36, height: 36))
editButton.addTarget(self, action: #selector(editButtonTapped), for: UIControlEvents.touchUpInside)
editButton.tag = indexPath.row
print(indexPath.row)
editButton.isUserInteractionEnabled = true
cell.addSubview(editButton)
When I click the cell, it works perfectly and segues me into a detailed view controller but when I click the button within the cell, I get an error.
Here is my editTappedButton function
#IBAction func editButtonTapped() -> Void {
print("Hello Edit Button")
performSegue(withIdentifier: "UsersProfile", sender: self)
}
It is obvious that you are getting that crash because with your button action you are calling performSegue(withIdentifier: "UsersProfile", sender: self) now with sender you are passing self means reference of current controller not the UICollectionViewCell what you need is get the indexPath of that cell and pass that and now in prepareForSegue cast the sender to IndexPath instead of UICollectionViewCell.
First replace your editButtonTapped with below one
#IBAction func editButtonTapped(_ sender: UIButton) -> Void {
print("Hello Edit Button")
let point = sender.superview?.convert(sender.center, to: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: point!) {
performSegue(withIdentifier: "UsersProfile", sender: indexPath)
}
}
Now in prepareForSegue for identifier UsersProfile cast the sender to IndexPath or simply replace your condition with my one.
if segue.identifier == "UsersProfile" {
if let indexPath = sender as? IndexPath{
let vc = segue.destination as! UsersProfileViewController
let post = self.posts[indexPath.row] as! [String: AnyObject]
let username = post["username"] as? String
let userpicuid = post["uid"] as? String
vc.username = username
vc.userpicuid = userpicuid
print(indexPath.row)
}
}