I am having trouble getting the didSelectRowAt method to work for a TableView inside of a regular ViewController. I have already made sure that the delegate and data source for the table are set in the ViewController code. This ViewController populates the tableview cells with results from a search query to an API, and the rendering of cell data is working fine.
It's just the didSelectRowAt method that is not registering. I did try manually adding the same delegate information on the Main.storyboard, but the little + sign won't trigger any popup windows. I am wondering if there is something in the Main.storyboard that needs fixing. I have attached the images of the ViewController and TableView connections inspector as well. I am new to iOS development and don't have much experience with graphic interfaces for mobile design, so I am assuming it's something there but maybe I am wrong.
Here's the basic version of my code:
class SearchViewController: UIViewController, UISearchBarDelegate, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBOutlet var searchBar: UISearchBar!
...variable declarations ....
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
searchResults = []
searchBar.delegate = self
tableView.dataSource = self
tableView.delegate = self
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1;
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResults!.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "searchTableViewCell", for: indexPath) as! SearchTableViewCell
if(searchActive && !(self.searchResults?.isEmpty)!) {
(doing some stuff with search results here...works fine)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("hello!")
}
func searchBar(_ searchBar: UISearchBar,
textDidChange searchText: String) {
print("search text \(searchText)")
getSearchResultJSON(term: searchText) { json in
let data = json as! [Dictionary<String, String>]
self.searchResults = data
}
self.tableView.reloadData()
}
...
}
[]
[]
EDIT: as a sanity check for if the search asynchronous function was changing anything, I just tried removing all search-related code and filling the tableview from a hardcoded dummy variable array. It worked to display the dummy variables, but still no ability to select a cell and get any reaction. I also saw a couple mentions that I had previously had a typo with didDeSelectRowAt instead of didSelectRow at, that has been fixed but the behaviour is the same.
This ended up being related to a tap gesture that occurs in the hideKeyboardWhenTappedAround() extension that I wrote
Found it! The culprit was the self.hideKeyboardWhenTappedAround(), which is an extension I wrote to hide the keyboard. This interfered with the tap of a cell because it did indeed utilize UITapGestureRecognizer. Thanks for the hints everyone.
You are using didDeselectRowAt instead of didSelectRowAt
Edit
Well, use this below delegate then
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
and make your controller conform to UIGestureRecognizerDelegate
If you are using tap gesture on main view then table view cell did select method is not working properly.
In picture you uploaded, delegate and datasource aren't connected to the ViewController.
Remove the code in viewdidload (tableview.delegate = self) and connect them in storyboard.
Related
So I have a UITableView (not a tableView controller), and I have set its delegate, dataSource, and even registered its reusable cell, in my viewDidLoad:
searchTableView.delegate = self
searchTableView.dataSource = self
searchTableView.isHidden = true
searchTableView.isUserInteractionEnabled = false
searchTableView.register(UITableViewCell.self, forCellReuseIdentifier: "SearchResultCell")
Don't worry about the isUserInteractionEnabled, I change that to true when the user is supposed to be able to use it. Anyway, I am 100% sure that the prototype cell has the correct Reuse Identifier in the storyboard as well as my cellForRowAt function. Here are the table view dataSource functions:
//MARK: - UITableViewDelegate & UITableViewDataSource Methods
extension MapViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
searchCompleter.queryFragment = searchBar.searchTextField.text!
let completionResults = searchCompleter.results
print("Yo")
return completionResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
searchCompleter.queryFragment = searchBar.searchTextField.text!
let completionResults = searchCompleter.results
print(completionResults)
print("yo")
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath)
cell.textLabel?.text = completionResults[indexPath.row].subtitle
return cell
}
}
Yet nothing is populating the tableView when I am in the simulator. I think the tableView dataSource functions are just not being called, because I set print statements in them and they do not appear. I am also 100% sure that completionResults (an MKLocalSearchCompleter) does work, as I use it at the same time, in my searchBar textDidChange function and it prints results. I don't see why it would work in that function, and not my tableview ones, unless it does HAVE to be in a searchBar function. Anyway, just in case that matters, here's that function:
//MARK: - UISearchBarDelegate Methods
extension MapViewController: UISearchBarDelegate, MKLocalSearchCompleterDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.count > 0 {
searchTableView.isHidden = false
searchTableView.isUserInteractionEnabled = true
} else {
searchTableView.isHidden = true
searchTableView.isUserInteractionEnabled = false
}
searchCompleter.queryFragment = searchBar.searchTextField.text!
let completionResults = searchCompleter.results
print(completionResults)
}
}
Does anyone have any idea why my searchTableView won't call the dataSource functions? Someone even suggested that it was constraints at one point, and I have made sure that those are correct, so that isn't a problem either. Thanks for any help!
You don’t appear to be calling searchTableView.reloadData(). It won’t reload the table unless you call that method.
Generally you’d set a delegate for the MKLocalSearchCompleter and call reloadData in completerDidUpdateResults(_:).
I've already looked at the post UITableView.reloadData() is not working. I'm not sure that it applies to my situation, but let me know if I'm wrong.
My app has a tableView. From the main viewController I am opening another viewController, creating a new object, and then passing that object back to the original viewController, where it is added to an array called timers. All of that is working fine. However, when I call tableView.reloadData() in didUnwindFromNewTimerVC() to display the updated contents of the timers array, nothing happens.
NOTE: I have verified that the timers array is updated with the new object. Its count increments, and I can access its members. Everything else in didUnwindFromNewTimerVC() executes normally. The tableView just isn't updating to reflect it.
Here is my code:
import UIKit
class TimerListScreen: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tabelView: UITableView!
var timers = [Timer]()
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tabelView.delegate = self
tabelView.dataSource = self
let tempTimer = Timer(timerLabel: "temp timer")
timers.append(tempTimer)
}
#IBAction func didUnwindFromNewTimerVC(_sender:UIStoryboardSegue){
guard let newTimerVC = _sender.source as? newTimerVC else{return}
newTimerVC.timer.setTimerLabel(timerLabel: newTimerVC.timerLabel.text!)
timers.append(newTimerVC.timer)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as? TimerCell{
let timer = timers[indexPath.row]
cell.updateUI(Timer: timer)
return cell
}else{
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timers.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 78
}
}
Thank you
Please note the spelling. There are two table view instances: the outlet tabelView and a (pointless) instance tableView.
Reload the data of the outlet
tabelView.reloadData()
and delete the declaration line of the second instance let tableView ....
However I'd recommend to rename the outlet to correctly spelled tableView (you might need to reconnect the outlet in Interface Builder).
And force unwrap the cell
let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as! TimerCell
and remove the if - else part. The code must not crash if everything is hooked up correctly in IB.
This question already has answers here:
Open UITableView edit action buttons programmatically
(5 answers)
Closed 5 years ago.
I have made a few custom edit actions on my tableviewcell. It works fine when I swipe, but I was wondering if there was any way to trigger these actions when I tap the cell. Also, I have seen lots of people answer similar questions with just,
tableView.setEditing(true, animated: true)
though this is not the solution I am looking for. I want the actions to immediately get displayed without the press of another button.
Short answer is - there is no such way.
However, if you really need something like that, you can mimic this behaviour, though it requires lot more implementation and state handling on your own.
Here is a quick and very dirty solution, which overrides touchesEnded method of your custom cell. Remember to set up Cell as a dynamic prototype of the cell in your table view in relevant storyboard and set its reuse identifier to identitifer.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CellDelegate {
#IBOutlet weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "identifier") as? Cell else {
return UITableViewCell()
}
cell.textLabel?.text = "\(indexPath.row)"
cell.indexPath = indexPath
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
return nil
}
func doAction(for cell: Cell) {
let indexPath = cell.indexPath
print("doing action for cell at: \(indexPath!.row)")
// your implementation for action
// maybe delete a cell or whatever?
cell.hideFakeActions()
}
}
protocol CellDelegate: class {
func doAction(for cell: Cell)
}
class Cell: UITableViewCell {
weak var delegate: CellDelegate?
var indexPath: IndexPath!
#IBOutlet weak var buttonAction1: UIButton?
#IBOutlet weak var constraintButtonFakeActionWidth: NSLayoutConstraint?
override func awakeFromNib() {
self.constraintButtonFakeActionWidth?.constant = 0
}
override func touchesEnded(_ touches: Set<UITouch>,
with event: UIEvent?) {
guard let point = touches.first?.location(in: self) else {
return
}
if self.point(inside: point, with: event) {
print("event is in cell number \(indexPath.row)")
self.showFakeActions()
}
}
func showFakeActions() {
self.constraintButtonFakeActionWidth?.constant = 80
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
func hideFakeActions() {
self.constraintButtonFakeActionWidth?.constant = 0
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
#IBAction func fakeAction() {
delegate?.doAction(for: self)
}
}
So how does it work? Each cell is a UIView which inherits from abstract UIResponder interface. You can override its methods to do actions on your own on behalf of events that are dispatched to them. This is the first step where you override touchesEnded.
Take a look at the screenshot from interface builder - you have to hook up the constraint.
I've also implemented the delegate which returns nil for all edit actions of the cells, so they don't interfere with your workaround.
Next, you set up a custom delegate to get a callback from the cell. I also attached IndexPath to the cell for the convenience of managing data in the dataSource, which you have to implement.
Remember that this code lacks a lot, like prepareForReuse method implementation. Also, you probably want to do additional checks in touchesEnded override which would guarantee that this delegate callback is not fired more than once per touch and prevent multiple touches. The logic for disabling user interaction on a cell is not implemented here as well. And interface requires fine-tuning (like text appears to be squashed during the animation).
I've been searching for awhile without luck. I am trying to find an example of a View Controller with a UITableView that has sections. The examples I've see are all dealing with a Table View Controller which I cannot use as I have need of buttons in the same view which control the content of the table view. Anyone have an example, know of an example or have an idea about to implement such? Thanks.
Edit
I've got a table view in a view controller, get the data from an api call, separate the sections and data in an array of a struct. I then send this to be bound to the table view. Doing so throws
[UIView tableView:numberOfRowsInSection:]: unrecognized selector sent to instance
but I don't understand where the problem is.
Code for the tablview
//MARK: Tableview delegates
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let count = incidentDataSection?.count{
return count
}
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (incidentDataSection?.count)! > 0{
return incidentDataSection![section].incidents.count
}
return 0
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
/*
func tableView(tableView: UITableView, iconForHeaderInSection section: Int) -> UIImage? {
return incidentDataSection?[section].icon
}*/
//if clicked, will openn details view passing in the details
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//let incidentDetails = incidentData?[indexPath.row]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let section = incidentDataSection?[indexPath.section] {
let cell = tableView.dequeueReusableCell(withIdentifier: "IncidentTableViewCell") as! IncidentTableViewCell
cell.roadNameLabel.text = section.incidents[indexPath.row].RoadWay
cell.whenLabel.text = section.incidents[indexPath.row].DateCreated
cell.statusLabel.text = section.incidents[indexPath.row].DateCleared
return cell
}
return UITableViewCell()
}
incidentDataSection is an array of a struct which has the section title and the different items.
Answer
Though I received some fairly good feedback, the cause was actually a typo. Looking closely at
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
you'll notice the problem is that there is no underscore before tableView:. What was happening is that the datasource and delegate were skipping over the functions since with and without call different protocols in swift 3. Thanks to thislink I was able to figure out the cause. My bad for forgetting to mention this was in Swift 3. Might had saved everyone some time.
You need a tableview instance in your view controller.
Implement the protocols UITableViewDelegate, UITableViewDataSource in your view controller as a UITableViewController.
Don't forget bind the tableview in XIB with tableview in the class.
Look this sample:
class Sample01ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
tableView?.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.tableView?.reloadData()
}
// ...
You have the required methods implemented, however it sounds like you need to "subclass" or "subcribe" to the UITableView's delegate and dataSource. By using:
class MyViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView : UITableView!
}
Now that you have those protocols you will need to set your tableView's delegate and dataSource to your viewController. You can do this using storyboard by drag and drop, or inside of your viewDidLoad() which is what I always do because it is easy for other developers to see from the start of opening your code where your delegate and dataSources are assigned to. Using:
#override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
Then your delegate methods and dataSource methods in your viewcontroller will be called for that tableView. Then you can add the IBOutlets to UIButton/UILabel/UISwitch, etc... and do what you will with your ViewController without being limited to simply using a table view inside of that view controller. I Almost always use this methods when using UITableViews/UICollectionViews even if I set the tableView/collectionView to be the size of the whole view because I like the freedom of using a UIViewController over a UITableViewController/UICollectionViewController.
*Note numberOfRows() is not required but I always override it as well, just kind of a habit at this point. Also you sound new to iOS development, so if you aren't already, the next thing I would look into after getting your tableView up and running is pulling your data from your API on a background thread to keep your mainThread open for user response on your UI, DispatchQueue. This is really important if you are displaying images from the API.
I have the following problem:
I am making a Pokédex-like application that displays a list of all 721 Pokémon on the first tab, and another list on the second tab containing My Favorite Pokémon. Essentially, there are two identical ViewControllers connected to my TabBar.
My storyboard is as follows:
So here is the problem:
The TableView on the first (and initial) tab works fine. However, when I load the TableView on the second tab the Pokémon are loaded, but not displayed. I am able to click the TableViewCell and go to the detail page, but the label in the TableViewCell is not showing anything.
This is the code I use for loading Favorites TableView
class FavoritesViewController: BaseViewController,
UITableViewDataSource, UITableViewDelegate {
#IBOutlet var FavoritesListView: UITableView!
var pokemonList: [String] = ["Nothing Here!"]
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("FavoriteCell", forIndexPath: indexPath) as! FavoriteCell
var name = pokemonList[indexPath.row]
capitalizeFirstLetter(&name)
cell.nameLabel.text = name
return cell;
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pokemonList.count
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print(pokemonList[indexPath.row])
self.performSegueWithIdentifier("ToPokemonDetail", sender: pokemonList[indexPath.row])
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "ToPokemonDetail"){
let destination = segue.destinationViewController as! PokemonDetailViewController
let thisPokemon = sender as! String
destination.currentPokemon = thisPokemon
}
}
override func viewWillAppear(animated: Bool) {
FavoritesListView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Fetch the cached list, getNames returns an array of strings
let list = utility.getNames("Favorites")
pokemonList = list
}
The delegate and the dataSource are set via the storyboard.
The above code works, and shows the Favorites list just fine. The class for the complete Pokédex has a similar construction.
I have tried switching Favorites and Pokédex around, so that it shows the complete Pokémon list on startup. All 721 Pokémon are shown correctly, but then the Favorites are not visible.
What else I have tried:
Checking the Reuse Identifiers, over and over
Referencing outlets should be bound correctly
Calling TableView.reloadData() in the viewDidAppear method
Switching around the tab items
Does anyone have any clue what on earth is going on here?
Feel free to ask any more questions
Edit: this is what happens when I swap the two TabBar Buttons around, no code changes
Pokédex Screen
Favorites Screen
GitHub Project Here
Problem is in storyboard cell label frame. Set constraints of view controller for (Any,Any) Size Class. I can commit the code on github if you can give me write rights on your git. Thanks
Perhaps your table's delegate and dataSource are not set.
table.delegate = self
table.dataSource = self
Of course this is after you add the properties to your view controller
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
Your number of rows is always 0 for that controller,
I looked into your code pokemonList count is always 0 its not updating data in it
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pokemonList.count
}
The big issue is your PokemonDetailViewController is not a UITableViewController. It needs to inherent from UITableViewDataSource, UITableViewDelegate and then be connected to the storyboard view to provide data and formatting for a table.