Prevent UITableViewCells from highlighting when panning - ios

I am using a UIViewController which contains a ContainerView. Inside the ContainerView I have a UITableViewController. I have a PanGestureRecognizer in my UIViewController which I use for dismissing it. Now the problem I have is that when I pan to close the UIViewController, the TableViewCells inside UITableViewController that are touched become briefly highlighted.
I have disabled scrolling in my tableview as I don't need it.
I added this to my pan gesture handler's .began but it didn't have any effect:
myTableView.isUserInteractionEnabled = false
I also tried:
myGestureRecognizer.cancelsTouchesInView = true
but the touches are still passed to the TableView and cause the cells to become highlighted. Is there any solution for this?

I ended up using this:
myGestureRecognizer.delaysTouchesBegan = true
It may not be useful in every situation, but for my TableView it prevents the highlights from happening.

You could try immediately deselecting rows that are selected in the delegate method for didSelectRow.
extension MyViewController: UITableViewDelegate {
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
Will prevent cells from being highlighted when selected. From my experience, this is somewhat common practice.
EDIT: My mistake, misread the question. In which case, you could consider using the tableView's scrollView delegate to determine when you're scrolling, and disable interaction on the individual cells like so:
class ViewController: UIViewController {
private var areCellsDisabled = false {
didSet {
tableView.reloadData()
}
}
// Rest of your view controller logic here...
}
extension ViewController: UITableViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
areCellsDisabled = true
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
areCellsDisabled = false
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Configure/dequeue the cell, etc.
if areCellsDisabled {
cell.isUserInteractionEnabled = false
} else {
cell.isUserInteractionEnabled = true
}
return cell
}
}
This might be taking a hammer to the problem, though. Let me know if it helps.

Related

How to trigger UITableViewCell editActions programmatically? [duplicate]

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).

Can't get didSelectRowAt to work for TableView

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.

didSelectRowAt called only on multitouch

What can make UITableViewCell detect only multitouch? If I tap with one finger it doesn't call didSelectRowAtIndex.
Here is my Code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "bookingSettingsCell", for: indexPath) as! SettingsTableViewCell
if indexPath.row < 3 {
cell.settingSwitch.removeFromSuperview()
}
cell.settingTitle.text = dataForSetting[indexPath.row].first
if dataForSetting[indexPath.row].last != "Pencil" {
cell.settingValue.isHidden = true
cell.settingChangable.isHidden = true
cell.settingSwitch.isHidden = false
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
DispatchQueue.main.async(execute: {
if indexPath.row < 3 {
let destinationVC = self.storyboard?.instantiateViewController(withIdentifier: "DatePicker") as! DatePickerViewController
destinationVC.modalPresentationStyle = .overCurrentContext
destinationVC.delegate = self
self.present(destinationVC, animated: true, completion: nil)
}
})
}
UITableViewCell class:
class SettingsTableViewCell: UITableViewCell {
#IBOutlet var settingTitle: UILabel!
#IBOutlet var settingChangable: UIImageView!
#IBOutlet var settingValue: UILabel!
#IBOutlet var settingSwitch: UISwitch!
override func awakeFromNib() {
super.awakeFromNib()
settingSwitch.transform = CGAffineTransform(scaleX: 0.65, y: 0.60)
}
}
You can handle the behavior of uitableviews's didSelectRowAt method after implementing the delegate methods of gesture recognizer. This prevents the undefined behavior of taps between uiview and tableViewCell and keeps the record of touches.
UIGestureRecognizerDelegate method:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
println("in shouldReceiveTouch")
gestureRecognizer.delegate = self
if (touch.view == tableView){
println("touching tableView list")
return false
}
else{
println("touching elsewhere")
return true
}
}
I figured out that this happened because I added UITapGestureRecognizerto my superview so I can hide keyboard on touch. But actually still don't understand why in this case it worked with multitouch? I would suggest that my mistake should block all tap events.
I think that the problem isn't in didSelectRowAt but in a UITableViewCell...
are you using the constraint logic when you design the xib?
Is possible that some object (label or imageView) create problem with touch...
review your constraint or design your tableViewCell direct in uitableView from storyboard.
You can solve this problem by adding the UIGesture recogniser delegate in your class. Add the UIGestureRecognizerDelegate in your class. Follow these steps:-
/*
class SignupInstructorViewController: UIViewController, UIGestureRecognizerDelegate
Note:- Now for identify whether you are tapping on tableView or some where else you have to implement this gesture delegate like this.
//MARK:- Gesture delegates
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
// if user touch the tableview then below condition will be true
if touch.view != nil && (touch.view!.isDescendantOfView(tableViewName)) {
return false
}
return true
}
HopeFully, it will works for you.
Thanks in Advance.
*/
You can check if this happens due to any gesture by removing all the gestures of the view as below and try.
view.gestureRecognizers?.removeAll()
Better to find the exact gesture which cause the issue and handle it properly.

Swift MPGTextField autocomplete in tableview cell

I can't see suggestions when typing.. i have tableview cell and textfield in it.
I'm using MPGTextField library, swift version(swift 2 supported).
Any solution for this?
Code:
#IBOutlet weak var articleField: MPGTextField_Swift!
override func viewDidLoad() {
super.viewDidLoad()
articleField.mDelegate = self
}
func dataForPopoverInTextField(textfield: MPGTextField_Swift) -> [Dictionary<String, AnyObject>] {
return articles
}
func textFieldShouldSelect(textField: MPGTextField_Swift) -> Bool{
return true
}
func textFieldDidEndEditing(textField: MPGTextField_Swift, withSelection data: Dictionary<String,AnyObject>){
print(data["CustomObject"])
}
In the MPGTextField-Swift.swift you'll find a function provideSuggestions()
In this function you'll find a line
self.superview!.addSubview(tableViewController!.tableView)
Replace this line with
//BUG FIX - SHOW ON TOP
//self.superview!.addSubview(tableViewController!.tableView)
let aView = tableViewController!.tableView
var frame = aView.frame
frame.origin = self.superview!.convertPoint(frame.origin, toView: nil)
aView.frame = frame
self.window!.addSubview(aView)
////
I've forked MPGTextField repository, made necessary changes for demo purpose.
You can find my repo at https://github.com/rishi420/MPGTextField
Note: This repo needs Xcode 7.1.1 to compile. Feel free to contribute. :-]
I can't really tell what's going on. Is the autocomplete box showing but just cut off at the bottom of the tableViewCell? If so, try setting clipsToBounds to false on the tableViewCell, and maybe even its content view too.
Touch events are not by default recognized for areas outside of a view's frame. To route the taps to the suggestion, you'll have to subclass the tableViewCell and override hitTest
A solution for this would be set the cell height when the cell is selected:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if tableView.indexPathForSelectedRow == indexPath {
return self.selectedCellHeight
}
return self.unSelectedCellHeight
}
You only have to set the unselectedCellHeight and the selectedCellHeight
The beginUpdates() and endUpdates() will update the cell height and animate it.

Swift - pop up view, date picker?

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

Resources