I have got a view controller which contains a tableview, this tableview contains dynamic cells done in storyboard. Most cells contain a textfield and in the view controller i need to detect when the textfields have been selected and which cell it was. (I load a popover pointing to the cell, and this must be called from the view controller).
Cell code
import UIKit
class AddNew_Date_Cell: UITableViewCell {
#IBOutlet var Label: UILabel!
#IBOutlet var textField: UITextField!
func loadItem(var data:NSArray) {
Label.text = data[0] as? String
}
}
ViewControllerCode
import UIKit
class AddNewDetailView: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPopoverPresentationControllerDelegate, UITextFieldDelegate {
#IBOutlet var tableView: UITableView!
var items : NSMutableArray!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch(items[indexPath.row][1] as! Int)
{
...
case 2:
var cell:AddNew_Date_Cell = tableView.dequeueReusableCellWithIdentifier("AN_Date_Cell") as! AddNew_Date_Cell
cell.loadItem(items[indexPath.row] as! NSArray, view: self)
cell.textField.delegate = self
return cell
...
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
//Need to know here which cell the textfield is attached to
turn true
}
}
Each time i select a textfield view I'm hitting a breakpoint in "textFieldShouldBeginEditing" however i have no idea which cell its in.
if i select the textfield in a cell "didSelectRowAtIndexPath" is never hit.
How do i find out which cell has been selected.
Thanks
You need to set the interactions from user off while the text field is not been edit, in cellForRowAtIndexPath add this line
cell.textField.userInteractionEnabled = false
In the didSelectRowAtIndexPath you will need to enable again for edition otherwise the user won't be to touche it if they want to and call first responder:
cell.textField.userInteractionEnabled = true
cell.textField.becomeFirstResponder()
once the user finish editing in textFieldShouldEndEditing you disable the interaction again:
cell.textField.userInteractionEnabled = false
This way the cellForRowAtIndexPath will always be called but will be up to you to set the user to use the correct UITextField.
I hope that helps you
You could use a custom cell class with a delegate protocol letting you know when a text field has started editing.
The custom cell would be the delegate of the text field and the view controller would be the delegate of the custom cell.
For a basic example, your cell class could look like the following:
import UIKit
protocol TextFieldCellDelegate {
func textFieldCellDidBeginEditing(cell: TextFieldCell)
}
class TextFieldCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
var delegate: TextFieldCellDelegate?
}
extension TextFieldCell: UITextFieldDelegate {
func textFieldDidBeginEditing(textField: UITextField) {
delegate?.textFieldCellDidBeginEditing(self)
}
}
And your view controller would implement the method like this:
extension ViewController: TextFieldCellDelegate {
func textFieldCellDidBeginEditing(cell: TextFieldCell) {
println(cell)
// Do something with cell
}
}
If you want to find out the index of your cell write the below code in func textFieldShouldBeginEditing(textField: UITextField)
var index = 0
var position: CGPoint = textField.convertPoint(CGPointZero, toView: self.<your_table_outlet_name>)
if let indexPath = self.<your_table_outlet_name>.indexPathForRowAtPoint(position)
{
index = indexPath.row
}
Related
I'm practicing creating an app where I have a label that gets its text from an UITextField when the user presses a button. Now, I added another button and a tableview and I want to be able to "save" the label's text to the table cells with the same mechanism of stopwatch's laps.
So, to be clear, I want the button to transfer the label's text to the table view cells each time I press it.
After your save button, you need to store the texts somewhere and reload the table. (Or insert it with animation)
class ViewController: UIViewController {
#IBOutlet private var textField: UITextField!
#IBOutlet private var tableView: UITableView!
var texts: [String] = [] {
didSet { tableView.reloadData() }
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SimpleCell")
tableView.dataSource = self
}
#IBAction func saveButtonTapped(_ sender: UIButton) {
guard let newText = textField.text else { return }
self.texts.append(newText)
}
}
And in tableView dataSource methods:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return texts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SimpleCell", for: indexPath)!
cell.textLabel?.text = texts[indexPath.row]
return cell
}
}
I would just like to preface this with the fact that I don't use storyboards.
I have a tableview with multiple sections that are filled with tableViewcells I have created programmatically. These custom cells include a textfield with some placeholder text. What I want the user to be able to do is to tap on the textfield, type their entry, and then hit "Enter" to dismiss the keyboard and then create a new cell underneath the cell they just edited. This is very similar to the behaviour that happens in the reminders app.
I'm having a hard time trying to figure out how to access the tableview's data model (an array) and figuring out which section that cell is in, adding the new string to the array, and then adding another dumby cell that has the placeholder text.
First at all you have to create a way to communicate between your cell and view controller.
You can use delegate pattern or callbacks for this.
For example:
final class TextFieldCell: UITableViewCell {
// MARK: - IBOutlets
#IBOutlet weak var textField: UITextField!
// MARK: - Local variables
var callback: ((_ text: String) -> Void)?
// MARK: - Lyfecycle
override func awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
}
Also don't forget to call our callback:
extension TextFieldCell: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
callback?(textField.text!)
return true
}
}
Great! Now we send our string from cell to controller!
Example of code for your view controller(simplified version):
class ViewController: UIViewController {
// MARK: - IBOutlets
#IBOutlet weak var tableView: UITableView!
// MARK: - Local variables
var titles = ["Hello", "world"]
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titles.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let textFieldCell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as! TextFieldCell
textFieldCell.textField.placeholder = titles[indexPath.row]
textFieldCell.callback = { [weak self] newTitle in // don't forget about memory leaks
guard let `self` = self else { return }
// calculating index path for new row
let newIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
// appending a new row in table view
self.titles.append(newTitle)
self.tableView.insertRows(at: [newIndexPath], with: UITableView.RowAnimation.automatic)
}
return textFieldCell
}
}
I have a UICollectionView inside a UITableView, I want to link another ViewController when the UICollectionViewCell is tapped by user. Inside the UITableViewCell, I downloaded all the data from the Internet and stored in an array. I created a Segue by control drag the CollectionViewCell to the new ViewController and chose "Push". Inside the new ViewController, I added an IBOutlet of UIImageView to show the image of the CollectionViewCell when user tapped the thumbnail. Now when I tapped the CollectionViewCell, it goes to the new ViewController, but there is no pics showed. here is my code, any suggestions?
In the UITableViewConroller:
import UIKit
class HomePageTableViewController: UITableViewController {
let sections: NSArray = ["latest news", "good news"]
var posts1: [Posts] = [Posts]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section < sections.count{
return sections[section] as? String
}
return nil
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 && indexPath.section == 0 {
let tableViewCell = tableView.dequeueReusableCellWithIdentifier("TableViewCell") as! TableViewCell
tableViewCell.getCategories()
// tableViewCell.scrollToNextCell()
// tableViewCell.startTimer()
return tableViewCell
}
and inside TableViewCell, here is the code:
import UIKit
import Alamofire
import AlamofireImage
class TableViewCell: UITableViewCell, UICollectionViewDataSource,
UICollectionViewDelegate {
var posts1: [Posts] = [Posts]()
var posts2: [Posts] = [Posts]()
var categories: [Category] = [Category]()
var selectedPost1: Posts?
#IBOutlet weak var theCollectionView: UICollectionView!
I download all the data from JSON and put them inside these arrays. inside this class, I also tried to call the function :
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print("selected")
self.selectedPost1 = self.posts1
}
the print works when I tapped a cell, but I can't use "prepareForSegue" inside this function. I checked and found out that this function can only be used in ViewController, but how can I do that?
And inside the new ViewController, here is the code:
import UIKit
class DetailViewController: UIViewController {
#IBOutlet weak var postImageView: UIImageView!
var posts: [Posts] = [Posts]()
var selectedPost: Posts?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
if let post = self.selectedPost{
let thumbnailUrlString = post.postThumbnailUrlString
let imageUrl: NSURL = NSURL(string: thumbnailUrlString)!
print(thumbnailUrlString)
postImageView.af_setImageWithURL(imageUrl)
}
}
}
Since when I download the data, I put them into 2 different array, posts1 and posts2. I want to show the corresponding images in the new ViewController. But I just created one "selectedPost" array, I am not sure is that the problem? and I am not sure if I reload the data to the "selectedPost" array, maybe thats why there is no images showing? really hope anyone can give me some suggestions. thanks
You should use prepareForSegue:. What you need to do is have a var selectedIndexPath which is set whenever you select a cell. Then in prepareForSegue: you can access the cell and it's properties i.e:
HomePageTableViewController: UITableViewController {
var selectedIndexPath: NSIndexPath?
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
selectedIndexPath = indexPath
}
public override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
let cell = theCollectionView.cellForItemAtIndexPath(selectedIndexPath)
//do what you need to do
}
}
I have the following class for a table view cell:
class QuestionCell: UITableViewCell {
#IBOutlet weak var questionAnswer: UISwitch!
#IBOutlet weak var questionLabel: UILabel!
var data: Answer?
func configureForQuestion(data: Answer) {
print("configureForQuestion triggered")
questionLabel.text = data.question
self.data = data
}
#IBAction func questionAnswerChanged(sender: UISwitch) {
data?.answer = sender.on
}
}
This cell consists of a label and switch. Currently when I change the switch status for the first row, it is also changing for the last row. No other rows seem to be connected in this way. I'm pretty stumped on this one.
Extra info:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return answers.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(TableView.CellIdentifiers.QuestionCell, forIndexPath: indexPath) as! QuestionCell
cell.configureForQuestion(answers[indexPath.row])
return cell
}
You need to override prepareForReuse in the cell and reset you ui elements data.
override func prepareForReuse() {
self.questionLabel.text = nil
self.questionAnswer.on = data?.answer
}
It may be that your answers array does not have the data you expect. Print out the values of answers in the debugger and see if each answer has the indexPath.row you expect it to have.
Can you show your code for how you set the values in answers?
I'm trying to make a view controller that has one text field that populates the tableview below, ideally the user will be able to continue to add to the tableview without jumping between two views.
I previously had it working with the text field on one view that populates a UITableView and used prepareForSegue to push the data to the table, but I haven't been able to get it to work with just one view.
Can anyone please point out where I'm going wrong or push me to a tutorial / documentation to help?
Edit: Clarity
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet var tableView: UITableView!
#IBOutlet weak var textField: UITextField!
var items: [String] = ["Pls", "work", "pls", "work", "pls"]
var foodGroup: FoodGroup = FoodGroup(itemName:"")
//var foodGroup: [FoodGroup] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel.text = self.items[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("Selected cell #\(indexPath)")
}
func addFood(sender: AnyObject!) {
if (countElements(self.textField.text) > 0) {
self.foodGroup = FoodGroup(itemName: self.textField.text)
}
}
#IBAction func addFoodToList() {
let source = FoodGroup
let foodGroup:FoodGroup = source.foodGroup
if foodGroup.itemName != "" {
self.foodGroup.append(foodGroup)
self.tableView.reloadData()
}
}
}
It seems like your intention here is to have your dataSource be an array of FoodGroup objects. If this is indeed the case you can get rid of your foodGroup instance variable and update your items definition to be like so:
var items = [FoodGroup]()
then in addFoodToList:
if self.textField.text != "" {
let foodGroup = FoodGroup(itemName: self.textField.text)
self.items.append(foodGroup)
self.tableView.reloadData()
}
and finally in cellForRowAtIndexPath:
var cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
let foodGroup = self.items[indexPath.row] as FoodGroup
cell.textLabel.text = foodGroup.itemName
return cell
Also I don't quite see the intention of your the addFood(sender: AnyObject!) function. Looks like cruft. I would get rid of it. Good luck!