How to use UITableView for TextField input view - ios

I currently have an app which uses UIPickerViews to allow users to select which answer they want for a text field (to avoid spelling mistakes etc).
However, I have found that the UIPickerView isn't really what I want to use because I haven't had great feedback from it when testing.
I have done some research into how to use a UITableView for text field inputs instead, so when the user clicks the Textfield, the user segues to a UITableView with the same options which would be provided by the UIPickerView. Once they click the cell with the option they are looking for it would segue back to the form with the result chosen inside the text field. I thought this would be a better user experience as I could also implement the search to help users narrow down the option they require quicker.
I have been trying to get this to work for a while now, but I'm quite new at coding and haven't been able to crack it yet. I would just like advice on how to approach this? I'm using Swift and the Storyboard to create my app.
Would I need to create a separate ViewController with a UITableView that loads the options and then move the value back to the form once the cell is clicked?

One approach would be to use a table view in separate view controller. lets call it choiceVC and pass data which text field was tapped.
Then send the data back to your form to show what user has selected.
Follow these steps
Detect user tap on text field and segue to choiceVC by implementing this delegate function of UITextField
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
//push your choiceVC and pass data
var textFeildTapped = ""
//note: chooseGameTextField should be text field's outlet
if textField == chooseGameTextField{
textFeildTapped = "games"
}else if textField == chooseActiviyTextField {
textFeildTapped = "activiy"
}
//first set identifier of your view controller by going to identity inspector and setting the value StoryBoard ID
if let controller = storyboard?.instantiateViewController(withIdentifier: "choiceVC") as? ProductDetailVc{
//pass which text field was tapped
controller.choicesToShow = textFeildTapped
navigationController?.pushViewController(controller, animated: true)
}
return true
}
Note: choiceVC should have a variable "choicesToShow" of type string
in viewdidload of choiceVC check the variable
if choicesToShow == "games" {
//populate your table view with games.
}else {
//check for other cases and proceed accordingly
//activiy, console etc
}
Implement didSelect delegate method of UITableView
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Pass data back to your form using **delegate pattern**. see link in notes below
}
Notes:
See screen shot of setting storyBoard ID
see how to implement table view if you dont know
https://stackoverflow.com/a/33234181/7698092
See how to pass data backward using delegate pattern
https://medium.com/#mayooresan/passing-data-between-viewcontrollers-via-delegate-protocols-4ecde4b167de

If you are looking for an alternative for picker view to select options you can use dropdown like controls Eg.
DropDown
RSSelectionMenu
I hope these libraries can solve your issues, best of luck

I hope this code work for you. it's wroking for me.
First view controller for textfiled form where you want to open tableview.for that use textfield Delegate.
First View Controller
func doSomething(text: UITextField, with data: String) {
text.text = data
}
func textFieldDidBeginEditing(_ textField: UITextField) {
let objGametableVC = self.storyboard?.instantiateViewController(withIdentifier: "GametableVC") as! GametableVC;
objGametableVC.delegate = self
objGametableVC.selectedTextField = textField
if textField == txtActivity{
objGametableVC.tblData.removeAll()
objGametableVC.tblData = ["act1","act2"]
}
else if textField == txtGameName{
objGametableVC.tblData.removeAll()
objGametableVC.tblData = ["gam1","game2"]
}
textField.resignFirstResponder()
self.navigationController?.pushViewController(objGametableVC, animated: true);
}
Second view Controller For tableview show and pass data from second to first controller
Second view Controller
var delegate: Delegate?
var tblData: [String] = [String]()
var selectedTextField:UITextField? = nil
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tblData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tblcell = tableView.dequeueReusableCell(withIdentifier: "gamelblCell", for: indexPath) as! gamelblCell
tblcell.lblName.text = tblData[indexPath.row]
return tblcell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let tblcell = tableView.cellForRow(at: indexPath) as! gamelblCell
let data = tblcell.lblName.text
delegate?.doSomething(text: selectedTextField!, with: data!)
self.navigationController?.popViewController(animated: true)
}

Related

TableViewController / Custom cell - can't move to next text field

I have a table view controller with a custom cell which contains a text field - it's a form basically.
i want to automatically go to the next text field when users press "return" on their keyboard but for some reason my solution doesn't work.
In TableViewController, I do:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? CustomCell
cell?.box.tag = indexPath.row
In my custom table view cell, I have
override func awakeFromNib() {
super.awakeFromNib()
box.delegate = self
...
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.viewWithTag(textField.tag+1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}
The issue is that textField.superview?.viewWithTag(textField.tag+1) is always nil. I don't know why because I clearly set the tag and also mark it as a delegate. thank you.
Adding some clarity and more suggestions to the valid answer by #jawadAli, as I feel you are still new to iOS development.
You are trying to get the tableView from the textField. But you will not get it by referring to the superview of textField. Because the view hierarchy would be like this:
UITableView > UITableViewCell > contentView > Your text field.
There can also be some more views in the view hierarchy, so you need to keep traversing through the superview chain till you get the UITableView. And #jawadAli has posted the code on how to get it.
But overall that is an incorrect approach. You should use delegation. I.e. your cell should call a method when it has resigned as first responder. And your table view controller will receive that call.
Then your view controller has to get the next cell and make it the first responder.
And if this doesn't make any sense to you, then I would very strongly suggest that you learn about Delegation. It's ubiquitous in iOS' libraries.
EDIT:
Approach to use delegation.
Create a protocol, let's say CellDelegate that has a function like func didFinishDataCapture(forCell: UITableViewCell).
The cell will have a delegate property of type CellDelegate.
The controller will conform to CellDelegate and will set itself as the cell's delegate in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
Now in your cell, when you are done with the text field (which you would know as cell would be the text field's delegate), you call your own delegate's function i.e. delegate.didFinishDataCapture(forCell: self).
In your implementation of didFinishDataCapture in the controller, you will know which cell has finished with the data capture and can put the logic on what to do next.
It should be nil as textField.superview is your cell class ... and your cell class does not have the view with required Tag .. so it will return nil..
import UIKit
extension UIView {
func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
guard let view = self.superview as? T else {
return self.superview?.lookForSuperviewOfType(type: type)
}
return view
}
}
Get tableView through this extension like this
let tableView = self.view.lookForSuperviewOfType(type: UITableView.self)
your function will become
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let tableView = self.view.lookForSuperviewOfType(type: UITableView.self)
if let cell = tableView?.cellForRow(at: IndexPath(row: textField.tag+1, section: 0)) as? CustomCell {
cell.box.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return true
}

Master Detail View With Segue

I'm Trying to learn how to do a detail view for my project .
I have a simple tableView with a simple Array data to fill it.
The Table View :
TableView Example
I designed a detail View as well, with static tableViewCells
Detail View example :
Example
I'v Connected both with a segue :
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("Profile", sender: indexPath);
}
I also connected all the labels and images with Outlets i want to change between each cell but i don't how to advance from here.
right now every cell shows the same thing but i want to change the data between rows . So i would like to change the data through the segue and create a master detail application like in my tableview. Can anybody help me ?
Am using Swift 2.3 and Xcode 8.1
If I understand your question correctly, you just want to pass dataSource element to the next viewController. So you can just pick it using indexPath.row and use sender parameter to set it in prepareForSegue method.
The code below assumes your dataSource is self.users array.
Swift 3
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let user = self.users[indexPath.row]
self.performSegueWithIdentifier("Profile", sender: user)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let segueId = segue.identifier? else { return }
if segueId == "Profile" {
guard let profileVC = segue.destination as? ProfileViewController else { return }
profileVC.user = sender as? User
}
}
Swift 2
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let user = self.users[indexPath.row]
self.performSegueWithIdentifier("Profile", sender: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let segueId = segue.identifier else { return }
if segueId == "Profile" {
guard let profileVC = segue.destinationViewController as? ProfileViewController else { return }
profileVC.user = sender as? User
}
}
Edit
im trying to change data like al the labels you saw between rows like
for example shalvata will have a different data from light house and
so , change the labels and images and so on
It is still unclear for me what data you want to change exactly. Also I don't understand the language on your screenshots, but since you name the relationship as master-detail, I suppose the second screen is meant to show more info about the entity selected on the first screen.
If so, you should start from designing you model so that it contains all those fields you need on the second screen. Judging by the icons it would be something like
struct Person {
var name: String?
var image: UIImage?
var age: Int?
var address: String?
var phone: String?
var schedule: String?
var music: String?
var smoking: Bool?
var car: String?
var info: String?
var hobby: String?
}
Note: Remove ? for those fields which aren't optionals, i.e. always must be set for every entity (perhaps name field)
Usage
I don't known how and when you create your Person array, but basically there are two approaches:
Use a list of entities with all fields filled on MasterVC and just pass the selected person to the DetailVC in didSelectRowAtIndexPath
Use a list of entities with some basic data (name, address, image) required for MasterVC and fill the rest of the fields only when required (didSelectRowAtIndexPath method)
In any case you'll get selected person in DetailVC and now everything you need is to use that data in cellForRow method, just as you did on MasterVC. Perhaps it would be a better option to use static TableViewController for Details screen.
Sounds like what you're trying to do does not involve segues at all. You can change data of cells using the cellForRow method in your tableViewController.
https://developer.apple.com/reference/uikit/uitableview/1614983-cellforrow
For example
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "foo"
return cell
}
If that sounds confusing to you then you should take a step back and do some tutorials then post specific questions on SO.

Container View that contains a TableView change number of rows depending number of inputs Swift

I am new to swift as well as creating iOS apps and I thought I would make a simple app that calculates the averages of the numbers inputted into the TextField. The averageViewController also has a container view as well that contains TableView. Once the person has hit the "Next" button I would like the TableView to display the numbers that have been inputted. (each cell label has a single number).
This is my segue method in my averageViewController as well as the function I am using when the user presses the button:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "sendResult" {
let inputArray = segue.destinationViewController as! averageTableViewController
inputArray.arrayFromSegue = average.getArray()
}
}
#IBAction func nextButton(sender: UIButton) {
average.arrayInput(((inputTextField.text!) as NSString).doubleValue)
calcAverage()
inputTextField.text=nil
}
This is the code I made for my averageTableViewController:
class averageTableViewController: UITableViewController {
var arrayFromSegue = NSMutableArray()
var arrayUsed = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
arrayUsed = arrayFromSegue
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var Cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
Cell.textLabel?.text = String(arrayUsed[indexPath.row])
return Cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayUsed.count
}
}
I have done quite a bit of research and I believe one of my mistakes is that the action segue that I am doing (show) does not produce the correct results.
Problem isn't in segue you do. If you want change number of rows depend on number of input you should update your array data and reload your table. In your case you can change like this:
Create variable hold your tableViewController, in your case can put name is: inputArray
inputArray = segue.destinationViewController as! averageTableViewController
When you tap nextbutton you update array average and assign it to tableViewConroller `inputArray and reload it:
inputArray.arrayUsed = average.getArray()
inputArray.tableView.reloadData()
If you have any problem don't hesitate ask me. I will help you.
You can check my demo: Demo
Your project leak segue to tableviewcontroller: Please fix project like step below:
drage segue from average to averagetable
make it is embed:
Select it and name it sendResult

How to trigger a pickerview when select a cell in a tableView

I have a tableView with 7 cells like this:
I wanna trigger some events when you select a cell. For example, start editing the username when you tap the Username row. And pop up a picker view at the bottom with Male/Female selection inside when you tap the Gender row.
As far as I know, I need to put those events inside this:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
But I have no idea how to accomplish this. Anyone has ideas? Thank you in advance.
Basically, you can make each cell has its own picker view.
open class DatePickerTableViewCell: UITableViewCell {
let picker = UIDatePicker()
open override func awakeFromNib() {
super.awakeFromNib()
picker.datePickerMode = UIDatePickerMode.date
}
open override var canBecomeFirstResponder: Bool {
return true
}
open override var canResignFirstResponder: Bool {
return true
}
open override var inputView: UIView? {
return picker
}
...
}
And then in your didSelectRowAt, just make the cell becomeFirstResponder:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? DatePickerTableViewCell {
if !cell.isFirstResponder {
_ = cell.becomeFirstResponder()
}
}
}
You can check my library for detail:
https://github.com/hijamoya/PickerViewCell
You are correct. Putting the logic in didSelectRowAtIndexPath is a good way to go.
How you do it is to write code. There is no stock answer.
If you want content to appear on top of the current window then you will need to handle that yourself. On iPad, you could use a popover, but popovers are not supported natively on iPhone/iPod touch. You might look at using a 3rd party popover library that offers popover support for iPhone. There are several on Github, and probably several on Cocoa Controls as well. I've used one before, but it had a few issues, so I wouldn't recommend it.
If you are ok presenting a whole new view controller then simply define a new view controller in your storyboard, give it a unique identifier, use instantiateViewControllerWithIdentifier to create it, then presentViewController:animated: to display it modally.
UIPickerView is subclass of UIView, so you can add and use it same like any other UIView object. for your specific recquirment you should create an object of UIPickerView and show and hide it when necessary.
So create a UIPickerView and add above the table view inside view in which you added tableView and in didSelectRowAtIndexPath set pickerView.hidden = false
And also you can animate it from bottom via
UIView.animateWithDuration(1, animations: { () -> Void in
// And set final frame here
})

tapping on table view cell

override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath : NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier ("ChecklistItem") as UITableViewCell
let label = cell.viewWithTag(1000) as UILabel
if indexPath.row == 0 {
label.text = "jaswanth"
} else if indexPath.row == 1 {
label.text = "nikhil"
} else if indexPath.row == 2 {
label.text = "krishna"
}
return cell
}
I have UITableViewController, I have used above code to display the cells in the table and I had another UIViewController when user taps on jaswanth the text(labels text) in UIViewController have be changed and the same way when nikhil is tapped the text(labels text) in UIViewController have changed
I had only one UITableViewController and one UIViewController how can I do this?
You would want to be able to detect when these cells in the table are pressed. This can be done using the recommendation by Mike Welsh through implementing the method: func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
This allows you to be able to tell which cell is selected from the NSIndexPath.
From here on, it seems to me that you want to be able to change the text (on a button or field) that is controlled by another UIViewController. One way it can be done is to have a reference to this UIViewController and creating a method to update this text field.
If the UITableViewController is a child view controller, it can be as simple as calling
let parent = parentViewController!
Else if the Controllers are related by a Segue, it can also be done by overriding prepareForSegue of the original ViewController.
By using the segue.identifier to map to the segue you have made in StoryBoard/programmatically, you are able to set a delegate/pointer to the original UIViewController.
override func prepareForSegue(_ segue: UIStoryboardSegue, sender sender: AnyObject?) {
if segue.identifier == "SomeStringID" {
self.tableViewController = segue.destinationViewController as? UITableViewController
self.tableViewController.delegate = self
}
}
With the reference to the other ViewController, you should be able to define your own method to change this text field quite easily
Hope this helps you!

Resources