I am working on a project and one of the table views the text for it is a UITextView, but on the other one is a UILabel. The UILabel detects the click from the user as a click on the table cell, but the UITextView doesn't. Why is this happening? Is there any way to fix it?
Try that
yourTextView.addTarget(self, action: #selector(myTargetFunction), for: .touchDown)
#objc func myTargetFunction(textField: UITextView) {
print("myTargetFunction")
}
Ensure the isSelectable property of your textView is true.
I figure it out, the cell view and text view are both scrow views and always will have conflict, so I need to uncheck the UserInteractions and Multiple Touch of the text view, booth set to false
I'm not sure from your question whether you're talking about selection on the UITableView row or the UITextView/UILabel itself.
If you're trying to set up a gesture recognizer on the UILabel or UITextView, I'm wondering if that's necessary or if you could just use didSelectRowAtIndexPath in your view controller.
e.g.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
yourMethodHere(for: cell)
}
Then you'd define in yourMethodHere() what behavior you want to happen when the cell is selected.
Related
I have a UICollectionView with 4 views in it. Each of these views have a UITableView inside it with custom cells. Each cell of the UITableView has a UIButton inside it and I have 2 cells per UITableView.
Something strange is happening. I have an action function for each button so that when a button is clicked, it becomes purple. The strange thing is this: if I scroll to the 4th view of my collection view and click on a button, it becomes purple as expected but then when I scroll to the 1st view of my collection view, the same button that I clicked in the 4th view (either the first one or second one) is also purple as if the 4th view of my collection view was referencing the items of my 1st view of the collection view.
I don't know at which point the 1st view becomes the same as the 4th view but here is a sample of the code:
// this is the cellForItemAt of my UICollectionView, very basic
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellView = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PollCellView
return cellView
}
// THIS IS ANOTHER FILE HERE
// this is part of my view that populate the UICollectionViews
class PollCellView: UICollectionViewCell {
// the table view
let questAndAnswersTableView : UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.separatorStyle = .none
tableView.allowsSelection = false
return tableView
}()
// I add the tableview in the view here
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(questAndAnswersTableView)
// a classic cellForRowAt of my UITableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AnswerCell2", for: indexPath) as! AnswerCell2
return cell
}
// THIS IS ANOTHER FILE HERE
// this part is my custom cell of the UITableView
class AnswerCell2: UITableViewCell {
let answerTextButton: UIButton = {
let answerButton = UIButton()
answerButton.setTitle("initial text", for: .normal)
return answerButton
}()
// I add the button to the cell here
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(answerTextButton)
// I define the action function
answerTextButton.addTarget(self, action: #selector(answerClicked), for: .touchUpInside)
}
// the action to make the button purple
#objc func answerClicked(sender: UIButton) {
sender.backgroundColor = UIColor(displayP3Red: 0.6902, green: 0.7176, blue: 0.9922, alpha: 1.0)
}
[EDIT FOLLOWING THE ANSWERS RECEIVED]
Dequeuing is definitely not as simple as it first seems... You can't trust that the tableview dequeued in a given collection view is really the one you expect... You need to keep track of the content (model) yourself. One thing that made it easier to fix is to use closures between the cell of the TableView and the cell of the UICollectionViewCell... You can very easily pass data from one to the other (like what indexPath was clicked, etc.).
when you're managing multiple nested tableviews or collectionviews or even when you're managing this yourself, you have to set up a system of arrays that when the button is clicked, you add those indexpaths to an array called "indexPathsThatArePuple" then when you after each button click, inside the button click function, you pass the indexPath and add that to the array. If the button is already inside the array when the button click function is pressed, then you remove that indexPath. in the button click function, after you add or remove indexPaths, you reload the collection view which will reload the tableviews and then inside "cellForItemAtIndexPath" you check the indexPathsThatArePuple array and then write "if indexpath is contained in indexPathsThatArePuple" then set the cell color to purple.
I understand what you're trying to do, but you don't realize or understand just how complicated the cell reuse system is in iOS. The first thing you need to do is wrap your mind around the idea the that you have to manually manage cell states due to cell resuse. The purple cell showing up in another table is from cell reuse. Apple won't automatically manage this for you, you have to do it yourself. As i described above and even my description above is not that cut and dry since you'll likely struggle with this for weeks until you grasp the concept. Good luck, you'll eventually get it.
It seems to be caused by UICollectionView cell reuse mechanism.
When the cell is created and scroll out of the screen, it will be reused later if a new cell with the same identifier is required. In your case, when you scroll down, the 1st view is reused as the 4th view, And when you scroll to the top, The 4th view is reused as the first view. If the method prepareForReuse happens to be not implemented, the UI status of the cell will not be change, so you will see the button with purple color.
I've been looking around for a while now but can't seem to work out how to stop the collection view cells from consuming the touch events.
I need the touch events to be passed down into the respective cells so that the buttons within the cells can be pressed. I was thinking i might need to work out how to disable the UICollectionView's didSelectCellAtIndexFunction?
I've also seen this as a potential solution: collectionView.cancelsTouchesInView = false
Also this link might help someone answer my question: How to add tap gesture to UICollectionView , while maintaining cell selection?
Thanks in advance!
Edit:
Also I should add: my buttons are added to a view that is in turn added to the cell's contentView. My code is done all programatically and so I am not using interface Builder at all.
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return false // all cell items you do not want to be selectable
}
Assuming all buttons are connected to the same selector, you need a way to differentiate which cell's button has been clicked. One of the ways for finding out the button's cell's index is:
func buttonPressed(button: UIButton) {
let touchPoint = collectionView.convertPoint(.zero, fromView: button)
if let indexPath = collectionView.indexPathForItemAtPoint(touchPoint) {
// now you know indexPath. You can get data or cell from here.
}
}
Try on your cell.
self.contentView.isUserInteractionEnabled = false
I am having a little trouble with buttons on a tableview.
I have a tableViewCell that I customised with 3 buttons. I set the buttons to hidden in interface builder and when the table loads the buttons are hidden as expected.
I then set the hidden property of the tableview to false when didSelectRow is called and hidden.true when didDeselectRow is called. This works fine as well. The problem is the buttons that are set to visible in the didSelectRow are also visible every seven cells down. they keep repeating themselves.
Below is the code that shows the buttons
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ContactsViewCell
print("Table selected")
cell.insertEmailButton.hidden = false
cell.insertPhoneButton.hidden = false
cell.insertAllButton.hidden = false
cell.contactTextLabel.alpha = 0.2
cell.contactDetailTextLabel.alpha = 0.2
}
And this hides them when the tableViewCell is deselected
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ContactsViewCell
cell.insertEmailButton.hidden = true
cell.insertPhoneButton.hidden = true
cell.insertAllButton.hidden = true
cell.contactTextLabel.alpha = 1.0
cell.contactDetailTextLabel.alpha = 1.0
}
I did some research and I learnt it might be the row with the buttons.hidden set to false are being reused by the tableview. But I understand from documentation that the cell being reused is from cellForRowAtIndexPath and not the cell at didSelectRow which is where I am setting the button.hidden to false.
I also tried using the cell.isSelected property in an if else statement in the cellForRowAtIndexPath to hide and show the buttons but this does not show the buttons at all.
Thanks in advance for your help
The tableview reuses the view of the cell when the table is scrolled, to save memory. So, for example, when you set the button to visible (inside didSelectRow) and then scroll down the table, the tableview will take the cells that go out of the visible screen at the top and will reuse them at the bottom, to save the overhead of creating new cells, improving performance.
That is why, your previous properties on the cells are repeating.
To get the desired hidden button on scrolled cells, I recommend setting button.hidden to true/false in
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
This will set the button to hidden whenever a new row is scrolled into the visible view area.
Hope this helps.
I solved the recurring buttons by hiding them when I check if the cell is deselected in cellForRowAtIndexPath. This also means any cell I select will lose its selected status and buttons will disappear when it leaves the view.
I can live with that.
if cell.selected == false{
cell.emailButton.hidden = true
cell.phoneButton.hidden = true
cell.allButton.hidden = true
}
UITableView reuses its cell to improve performance. So, you can not do the way you are trying. What we have to do is, like other tableview cell info e.g. title, description, thumb image etc we also need to save the state for buttons in the array. When you want to hide a button for the cell take object at index from the array and change the button state for the button and reload that table view cell. Still if you face problem or feel difficult to understand, please feel free to ask.
I want to have a table with custom UITableViewCell class as rows. Within each row (custom UITableViewCell), I have a control which the user can use to change the state of something in that row.
Let's say for sake of an example that each table row (custom UITableViewCell) contains a button and a label, and I want the clicking of the button to change the label text from "activated" to "deactivated" back and forth.
*NOTE** I understand that for the above scenario I can use the didSelectRowAtIndexPath method to implement, but this doesn't work for my actual use scenario. I used the above scenario because its simpler, and includes the core functionality I need.
Normally if this were not in a UITableViewCell, I would have a button with an IBAction to the UIViewController custom class, and an IBOutlet to a label. The IBAction method would change the text of the IBOutlet label. Pretty standard stuff.
But with the control embedded in a table row, I cannot make IBAction connections. I've tried making IBAction connections to the UIViewController as well as the UITableViewCell, but it doesn't seem to allow it. How should I go about setting this up? It seems to me like a pretty useful thing to do in interface design, so I think it should be possible right?
First of all, I can control-drag from the button in a UITableViewCell xib to my header files to create IBAction. You just choose connection from Outlet to Action.
On the other hand, you can specify your Button's action by code.
If you button is a property, let's say it's self.tableCellButton, add the action like:
[self.tableCellButton addTarget:self action:#selector(yourAction:) forControlEvents:UIControlEventTouchUpInside];
You can set button's action in cellForRowAtIndexPath like so :
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! YourCustomCell
cell.button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
return cell
}
func buttonClicked(button: UIButton) {
// get row index
let index = self.tableView.indexPathsForRowsInRect(button.superview!.convertRect(button.frame, toView: self.tableView))
}
I need the user to be able to reorder a UITableView by this way: he touches a cell for a predetermined period (e.g. 1 second), then he can drag and drop it over the other cells.
I know how to implement the 'long touch' detection using a gesture recognizer, but what is the best way to implement the drag and drop ability without using a reorder control (the user should drag the cell from anywhere in the cell, not only from the reorder control)?
This is an old question, but here's a solution that's tested and working with iOS 8 through 11.
In your UITableViewCell subclass try this:
class MyTableViewCell: UITableViewCell {
weak var reorderControl: UIView?
override func layoutSubviews() {
super.layoutSubviews()
// Make the cell's `contentView` as big as the entire cell.
contentView.frame = bounds
// Make the reorder control as big as the entire cell
// so you can drag from everywhere inside the cell.
reorderControl?.frame = bounds
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: false)
if !editing || reorderControl != nil {
return
}
// Find the reorder control in the cell's subviews.
for view in subviews {
let className = String(describing: type(of:view))
if className == "UITableViewCellReorderControl" {
// Remove its subviews so that they don't mess up
// your own content's appearance.
for subview in view.subviews {
subview.removeFromSuperview()
}
// Keep a weak reference to it for `layoutSubviews()`.
reorderControl = view
break
}
}
}
}
It's close to Senseful's first suggestion but the article he references no longer seems to work.
What you do, is make the reorder control and the cell's content view as big as the whole cell when it's being edited. That way you can drag from anywhere within the cell and your content takes up the entire space, as if the cell was not being edited at all.
The most important downside to this, is that you are altering the system's cell view-structure and referencing a private class (UITableViewCellReorderControl). It seems to be working properly for all latest iOS versions, but you have to make sure it's still valid every time a new OS comes out.
I solved the question of the following steps:
Attach gesture recognizer to UITableView.
Detect which cell was tapped by "long touch". At this moment create a snapshot of selected cell, put it to UIImageView and place it on the UITableView. UIImageView's coordinates should math selected cell relative to UITableView (snapshot of selected cell should overlay selected cell).
Store index of selected cell, delete selected cell and reload UITableView.
Disable scrolling for UITableView. Now you need to change frame of snapshot UIImageView when you will drag cell. You can do it in touchesMoved method.
Create new cell and reload UITableView (you already have stored index) when the user finger leaves screen.
Remove the snapshot UIImageView.
But it was not easy to do it.
The article Reordering a UITableViewCell from any touch point discusses this exact scenario.
Essentially you do the following:
Find the UITableViewCellReorderControl (a private class).
Expand it so it spans the entire cell.
Hide it.
The user will now be able to drag the cell from anywhere.
Another solution, Cookbook: Moving Table View Cells with a Long Press Gesture, achieves the same effect by doing the following:
Add a long press gesture recognizer on the table view.
Create a snapshot of the cell when the cell is dragged.
As the cell is dragged, move the snapshot around, and call the -[UITableView moveRowAtIndexPath:toIndexPath:].
When the gesture ends, hide the cell snapshot.
For future reference...
I had the same problem, I found another question(Swift - Drag And Drop TableViewCell with Long Gesture Recognizer) about it and someone suggested this tutorial: https://www.freshconsulting.com/create-drag-and-drop-uitableview-swift/
worked just perfectly for me
I know this is a question about UITableView. But I ended with a solution of using UICollectionView rather than UITableView to implement longtap reorder. Its easy and simple.
tableView.dragInteractionEnabled = true
tableView.dragDelegate = self
tableView.dropDelegate = self
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { }
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession,
at: indexPath: IndexPath) -> [UIDragItem] {
return [UIDragItem(itemProvider: NSItemProvider())]
}
func tableView(_ tableView: UITableView, dropSessionDidUpdate session:
UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
if session.localDragSession != nil {
return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
return UITableViewDropProposal(operation: .cancel, intent: .unspecified)
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
}