I have got a TableView and each cell is covered by a Text View.
I think that because of this reason I can't select any cell.
Whenever I try to print it I don't get anything:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var i = Int()
i = indexPath.row
print("i=\(i)")
}
**Its print and not println because of Swift 2.0.
Please ensure following:
You are correctly setting your view controller as delegate of the table view.
Your touches are intercepted by text view and are not reaching underneath cell. To fix this set userInteractionEnabled to false in cellForRowAtIndexPath: and set it back to true in didSelectRowAtIndexPath:.
Take a look at UITextView's userInteractionEnabled, multipleTouchEnabled, and exclusiveTouch. Those properties alter whether or not a view receives touches; if it doesn't, the touches get passed to the view behind it (or at least I think that's how it works). I forget where it is, but there's also a method that's called to ask a view which view should be the target of a specific touch, which would allow you to explicitly tell it to send them to the underlying view.
However, from a design perspective, I would re-evaluate having a UITextView on top of a UITableViewCell - the text view is a scroll view and the cell is in a scroll view, so they will always conflict. UILabel is generally more appropriate for putting in a text view.
For those who are using storyboard just uncheck User Interaction Enabled and Multiple Touch as showing below in the screenshot:
I have listed below points for your solution.
you need to check delegate is connected to your view controller.
next you need to check table view selection is enabled for your table view you can check it from table view attributed inspector.
next make sure user interaction is enabled for your text view.
Hope it will be the right solution for you.
Related
I have some customised UITableViewCells which contain a UITextView and a set of buttons. The buttons in the cell should be visible only when the user tries to edit the text view in the corresponding cell.
So, if the user attempts to edit the textview in cell1, then the set of buttons should be visible below the textview in cell1 and the height of cell1 should also be increased. Now, if the user attempts to edit the textview in cell2, then the set of buttons should be visible below the textview in cell2 and the height of cell2 should also be increased, whereas the buttons in cell1 should get removed and cell size needs to be calculated accordingly.
For this I tried to reload the table view cells from textViewDidBeginEditing:. This is reloading the cells properly and shows/hides the buttons properly in the required cells, but does not allow proper editing of text view. When the user tries to edit with the textview, the tableview reload methods are invoked constantly and not allowing the keyboard to stand for editing.
Is it right to handle reloading from textViewDidBeginEditing: in first place ? is there some better way to do this ? please help.
First I would suggest that do not reload the entire TableView each time. Instead use the
reloadRowsAtIndexPaths
method to load only the cell in which you want modifications.
Next, to solve your issue regarding the textView, you could do something like this, declare a class property of bool type, and set it to false. When you reload your cell for the first time, set it to true. Now in the textViewDidBegin editing method, check for this bool. If it is set to true, that means you already have loaded the cell and you do not need to load it again, so in this case do not call the reloadRows method. Else if it is false, reload the rows and set this bool to true.
Now in the textViewDidEndEditing delegate method, set this bool to false again so that when a user taps on another textView in some other row, it is reloaded properly.
This logic may not be perfect, you may require some tweaking. But it will get the job done
Explicitly make the textfield as firstResponder
if buttonsDisplayed == NO {
reload cell
}
if textFieldIsFirstResponder == NO {
[textField becomeFirstResponder];
}
I'm adding Accessibility support to my iOS app and I'm having trouble with a collection view in one of my table view cells.
For example, when the user scrolls (horizontally) from the first cell to the second cell, Accessibility still reads the contents of the first cell. If I try to tap on a view in the second cell, it highlights an empty space to the left of the second cell (where the first cell would be but no longer visible) and reads the contents of the first cell.
When the collection view is not in a table view cell (i.e. a subview of a UIView), this does not happen.
I'm suspecting this has something to do with calling UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification) and I've tried calling it in many different places, but nothing has worked.
The following two screenshots show a collection view inside a UIView. Accessibility is enabled, so it gets selected with a black border when tapped.
When user taps first cell, it will get selected.
When the user taps 'Next', goes to the second cell, and taps the cell, the new cell will get selected.
The next two screenshots show the collection view inside a table view cell.
When the user taps the first cell, it gets selected and VoiceOver properly reads "I'm label 0".
However, when the user taps 'Next', goes to the next cell, and taps the second cell, it does not get selected and VoiceOver will still read, "I'm label 0".
The code is available here on github.
I had simillar problem which I eventually resolved.
I think that you have mismatched elements with
isAccessibilityElement = true
over each other.
I have a table view which scrolls vertically and each cell contains a title and collection view which scrolls horizontally.
I set isAccessibilityElement to true only on title and collection view cells, false on the rest.
Then, I subclassed UICollectionView and overrode the following NSObject methods:
func accessibilityElementCount() -> Int
func accessibilityElement(at: Int) -> Any?
func index(ofAccessibilityElement element: Any) -> Int
It basically just tells the voice over that your collection view has these accessible elements. The collection view itself is not accessible which is not a problem, the contrary. You could probably use
open var accessibilityElements: [Any]?
instead.
Here is some more reading from documentation (UIAccessibility.h):
UIAccessibilityContainer methods can be overridden to vend individual elements that are managed by a single UIView.
For example, a single UIView might draw several items that (to an end user) have separate meaning and functionality. It is important to vend each item as an individual accessibility element.
Sub-elements of a container that are not represented by concrete UIView
instances (perhaps painted text or icons) can be represented using instances of UIAccessibilityElement class (see UIAccessibilityElement.h).
Accessibility containers MUST return NO to -isAccessibilityElement.
To allow nice 3-finger voice over scroll you probably want to override
func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool
as well and scroll your collection view accordingly.
You may want to try this:
let nextCell = collectionView.cellForItemAtIndexPath(nextIndexPath)
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
argument: nextCell)
in your onNextButtonTapped method, after your call to scrollToItemAtIndexPath.
This will focus Accessibility on the next collection view cell.
Here's a dynamic cell
Note - in the example, the text is not data driven. It's just some text local to the cell (consider, say, a help text). At runtime, change the .text of the UILabel from one word to many lines, using a button actually inside the cell. iOS perectly resizes the cell and table....
... but only when the cell is scrolled offscreen, and then on again.
How to alert the table view to recalculate everything "now" ?
(Please note, this question ONLY in the case of iOS8+, Xcode7+, autolayout for dynamic cell heights.)
Changing height
So basically, there are two ways to do:
The first one is to actually reload the cell (not the tableview). Reloading will call new heightForRow (don't forget to purge cache, if you are caching the sizes), which will return proper new height:
let indexPaths = [NSIndexPath(forRow: ~the rows in question~, inSection: 0)]
self.table.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: .Automatic)
(Note however that this often involves reloading more than one row; notably if you select/deselect, you have to reload all rows changed.)
If however you ONLY want to change the size of the cell and the content per se, and did not really change the data content ... so for example:
you clicked some button and you assigned new local text in the cell to outlets (perhaps a help text):
you changed only the LAYOUT of the cell. for example, you made a font larger, or changed the margin of a block of text so that the height of a block of text changed, so indeed the height of the overall cell changed:
In that case instead of reloading, just call the following, which forces the tableview to basically do all animations, and for that it needs new heights, so it requests it:
self.table.beginUpdates()
self.table.endUpdates()
The true solution
I see what your problem is. You are trying to change the height of the cell from the actual cell - but you will not succeed in that -> and you should not. See, the cell is view, and view should not have any idea about its data whatsoever - view is what presents. If you need any changes, you should inform your controller to do so. To do that, you can use notifications, but preferably protocols / delegates.
So at first you create protocol in your cell, which will be used to inform the controller, that there is a change:
protocol MyCellDelegate {
func buttonTappedForCell(cell : UITableViewCell)
}
Now, you need to conform to that protocol in your view controller that contains table:
class MyClassWithTableView : MyCellDelegate
Lastly, you need to declare delegate in the cell:
class MyCell {
var delegate : MyCellDelegate
}
And assign it in the configuration of the cell, which you probably have in the view controller:
cell.delegate = self
This is the basic setup for all the delegates / protocols really, and now, when you click on your button, you can forward the action to your controller:
#IBAction myButtonTouchUpInside() {
self.delegate.buttonTappedForCell(self)
}
After doing all that, proceed as in part 1. That is to say, either reloadRowsAtIndexPaths or a beginUpdates / endUpdates pair as explained above.
Hope it helps!
I'm presuming you're not setting the text property of the UILabel inside cellForRowAtIndexPath but rather somewhere else (or doing it asynchronously). If that's the case, I wouldn't update the UI there. Rather, I'd update the model backing the table and then call reloadRowsAtIndexPaths. That will let cellForRowAtIndexPath call again, but unlike reloading the whole table, this will gracefully keep the contentOffset of the tableview right where it is.
I know this all sounds unnecessarily complicated, but the bottom line is that you don't own this view, the table view does. It has to do all sorts of stuff above and beyond updating the cell. I.e., if the cell grew, figure out which cells scrolled out of view and dequeue them. If the cell shrunk, figure out which cells scrolled into view as a result.
It's a surprisingly complex dance. You can try calling setNeedsLayout on the cell, but I wouldn't expect that to work (and even if it does, it is a fragile approach). The table view is responsible for managing its cells, so if you really should just update model and reload that one cell.
did you try calling reloadRowsAtIndexPaths on the cell index? it's supposed to animate to the new size, if the constraints are setup correctly.
You should call self.tableView.reloadData() just AFTER you made the cell's label's text change.
It will force the tableView to redraw the cell's. That's what happened when you scroll, the cell is being reused, and redrawn when it comes back again.
EDIT:
If you can't or won't do a reloadData on your tableView, you can use:
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(row:0 section:0)] withRowAnimation:UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
I dont know your code but did you really execute your ui changes on the main thread. Same problem happened to me and was solved with putting the exectuion on the main thread.
dispatch_async(dispatch_get_main_queue()) { () -> Void in
[...]
}
I have a table view with a lot of cells and not every cell is visible on a screen. When I check with
table.cells.staticTexts.matchingIdentifier("My Cell").element.exists
It returns true but the cell is not visible on the screen and I cannot tap on it. Because whenever I tap on it, a test fails.
How to check if an element is visible on a screen? Or how to tap on an element that is not visible?
Use the hittable property instead of exists.
The class reference for XCUIElement explains that the hittable property will only return true if the element can be touched.
table.cells.staticTexts.matchingIdentifier("My Cell").element.hittable
Instead of using element.exists try using element.hittable. This has worked for me, exists returns true if the element is currently in display hierarchy even though it is outside the screen view. hittable returns true only if the element is on screen and its hittable.
The tableview method cellForRowAtIndexPath (NOT the delegate method of the same name) will return the cell at some index path if it is currently being displayed, or nil if it is not displayed.
If the user clicks on a button (or if anything happens with any view in your cell), you can also go up the view hierarchy to first find the cell, then the table view, and the method indexPathForCell will give you the index path of the cell.
you can use swipeUp method to scroll down until specific cell will be visible. You can also check that cell exist or not. something like this.
XCUIElementQuery *tableQuery = app.tables;
if (!tablesQuery.cells.staticText[#"some text"].exist){
[tablesQuery.staticTexts[#"visible cell text"] swipeUp];
}
I have a UITableViewController, a bunch of sections and rows, and for each row I added a UITextField as a subview, right aligned in the row itself.
If the users taps on the row, I locally save the indexPath, make the corresponding text field become the first responder and finally, when the keyboard appears, I make the table view scroll so that the row remains visible.
I am facing the problem to obtain the same behaviour when the user taps the text field instead. In this case the didSelectRowAtIndexPath: method isn't called, so I do not know how to tell the table view to scroll to make sure that the "selected" row is still visible.
Probably the whole process is not correct. Do you know a way to solve it?
Thanks a lot!
I'm not absolutely sure about this, but...
Set the userInteractionEnabled property of the UITextField to NO. This way, the touch goes "through" the control, tapping the UITableViewCell. When didSelectRowAtIndexPath: is called, set the userInteractionEnabled property of the UITextField to YES. When the editing is complete, change it back to NO.