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
})
Related
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
}
Problem I want to allow users to hit 'swap' in a table cell and then find a different Realm object to populate the 2 text labels (for exercise name and number of reps) in the cell with the values from the new object.
Research There's quite a bit (admittedly old) on 'moving rows' (e.g. here How to swap two custom cells with one another in tableview?) and also here (UITableView swap cells) and then there's obviously a lot on reloading data in itself but I can't find anything on this use case.
What have I tried my code below works fine for retrieving a new object. i.e. there's some data in the cell, then when you hit the 'swapButton' it goes grabs another one ready to put in the tableView. I know how to reload data generally but not within one particular cell in situ (the cell that the particular swap button belongs to... each cell has a 'swap button').
I'm guessing I need to somehow find the indexRow of the 'swapButton' and then access the cell properties of that particular cell but not sure where to start (I've played around with quite a few different variants but I'm just guessing so it's not working!)
class WorkoutCell : UITableViewCell {
#IBOutlet weak var exerciseName: UILabel!
#IBOutlet weak var repsNumber: UILabel!
#IBAction func swapButtonPressed(_ sender: Any) {
swapExercise()
}
func swapExercise() {
let realmExercisePool = realm.objects(ExerciseGeneratorObject.self)
func generateExercise() -> WorkoutExercise {
let index = Int(arc4random_uniform(UInt32(realmExercisePool.count)))
return realmExercisePool[index].generateExercise()
}
}
//do something here like cell.workoutName
//= swapExercise[indexRow].generateExercise().name???
}
Hold your objects somewhere in a VC that shows UITableView. Then add the VC as the target to swap button. Implement swapping objects on button press and reload data of table view after.
The whole idea is to move logic to view controller, not in separate cell.
There are 2 ways.
1. Adding VS as button action target.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ... // get cell and configure it
cell.swapBtn.addTarget(self, action: #selector(swapTapped(_:)), for: .touchUpInside)
return cell
}
func swapTapped(_ button: UIButton) {
let buttonPosition = button.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)!
// find object at that index path
// swap it with another
self.tableView.reloadData()
}
Make VC to be delegate of cell. More code. Here you create protocol in cell and add delegate variable. Then when you create cell you assign to VC as delegate for cell:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ... // get cell and configure it
cell.delegate = self
return cell
}
func swapTappedForCell(_ cell: SwapCell) {
// the same logic for swapping
}
Solution from OP I adapted the code here How to access the content of a custom cell in swift using button tag?
Using delegates and protocols is the most sustainable way to achieve this I think.
I hope this helps others with the same problem!
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)
}
I want to implement a simple tableView in my Viewcontroller but the output is not complete. The content is just visible in one sometimes in two rows.
The classic things:
The class use this:
class MealOfWeekView: UIViewController, UITableViewDelegate, UITableViewDataSource {...}
I set the delegates
override func viewDidLoad() {
super.viewDidLoad()
self.tableViewFood.delegate = self
self.tableViewFood.dataSource = self
self.tableViewFood.reloadData()
}
I use the right identifier:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("foodIdent", forIndexPath: indexPath) as! FoodTableViewCell
cell.dayLabel?.text = "\(day[indexPath.row])"
return cell
}
return 1 section and return 7 rows
=> I use the first time the Tab Bar Controller, in my first tab there is already a tableView. This one works perfect.
The tableView shows as far as I know the days tuesday, saturday or sunday... don't know, whether the info is important :)
EDIT
So with your help I figured out, that my daylabel is nil.
My FoodTableViewCell
class FoodTableViewCell: UITableViewCell {
#IBOutlet weak var dayLbl: UILabel!
}
I add to my viewDidLoad this line:
self.tableViewFood.registerClass(FoodTableViewCell.self, forCellReuseIdentifier: "foodIdent")
But it doesn't work.
If you need more code, give a sign.
Thank you!
Looks like this:
Your issue is with your custom class FoodTableViewCell, but I would verify the following first.
Confirm that the label is being set with the day of the week for the row index. You can do this by setting a breakpoint or printing out statements such as where you create your cells.
print("dayLabel: (cell.dayLabel?.text)")
print("day[indexPath.row]: (day[indexPath.row]")
Confirm you are registering the FoodTableViewCell with the table view.
Confirm that your subclass FoodTableViewCell's dayLabel property is setup correctly. Try changing the background color so you know it is least being displayed in the UI.
Check that your subclass of UITableViewCell is overriding and setting up the dayLabel for reuse correctly.prepareForReuse()
Background information for working with table views
SOLUTION
I found my answer here
When you use Tab Bar Controller you have to use viewDidAppear or viewWillAppear
this lines worked for me:
override func viewWillAppear(animated: Bool) {
self.tableViewFood.delegate = self
self.tableViewFood.dataSource = self
dispatch_async(dispatch_get_main_queue(), {
self.tableViewFood.reloadData()
})
}
I've googled for hours and have tried a handful of tutorials, but haven't been able to get this working:
I have a TableView, and I want to make it so pressing on a cell presents a popup that has a date picker.
I have my custom viewcontroller with the date picker presenting (popping up from the bottom), but it takes up the entire screen. Thoughts? I found one mention of this exact issue while googling but the solution didn't work.
One possibility is to overlay a subview (object of UIView) (with a date picker and a done button) on top of your tableview. Then use .hidden feature of the subview to hide/show the view. The following is an example of the tableviewcontroller. When setting up the storyboard make sure that the subview has the layout constraints so the date picker is positioned properly. I used the "resolve auto layout issues" and it worked good. Unless you do special processing the subview will get positioned at the bottom of the rows. If you have a lot of rows the aubview will get clipped or hidden completely. So it is better to position the subview at the relative to the bottom of the page in your auto layout.
Here is a simple example that worked well for me. In viewDidLoad the subview is hidden. When you click on any row it will show the subview and the date picker. When you press done it will hide the subview again.
import UIKit
class TableViewController: UITableViewController {
#IBAction func doneButton(sender: UIButton) {
// process the date using datePickerOutlet properties
subView.hidden = true // hide the subview and its components
}
#IBOutlet weak var subView: UIView!
#IBOutlet weak var datePickerOutlet: UIDatePicker!
#IBAction func datePicker(sender: UIDatePicker) {
}
override func viewDidLoad() {
super.viewDidLoad()
subView.hidden = true // hide the subview and its components
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("\(indexPath.row)")
subView.hidden = false // show the subview and its components
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel!.text = "\(indexPath.row)"
return cell
}
}
Alternatively, put it in a UIAlertView(). At least it will be centered. It's going to be tough to combine a table and a picker on a small screen, like let's say a 4S.
I think this should work:
override var preferredContentSize: CGSize {
get{
// Checks if it is currently presenting
if presentingViewController != nil {
return (datePicker.sizeThatFits(presentingViewController!.view.bounds.size))
}
return super.preferredContentSize
}
set{ super.preferredContentSize = newValue }
}
This code goes under the View controller for the popup.
Basically what it does is to set the width of the popup to the minimum size required for the date picker.
Got it from the iTunes U tutorial by Paul Hegarty