How can I get the IndexPath of a textField in Swift - ios

I am using a sectioned tableView in my app, each row of the tableView contains a textField, when textFieldDidBeginEditing I need to know the indexPath of that textField. Using the tag can only get the section or row, and creating an extension of UITextField doesn't allow you to add variables. How can I get this done?

I like to walk from the text field to the cell and ask the table view for its index path.
extension UIResponder {
func next<T:UIResponder>(ofType: T.Type) -> T? {
let r = self.next
if let r = r as? T ?? r?.next(ofType: T.self) {
return r
} else {
return nil
}
}
}
And
func textFieldDidBeginEditing(_ textField: UITextField) {
if let cell = textField.next(ofType: MyCell.self) {
if let ip = self.tableView.indexPath(for:cell) {
// whatever
}
}
}

There are way to do it but it's bad design anyway, suggest you to put the textfield delegate inside the cell class.
You can try to get the exact cell/contentView with textField.superview, convert it to MyTableViewCell, then use tableView.indexPath(for: cell) to get index.
No need for tags to do it.
Example:
var view: UIView = textField
while !view.isKind(of: UITableViewCell.self), let superView = view.superview {
view = superView
}
if let view = view as? MyTableViewCell {
//Do sth
}

in cellForRow
var section = indexPath.section + 1
var row = indexPath.row + 1
index = Int("\(section)0\(row)")!
setting the textFields' tags to index
in textFieldDidBeginEditing
let indexString = "\(textField.tag)"
let parts = indexString.components(separatedBy: "0")
let row = Int(parts[1])! - 1
let section = Int(parts[0])! - 1

Easiest way to get indexPath of cell that contain textfield
func getIndexPathFromView(_ sender : UIView) -> IndexPath? {
let point = sender.convert(CGPoint.zero, to: self.tblView)
let indexPath = self.tblView.indexPathForRow(at: point)
return indexPath
}

Related

Create generic function to get indexpath from table and collection view - iOS - Swift

I have a tableView and collectionView and to get indexPath i'm using below methods on tableViewCell and collectionViewCell (i dont want to use indexPathForSelectedRow/Item methods). Is there a way that I can make this generic?
Ideas please
// For Tableview
func getIndexPath() -> IndexPath? {
guard let superView = self.superview as? UITableView else {
return nil
}
let indexPath = superView.indexPath(for: self)
return indexPath
}
// For CollectionView
func getIndexPath() -> IndexPath? {
guard let superView = self.superview as? UICollectionView else {
return nil
}
let indexPath = superView.indexPath(for: self)
return indexPath
}
You could do this with two protocols, one that both UITableView and UICollectionView conforms to, and the other that both UITableViewCell and UICollectionViewCell conforms to.
protocol IndexPathQueryable: UIView {
associatedtype CellType
func indexPath(for cell: CellType) -> IndexPath?
}
protocol IndexPathGettable: UIView {
associatedtype ParentViewType: IndexPathQueryable
}
extension UITableView : IndexPathQueryable { }
extension UICollectionView : IndexPathQueryable { }
extension UICollectionViewCell : IndexPathGettable {
typealias ParentViewType = UICollectionView
}
extension UITableViewCell : IndexPathGettable {
typealias ParentViewType = UITableView
}
extension IndexPathGettable where ParentViewType.CellType == Self {
func getIndexPath() -> IndexPath? {
guard let superView = self.superview as? ParentViewType else {
return nil
}
let indexPath = superView.indexPath(for: self)
return indexPath
}
}
Really though, you shouldn't need a getIndexPath method on a table view cell. Cells should not know their index paths. I suggest you reconsider your design.

DidSelect highlight changing view color is not working

I have one collection view, where in each cell i have one background view. So whenever user select any cell that particular cell view background color will change.
But now the problem is its background color is changing...but if i select another cell the previous selected cell view background color should be change to normal color.That is not happening.
the previous cell view background color also still as selected state
here is my vc didselectmethod :
let cell = chartCollectionView.cellForItem(at: indexPath) as? UserDataVC
var data = [String: Any]()
data["selectedCell"] = true
cell?.set(dataSource: data)
my collectionview cell :
class userCell: CollectionViewCell {
override func set(data: [String : AnyObject]) {
if let selectedCell = data["selectedCell"] as? Bool {
if selectedCell {
mainView.backgroundColor = UIColor.red
}
}
}
}
Any solution would be helpful
What's mainView?
You should generally change either the cell's contentView or assign a backgroundView and selectedBackgroundView to a cell.
Additionally and a bit unrelated, I would use a different method for changing content in response to events:
I would never change a cell directly by down casting it.
Instead, I would change the data I use to initialize the cells, and then reload the cells I want to change, and then re-create (actually, it would probably be re-used) the cells as needed using collectionView(_:cellForItemAt:).
This way, you never need to down cast.
Your current implementation is very poor.
I'd suggest saving the highlighted cell's index as a property, and accessing it to highlight or unhighlight when it dequeues. For example:
// Your CollectionView Delegate class
var currentHighlightedCellIndex: Int?
Then in your collectionView(_:cellForItemAt:):
// Dequeue the cell
...
if let selectedIndex = self.currentHighlightedCellIndex, selectedIndex == indexPath.row {
cell.selectedCell = true
} else {
cell.selectedCell = false
}
// Return the cell
...
In your collectionView(_:didSelectItemAt:):
guard let cell = collectionView.cellForItem(at: indexPath) as? YourCustomCellClass else { fatalError() }
cell.selectedCell = true
if let previouslySelectedIndex = self.currentHighlightedCellIndex {
// Here we get the index paths for each visible cell and check wether it's selected.
let indexPaths = collectionView.visibleCells.compactMap { collectionView.indexPath(for: $0) }
for index in (indexPaths.map { $0.item }) {
if index == previouslySelectedIndex {
// We found a cell that is highlighted and visible, get it in deselect it.
guard let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { fatalError() }
cell.selectedCell = false
}
}
}
self.currentHighlightedCellIndex = indexPath.item
You can set an observed property in your custom collection view cell class that will control the background color, like so:
// Custom cell class
var selectedCell: Bool = false {
didSet {
self.mainView.backgroundColor = selectedCell ? .red : .white
}
}

How to get the selected cell index path Swift [duplicate]

This question already has answers here:
How to get the indexpath.row when an element is activated?
(20 answers)
Closed 4 years ago.
I have a table view in my VC. Inside the cell there are some lables and buttons. I passed the values in my labels, now i'm trying that when i hit a button that is also in that cell it should increment the value of the label. The value in that label is coming from previous VC. I have made a delegate for it when button is pressed, when button is pressed it should increment the value of label by the first price which is present in it. i'M TRYING to get that cell index path but not geeting it. My code is this,
In my cel class i have this protocol,
protocol cartDelegate {
func addTappedInCell(_ cell: CartTableViewCell)
func minusTappedInCell(_ cell: CartTableViewCell)
}
var delegate : cartDelegate?
#IBAction func addBtnTapped(_ sender: Any) {
delegate?.addTappedInCell(self)
}
#IBAction func minusBtnTapped(_ sender: Any) {
delegate?.minusTappedInCell(self)
}
and in my view controller i'm trying this,
extension CartViewController : cartDelegate{
func addTappedInCell(_ cell: CartTableViewCell) {
guard let indexPath = cartTableView?.indexPath(for: cell) else { return }
print(indexPath)
total += 1
cell.totalLbl.text = String(total)
print(cell.priceLbl.text!)
count = "5"
let tPrice = cell.priceLbl.text! + count
print(tPrice)
cell.priceLbl.text = String(tPrice)
subTotalLbl.text = cell.priceLbl.text
}
func minusTappedInCell(_ cell: CartTableViewCell) {
total -= 1
cell.totalLbl.text = String(total)
price = Int(cell.priceLbl.text!)! - Int(count)!
cell.priceLbl.text = String(price)
subTotalLbl.text = cell.priceLbl.text
}
I'm not getting the indexPath of that cell which button is pressed.
This is how my screen looks,
Inside the custom cell class declare a var
var cellIndex:NSIndexPath?
and in cellForRow set it
cell.cellIndex = indexPath
and then access it anywhere
OR
directly use
var index = tableView.indexPath(for:cell)

getting value from uitextfield in uitableview

i have a lot of uitableviewcells with uitextfields inside. And i am catching the editing of the user with the delegate of the textField. That works fine as long as i don't use sections. Because i use the tag of the textField to save the indexPath.row of the cell. The problem is that i have to use sections now and with the sections i would have to save the indexPath.row and .section somehow.
Here is some Code.
func textFieldDidEndEditing(textField: UITextField) {
let indexPath = NSIndexPath(forRow: textField.tag, inSection: 0)
let cell : UITableViewCell? = self.tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell?
if data[(cell?.contentView.viewWithTag(textField.tag + 10000) as! UILabel).text!] != textField.text {
newData[fields[indexPath.row].ID] = textField.text
}
print((cell?.contentView.viewWithTag(textField.tag + 10000) as! UILabel).text)
print(textField.text)
}
Is there a better way to catch the edit of the textfields? Or how could i save both informations in the tag of the textfield?
greetings Adarkas.
Lets take a different approach.
Instead of using tag, you could convert your textfield location and get the index path of its cell superview.
func textFieldDidEndEditing(textField: UITextField) {
let origin: CGPoint = textField.frame.origin
let point: CGPoint = textField.convertPoint(origin, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(point)
let cell : UITableViewCell? = self.tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell?
if data[(cell?.contentView.viewWithTag(textField.tag + 10000) as! UILabel).text!] != textField.text {
newData[fields[indexPath.row].ID] = textField.text
}
print((cell?.contentView.viewWithTag(textField.tag + 10000) as! UILabel).text)
print(textField.text)
}
I don't remember where on SO I saw this to give proper credit, but here's what I'm using in my app. You get the indexPath based on the textField's superview.
let textField = sender as! UITextField
let view = textField.superview!.superview!
let currentCell = view.superview as! UITableViewCell // Or substitute your custom cell class name
let indexPath = tableView.indexPathForCell(currentCell)
I have resolved my problem like follows.
I have created a unique ID for my Datasource and i use that to determine with cell has been updated.
var row = 0
var section = 0
for var k = 0; k < fieldsWSections.count; k++ {
for var kk = 0; kk < fieldsWSections[k].count; kk++ {
if fieldsWSections[k][kk].ID == String(textField.tag) {
section = k
row = kk
}
}
}
if data[fieldsWSections[section][row].name] != textField.text {
newData[fieldsWSections[section][row].ID] = textField.text
}
So i don't need to know the indexPath anymore.
Using tags is a bad idea for reusable code, but the other answers rely upon a precise structure of the hierarchy.
This helper function only expects that the UITextField is a child of the UITableViewCell and that the UITableViewCell is a child of the their UITableView, which should always be true when the UITextFieldDelegate is called. If either is not true, this returns nil.
func indexPath(for view: UIView) -> IndexPath? {
// find the cell
var sv = view.superview
while !(sv is UITableViewCell) {
guard sv != nil else { return nil }
sv = sv?.superview
}
let cell = sv as! UITableViewCell
// find the owning tableView
while !(sv is UITableView) {
guard sv != nil else { return nil }
sv = sv?.superview
}
let tableView = sv as! UITableView
// locate and return the indexPath for the cell in this table
return tableView.indexPath(for: cell)
}

Swift: retrieving text from a UITextField in a custom UITableViewCell and putting it in an array

I'm making a very simple app where the user enters the number of people in the first Screen.
In the second screen it generates a number of UITableViewCell based on the number the user entered in the first screen. The UITableViewCell have a UITextField in them and I'm trying to store the data entered in those fields in an array once the user clicks to go to the third screen.
How can I do that? Thanks in advance!
Edit: I'm using the storyboard.
Here is what the code that calls for the custom UITableViewCell looks like for my UIViewController:
func tableView(tableView:UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var cell: EditingCell = tableView.dequeueReusableCellWithIdentifier("Cell") as EditingCell
if indexPath.row % 2 == 0{
cell.backgroundColor = UIColor.purpleColor()
} else {
cell.backgroundColor = UIColor.orangeColor()
}
let person = arrayOfPeople[indexPath.row]
cell.setCell(person.name)
return cell
}
Here is what the code for the UITableViewCell looks like:
class EditingCell: UITableViewCell{
#IBOutlet weak var nameInput: UITextField!
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
}
func setCell(name:String){
self.nameInput.placeholder = name
}
}
There is a problem with your approach if the number of rows in your table exceeds the number that can fit on screen. In that case, the cells that scroll off-screen will be re-used, and the contents of the nameInput textField will be lost. If you can be sure that this will never happen, use the following code (in the method that handles button taps) to compose your array:
var arrayOfNames : [String] = [String]()
for var i = 0; i<self.arrayOfPeople.count; i++ {
let indexPath = NSIndexPath(forRow:i, inSection:0)
let cell : EditingCell? = self.tableView.cellForRowAtIndexPath(indexPath) as EditingCell?
if let item = cell?.nameInput.text {
arrayOfNames.append(item)
}
}
println("\(arrayOfNames)")
Alternatively....
However, if it is possible that cells will scroll off-screen, I suggest a different solution. Set the delegate for the nameInput text fields, and then use the delegate methods to grab the names as they are entered.
First, add variables to your view controller, to hold the array and the row number of the text field currently being edited.
var arrayOfNames : [String] = [String]()
var rowBeingEdited : Int? = nil
Then, in your cellForRowAtIndexPath method, add:
cell.nameInput.text = "" // just in case cells are re-used, this clears the old value
cell.nameInput.tag = indexPath.row
cell.nameInput.delegate = self
Then add two new functions, to catch when the text fields begin/end editing:
func textFieldDidEndEditing(textField: UITextField) {
let row = textField.tag
if row >= arrayOfNames.count {
for var addRow = arrayOfNames.count; addRow <= row; addRow++ {
arrayOfNames.append("") // this adds blank rows in case the user skips rows
}
}
arrayOfNames[row] = textField.text
rowBeingEdited = nil
}
func textFieldDidBeginEditing(textField: UITextField) {
rowBeingEdited = textField.tag
}
When the user taps the button, they might still be editing one of the names. To cater for this, add the following to the method that handles the button taps:
if let row = rowBeingEdited {
let indexPath = NSIndexPath(forRow:row, inSection:0)
let cell : EditingTableViewCell? = self.tableView.cellForRowAtIndexPath(indexPath) as EditingTableViewCell?
cell?.nameTextField.resignFirstResponder()
}
This forces the textField to complete editing, and hence trigger the didEndEditing method, thereby saving the text to the array.
Here for new swift versions of answer
var arrayOfNames : [String] = [String]()
var i = 0
while i < taskArrForRead.count {
let indexPath = IndexPath(row: i, section: 0)
let cell : taslakDuzenlemeCell? = self.tableView.cellForRow(at: indexPath) as! taslakDuzenlemeCell?
if let item = cell?.taslakTextField.text {
arrayOfNames.append(item)
}
i = i + 1
}
print("\(arrayOfNames)")

Resources