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)
}
Related
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
}
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
}
}
I need to get the first cell in my tableView to be a different size from the rest. The rest of my cells are all under the class CustomPFTableViewCell, but the first one is a different cell so its under the class FirstPFTableViewCell, both of which extend from the class PFTableViewCell. Right now, I just used an if depending on the indexPath.row for whether or not the cell was the first cell. When its not it will load data for the cell from Parse.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {
if(indexPath.row >= 1){
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomPFTableViewCell!
print("Loading Parse Database Files...")
// Extract values from the PFObject to display in the table cell
if let name = object?["Name"] as? String {
cell?.nameTextLabel?.text = name
print("Loading " + name)
}
if let author = object?["authorName"] as? String {
cell?.authorTextLabel?.text = author
}
if let likes = object?["Likes"] as? Int {
let stringVal = String(likes)
cell?.numLikes.text = stringVal
}
if let descrip = object?["Description"] as? String {
cell?.descriptionHolder = descrip
}
let initialThumbnail = UIImage(named: "Unloaded")
cell.customFlag.image = initialThumbnail
if let thumbnail = object?["imageCover"] as? PFFile {
cell.customFlag.file = thumbnail
cell.customFlag.loadInBackground()
}
return cell
}
print("Finished loading!")
let cell = tableView.dequeueReusableCellWithIdentifier("firstCell") as! PFTableViewCell
return cell
}
The end is empty because I'm not sure how to go about changing the one/first cell's size. (In the Interface Builder its set to 60). I guess the most important part in solving this is this:
let cell = tableView.dequeueReusableCellWithIdentifier("firstCell") as! PFTableViewCell
return cell
}
In order to play with the size of the cell you have to implement the UITableViewDelegate function
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 0 {
return firstCellHeight
} else {
return customCellHeight
}
Setup (Swift 1.2 / iOS 8.4):
I have UITableView custom cell (identifier = Cell) inside UIViewController. Have two buttons (increment/decrement count) and a label (display count) inside the custom TableView cell.
Goal:
Update the label as we press the increase count or decrease count button.
At present I am able to get the button Tag and call a function outside of the CellForRowAtIndexPath. The button press increases and decreases the count. But I am not able to display the count update in the label.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:FoodTypeTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! FoodTypeTableViewCell
cell.addBtn.tag = indexPath.row // Button 1
cell.addBtn.addTarget(self, action: "addBtn:", forControlEvents: .TouchUpInside)
cell.subBtn.tag = indexPath.row // Button 2
cell.subBtn.addTarget(self, action: "subBtn:", forControlEvents: .TouchUpInside)
cell.countLabel.text = // How can I update this label
return cell
}
func addBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
count = 1 + count
println(count)
return count
}
func subBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
if count == 0 {
println("Count zero")
} else {
count = count - 1
}
println(count)
return count
}
I have seen this question here and there but was not able to find a clear answer in Swift. I would really appreciate if you could help answer it clearly so that other people can not just copy, but clearly understand what is going on.
Thank you.
Here is a solution that doesn't require tags. I'm not going to recreate the cell exactly as you want, but this covers the part you are asking about.
Using Swift 2 as I don't have Xcode 6.x anymore.
Let's start with the UITableViewCell subclass. This is just a dumb container for a label that has two buttons on it. The cell doesn't actually perform any specific button actions, it just passes on the call to closures that are provided in the configuration method. This is part of MVC. The view doesn't interact with the model, just the controller. And the controller provides the closures.
import UIKit
typealias ButtonHandler = (Cell) -> Void
class Cell: UITableViewCell {
#IBOutlet private var label: UILabel!
#IBOutlet private var addButton: UIButton!
#IBOutlet private var subtractButton: UIButton!
var incrementHandler: ButtonHandler?
var decrementHandler: ButtonHandler?
func configureWithValue(value: UInt, incrementHandler: ButtonHandler?, decrementHandler: ButtonHandler?) {
label.text = String(value)
self.incrementHandler = incrementHandler
self.decrementHandler = decrementHandler
}
#IBAction func increment(sender: UIButton) {
incrementHandler?(self)
}
#IBAction func decrement(sender: UIButton) {
decrementHandler?(self)
}
}
Now the controller is just as simple
import UIKit
class ViewController: UITableViewController {
var data: [UInt] = Array(count: 20, repeatedValue: 0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! Cell
cell.configureWithValue(data[indexPath.row], incrementHandler: incrementHandler(), decrementHandler: decrementHandler())
return cell
}
private func incrementHandler() -> ButtonHandler {
return { [unowned self] cell in
guard let row = self.tableView.indexPathForCell(cell)?.row else { return }
self.data[row] = self.data[row] + UInt(1)
self.reloadCellAtRow(row)
}
}
private func decrementHandler() -> ButtonHandler {
return { [unowned self] cell in
guard
let row = self.tableView.indexPathForCell(cell)?.row
where self.data[row] > 0
else { return }
self.data[row] = self.data[row] - UInt(1)
self.reloadCellAtRow(row)
}
}
private func reloadCellAtRow(row: Int) {
let indexPath = NSIndexPath(forRow: row, inSection: 0)
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
tableView.endUpdates()
}
}
When the cell is dequeued, it configures the cell with the value to show in the label and provides the closures that handle the button actions. These controllers are what interact with the model to increment and decrement the values that are being displayed. After changing the model, it reloads the changed cell in the tableview.
The closure methods take a single parameter, a reference to the cell, and from this it can find the row of the cell. This is a lot more de-coupled than using tags, which are a very brittle solution to knowing the index of a cell in a tableview.
You can download a full working example (Requires Xcode7) from https://bitbucket.org/abizern/so-32931731/get/ce31699d92a5.zip
I have never seen anything like this before so I am not sure if this will be the correct way to do. But I got the intended functionality using the bellow code:
For people who find it difficult to understand:
The only problem we have in this is to refer to the TableView Cell. Once you figure out a way to refer the cell, you can interact with the cell components.
func addBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0) // This defines what indexPath is which is used later to define a cell
let cell = tableView.cellForRowAtIndexPath(indexPath) as! FoodTypeTableViewCell! // This is where the magic happens - reference to the cell
count = 1 + count
println(count)
cell.countLabel.text = "\(count)" // Once you have the reference to the cell, just use the traditional way of setting up the objects inside the cell.
return count
}
func subBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as! FoodTypeTableViewCell!
if count == 0 {
println("Count zero")
} else {
count = count - 1
}
cell.countLabel.text = "\(count)"
println(count)
return count
}
I hope someone will benefit from this.
PLEASE CORRECT ME IF THERE IS SOME PROBLEM IN THIS SOLUTION OR THERE IS A BETTER/PROPER WAY TO DO THIS.
Use tableView.reloadData() to reload your tableView content each time you click a button.
let text = "something"
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:FoodTypeTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! FoodTypeTableViewCell
cell.addBtn.tag = indexPath.row // Button 1
cell.addBtn.addTarget(self, action: "addBtn:", forControlEvents: .TouchUpInside)
cell.subBtn.tag = indexPath.row // Button 2
cell.subBtn.addTarget(self, action: "subBtn:", forControlEvents: .TouchUpInside)
cell.countLabel.text = something
return cell
}
func addBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
count = 1 + count
println(count)
something = "\(count)"
self.tableView.reloadData()
return count
}
func subBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
if count == 0 {
println("Count zero")
} else {
count = count - 1
}
println(count)
something = "\(count)"
self.tableView.reloadData()
return count
}
Update1
After your comments ...
you have an array (one value for each food) like this, and whenever you click on a button, you take the index of the row the contains that button, then use that index to retrive the value of count from your array, then reload the table view content.
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)")