I have a dynamic Table as UITableView and all cells are as normal (retrieved form my array)
However I need only one cell as TextView to be able input text. On text Change I need to retrieve the text user input.
How to make this?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count+1 //to allow this input field
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row < array.cont){
//normal cell from array
let cell = Table.dequeueReusableCell(withIdentifier: "myCell")
cell?.textLabel?.text = array[indexPath.row]
cell?.isUserInteractionEnabled = true;
cell?.textLabel?.adjustsFontSizeToFitWidth = true
cell?.textLabel?.textAlignment = .center;
return cell!;
}else{
//create input text field (DON'T KNOW HOW)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(indexPath.row < array.cont){
//.. perform action ..
}else{
//retrieve input text (DONT know How)
}
}
Creating UITextView inside UITableViewCell is quite simple :
let textView: UITextView = UITextView(frame: CGRect(x: 0, y: 20, width: 311.00, height: 50.00)) // Set frames as per requirements
textView.textAlignment = NSTextAlignment.justified
cell.contentView.addSubView(textView)
But this would lead to incorrect values while scrolling the table. Best approach would be to create a custom cell and add UITextView there. Here is the custom cell. Keep the constraints intact.
Before using the custom cell, you need to register it in your table. So :
let nib = UINib(nibName: "TextCell", bundle: nil)
Table.register(nib, forCellReuseIdentifier: "TextCell")
Don't forget to put identifier of cell in xib.
Now in cellForRowAtIndexPath :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row < array.cont){
//normal cell from array
let cell = Table.dequeueReusableCell(withIdentifier: "myCell")
cell?.textLabel?.text = array[indexPath.row]
cell?.isUserInteractionEnabled = true;
cell?.textLabel?.adjustsFontSizeToFitWidth = true
cell?.textLabel?.textAlignment = .center;
return cell!;
}else{
//create input text field (DON'T KNOW HOW)
let cell = tableView.dequeueReusableCell(withIdentifier: "TextCell", for: indexPath) as! TextCell
// Access UITextView by cell.textView
return cell
}
}
The main issue is - dynamic cell size as per UITextView height. But that entirely depends on your requirement. You can follow this post for that.
You can achieve this with delegation pattern or NSNotification.
Here's the solution for this using delegation pattern.
Create new UITableViewCell using xib and add the textView on contentView of cell, set the reuse identifier and than register the xib in the ViewController with
tableView.register(UINib(nibName: "name of the xib file", bundle: nil), forCellReuseIdentifier: "Identifier here")
Now define protocol anywhere outside of the class
// You can give any name
// Here we are confirming to class to avoid any retain cycles
protocol CustomCellDelegate :class {
func returnText(text :String)
}
Now initialise " var delegate : CustomCellDelegate? " in same class of UITableViewCell that we created above while creating xib.
and confirm to protocol UITextViewDelegate and than in the cell class write this
override func awakeFromNib() {
super.awakeFromNib()
textView.delegate = self
}
after that add these functions in same class of tableViewCell
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if(text == "\n") {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
delegate.returnText(text : textView.text ?? "")
}
Now in the ViewController class
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == array.count { // this will call the cell with TextView at the end , you can play with any indexPath to call the cell
let cell = tableView.de... // Deque the cell with TextView here using reuse identifier
cell.delegate = self
return cell
}
else {
// deque other cells
}
}
we'll write an extension of ViewController and confirm to our custom protocol
extension ViewController : CustomCellDelegate {
// this function will get called when you end editing on textView
func returnText(text :String) {
print(text) // you may save this string in any variable in ViewController class
}
}
Add a TextView in to your custom cell, hide it, show when you need
if you want to have the textView on the top of all cells, drag and drop a UIView inside the tableView before the cell.
this view will scroll with cells.
design this view as you need insert a textView inside it, and use textView's delegate methods to perform operations.
Related
I have made a custom cell in a table view. There are some buttons and labels in a cell. I'm making a delegate method and call it on the action of the button. The button is also in a cell. Now i'm trying that whenever user press button the label text should increment by one. I'm trying to access the cell label outside the cellForRow delegate method but fail. How can i get the label in a cell outside the cellForRow delegate method in my button action? I have tried some code,
this is in my cell class,
protocol cartDelegate {
func addTapped()
func minusTapped()
}
var delegate : cartDelegate?
#IBAction func addBtnTapped(_ sender: Any) {
delegate?.addTapped()
}
#IBAction func minusBtnTapped(_ sender: Any) {
delegate?.minusTapped()
}
This is in my view controller class,
extension CartViewController : cartDelegate{
func addTapped() {
total += 1
print(total)
}
func minusTapped() {
total -= 1
print(total)
}
}
this is cellForRow method,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CartTableViewCell
cell.dishTitleLbl.text = nameArray[indexPath.row]
cell.priceLbl.text = priceArray[indexPath.row]
price = Int(cell.priceLbl.text!)!
print(price)
cell.dishDetailLbl.text = "MANGO,Apple,Orange"
print(cell.dishDetailLbl.text)
total = Int(cell.totalLbl.text!)!
cell.selectionStyle = .none
cell.backgroundColor = UIColor.clear
cell.delegate = self
return cell
}
I want to access priceLbl in my addTapped and minusTapped functions.
Change your protocol to pass the cell:
protocol cartDelegate {
func addTappedInCell(_ cell: CartTableViewCell)
func minusTappedInCell(_ cell: CartTableViewCell)
}
Change your IBActions to pass the cell:
#IBAction func addBtnTapped(_ sender: Any) {
delegate?.addTappedInCell(self)
}
#IBAction func minusBtnTapped(_ sender: Any) {
delegate?.minusTappedInCell(self)
}
And then your delegate can do whatever it wants to the cell.
To be able to access the label inside of CartViewController but outside of cellForRowAt you have to be able to access a particular cell. To achieve that, since you are dynamically dequeueing reusable cells, you will need an indexPath of that cell and then you can ask the tableView to give you the cell:
// I will here assume it is a third cell in first section of the tableView
let indexPath = IndexPath(row: 2, section: 0)
// ask the tableView to give me that cell
let cell = tableView.cellForRow(at: indexPath) as! CartTableViewCell
// and finally access the `priceLbl`
cell.priceLbl.text = priceArray[indexPath.row]
It should be something as simple as:
self.priceLbl.text = "count = \(total)"
I have spent days on resolving this issue and after trying much I am asking a question here. I am using a custom UITableViewCell and that cell contains UITextFields. On adding new cells to the table view, the table view behaves abnormal like it duplicates the cell and when I try to edit the textfield of new cell, the textfield of previous cel gets edited too.
The behavior of duplication is as follows: 1st cell is duplicated for 3rd cell. I don't know this is due to reusability of cells but could anyone tell me about the efficient solution?
I am attaching the screenshot of UITableViewCell.
The code for cellForRow is as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.dropDownViewProducts.index = indexPath.row
cell.txtDescription.index = indexPath.row
cell.tfPrice.index = indexPath.row
cell.dropDownQty.index = indexPath.row
cell.tfTotalPrice_Euro.index = indexPath.row
cell.tfTotalPrice_IDR.index = indexPath.row
cell.dropDownViewTotalDiscount.index = indexPath.row
cell.dropDownViewDeposit.index = indexPath.row
cell.tfTotalDeposit_Euro.index = indexPath.row
cell.tfRemaingAfterDeposit_IDR.index = indexPath.row
return cell
}
The issue is the cell is being reused by the UITableView, which is what you want to happen for good scrolling performance.
You should update the data source that supports each row in the table to hold the text the user inputs in the field.
Then have the text field's text property assigned from your data source in cellForRowAt.
In other words, the UITableViewCell is the same instance each time you see it on the screen, and so is the UITextField and therefore so is it's text property. Which means it needs to be assigned it's correct text value each time cellForRowAt is called.
I'm unsure of your code so I have provided an example of how I would do something like what you want:
class MyCell: UITableViewCell {
#IBOutlet weak var inputField: UITextField!
}
class ViewController: UIViewController {
#IBOutlet weak var table: UITableView!
var items = [String]()
fileprivate func setupItems() {
items = ["Duck",
"Cow",
"Deer",
"Potato"
]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setupItems()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// the # of rows will equal the # of items
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// we use the cell's indexPath.row to
// to get the item in the array's text
// and use it as the cell's input field text
guard let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as? MyCell else {
return UITableViewCell()
}
// now even if the cell is the same instance
// it's field's text is assigned each time
cell.inputField.text = items[indexPath.row]
// Use the tag on UITextField
// to track the indexPath.row that
// it's current being presented for
cell.inputField.tag = indexPath.row
// become the field's delegate
cell.inputField.delegate = self
return cell
}
}
extension ViewController: UITextFieldDelegate {
// or whatever method(s) matches the app's
// input style for this view
func textFieldDidEndEditing(_ textField: UITextField) {
guard let text = textField.text else {
return // nothing to update
}
// use the field's tag
// to update the correct element
items[textField.tag] = text
}
}
I suggest to do the following
class Product_PriceTableViewCell: UITableViewCell {
var indexRow: Int = -1
func configureCell(index: Int) {
cell.dropDownViewProducts.clean()
...
cell.tfRemaingAfterDeposit_IDR.clean()
}
}
where clean is the function to empty de view (depend on the type)
Then in the delegate:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : Product_PriceTableViewCell = tableView.dequeueReusableCell(withIdentifier: "product_priceCell") as! Product_PriceTableViewCell
cell.configureCell(row: indexPath.row)
return cell
}
As #thefredelement pointed out when the cell is not in the view frame, it is not created. Only when the view is going to appear, it tries to reuse an instance of the cell and as the first is available, the table view uses it but does not reinitialize it. So you have to make sure to clean the data
The rest of the answer is for better coding.
I'm trying to set a different style for my active object in a TableView. I tried setting a flag for my object (myObject.isActive) and read it in my custom UITableViewCell like this;
var myArray = [MyObject]()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "myCustomCell", for: indexPath) as? myCustomCell {
if myArray.count > 0 {
// Return the cell
let myObject = myArray[indexPath.row]
cell.updateUI(myObject: myObject)
return cell
}
}
return UITableViewCell()
}
myCustomCell:
func updateUI(myObject: MyObject){
if myObject.isActive {
self.selectedCell()
}
}
func selectedCell() {
labelTitle.font = UIFont(name: "Montserrat-Medium", size: 32)
labelTitle.textColor = UIColor(hex: 0x64BA00)
}
This works great when the tableView data loads. But when I scroll the tableView other cells are also styling differently. How can I solve this?
Cells get reused. You need to handle all possibilities. Your updateUI method changes the cell if myObject is active but you make no attempt to reset the cell if it isn't.
You need something like:
func updateUI(myObject: MyObject){
if myObject.isActive {
selectedCell()
} else {
resetCell()
}
}
And add:
func resetCell() {
// Set the cell's UI as needed
}
Another options is to override the prepareForReuse method of the table cell class. That method should reset the cell to its initial state.
I have a table view and I am adding several cells to it based on my json.
So far the code for adding cells looks as follows:
override func viewWillAppear(animated: Bool) {
let frame:CGRect = CGRect(x: 0, y: 90, width: self.view.frame.width, height: self.view.frame.height-90)
self.tableView = UITableView(frame: frame)
self.tableView?.dataSource = self
self.tableView?.delegate = self
self.view.addSubview(self.tableView!)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CELL")
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "CELL")
}
let user:JSON = JSON(self.items[indexPath.row])
cell!.textLabel?.text = user["description"].string
var photoURL = "/path/to/my/icon/google.png"
if let data = NSData(contentsOfFile: photoURL)
{
cell!.imageView?.image = UIImage(data: data)
}
return cell!
}
Besides the description in my json I have also username and price. So far - since I'm adding only imageView and description, 3 cells look like this:
Is there a way to style it so that each cell looks similar to this:
(price and username are grey here`)? How can I achieve this effect?
===EDIT:
this is how I populate my table:
I'm fetching data from rest webservice to json:
func getAllUsers() {
RestApiManager.sharedInstance.getUsers { json in
let results = json
for (index: String, subJson: JSON) in results {
let user: AnyObject = JSON.object
self.items.addObject(user)
dispatch_async(dispatch_get_main_queue(),{
self.tableView?.reloadData()
})
}
}
}
and I invoke this method in my viewWillAppear function
You can make your table use custom UITableViewCells and style them to your liking.
In a nutshell, you create a prototype cell in Storyboard that looks like the example you posted and connect it to a custom UITableViewCell class with the elements you created. At cellForRowInIndexPath you return your custom cell rather than regular UITableViewCells.
Check out this tutorial for details: http://shrikar.com/uitableview-and-uitableviewcell-customization-in-swift/
Create the layout of the cell using a custom style. Place labels and imageView like you would anywhere else in storyborad.
You will need to create a UITableViewCell file. The one I used is named ExampleTableViewCell. Make note of the subclass.
Now connect your cell to the ExampleTableViewCell you just created.
Now we can make outlets from the labels and imageView of the cell into the ExampleTableViewCell. Control drag from each element into the ExampleTableViewCell.
The final step is to configure the cell using the cellForRowAtIndexPath func. Make note of the var cell. We now cast this to the ExampleTableViewCell. Once we do this we can use the outlets in the ExampleTableViewCell to set our labels and image. Make sure you set the resuseIdentifier for the cell in the storyboard. If you are unfamiliar with this leave a comment and I can add instructions for this.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier") as! ExampleTableViewCell
cell.imageDisplay.image = yourImage
cell.descriptionLabel.text = yourDescription
cell.priceLabel.text = yourPrice
cell.usernameLabel.text = yourUsername
return cell
}
Subclass UITableViewCell. You can go to the TableView on your storyboard and go to one of the prototypes and set it's class to your custom class and it's style to Custom and then you can ctrl+click & drag outlets/actions to the UITableViewCell subclass the same way you would for a basic view controller.
So, I'm building a Detail View Controller App that presents a Table with a two-part cell: the label and the Text Field.
I'm trying to retrieve the Text Field value and add it to an array.
I tried to use the "textField.superview.superview" technique but it didn't worked.
func textFieldDidEndEditing(textField: UITextField!){
var cell: UITableViewCell = textField.superview.superview
var table: UITableView = cell.superview.superview
let textFieldIndexPath = table.indexPathForCell(cell)
}
Xcode fails to build and presents that "UIView is not convertible to UITableViewCell" and "to UITableView".
The referring table has two sections, of four and two rows, respectively.
Thanks in advance.
EDIT:
added ".superview" at the second line of the function.
While the currently accepted answer might work, it assumes a specific view hierarchy, which is not a reliable approach since it is prone to change.
To get the indexPath from a UITextField that is inside a cell, it's much better to go with the following:
func textFieldDidEndEditing(textField: UITextField!){
let pointInTable = textField.convert(textField.bounds.origin, to: self.tableView)
let textFieldIndexPath = self.tableView.indexPathForRow(at: pointInTable)
...
}
This will continue to work independent of eventual changes to the view hierarchy.
You'll want to cast the first and second lines in your function, like this:
func textFieldDidEndEditing(textField: UITextField!){
var cell: UITableViewCell = textField.superview.superview as UITableViewCell
var table: UITableView = cell.superview as UITableView
let textFieldIndexPath = table.indexPathForCell(cell)
}
superview returns a UIView, so you need to cast it to the type of view you expect.
Using superview and typecasting isn't a preferred aaproach. The best practice is to use delegate pattern. If you have a textField in DemoTableViewCell which you are using in DemoTableViewController make a protocol DemoTableViewCellDelegate and assign delegate of DemoTableViewCell to DemoTableViewController so that viewcontroller is notified when eiditing ends in textfield.
protocol DemoTableViewCellDelegate: class {
func didEndEditing(onCell cell: DemoTableViewCell)
}
class DemoTableViewCell: UITableViewCell {
#IBOutlet var textField: UITextField!
weak var delegate: DemoTableViewCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
}
extension DemoTableViewCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
delegate.didEndEditing(onCell: self)
}
}
class DemoTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: DemoTableViewCell.self, for: indexPath)
cell.delegate = self
return cell
}
}
extension DemoTableViewController: DemoTableViewCellDelegate {
func didEndEditing(onCell cell: DemoTableViewCell) {
//Indexpath for the cell in which editing have ended.
//Now do whatever you want to do with the text and indexpath.
let indexPath = tableView.indexPath(for: cell)
let text = cell.textField.text
}
}
You can use tag property of UITableViewCell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UpdateTableViewCell", for: indexPath) as! UpdateTableViewCell
cell.tag = indexPath.row
cell.setCellData()
return cell
}
now in UITableViewCell
func textFieldDidEndEditing(textField: UITextField!){
let textFieldIndexPath = self.tag
}