iOS 8: -[UITableViewWrapperView textField]: unrecognized selector sent to instance - ios

Hello: I've been testing my app on iOS 6, 7, and now 8 (beta 5). My UITableView with custom UITableViewCells is working fine on 6 and 7. However, on iOS 8, I'm getting a crash when I attempt to access a subview (text field) of a cell.
I am aware of the fact that there's another view in the cell's hierarchy in iOS 7. Strangely, it appears that this isn't the case in iOS 8. Here's the code I'm using:
//Get the cell
CustomCell *cell = nil;
//NOTE: GradingTableViewCell > UITableViewCellScrollView (iOS 7+ ONLY) > UITableViewCellContentView > UIButton (sender)
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
cell = (CustomCell *)sender.superview.superview;
} else {
cell = (CustomCell *)sender.superview.superview.superview;
}
//Get the cell's index path
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
//etc.
NSLog(#"%#", cell.textField); //<---Crashes here
So, as you can see, I'm accounting for the extra view in iOS 7. After adding some breakpoints and taking a closer look at the variables, I see that cell exists, but all the subviews it has in the interface file (which are linked up) - including textField - are nil. At the line specified, I'm receiving the following crash log:
-[UITableViewWrapperView textField]: unrecognized selector sent to instance 0x12c651430
I looked into this further, and I found this:
Changing the else statement to be identical to the preceding line gets rid of the crash, and the app works fine (using sender.superview.superview like in iOS 6).
This makes no sense to me. Did Apple revert the hierarchy of UITableViewCells to that of iOS 6's, or am I missing something? Thanks!

I've encountered the same issue. Here's a more reliable method:
UITextField* textField = (UITextField*)sender;
NSIndexPath* indexPath = [self.tableView indexPathForRowAtPoint:[self.tableView convertPoint:textField.center fromView:textField.superview]];
This will work regardless of the underlying view hierarchy of UITableViewCell.

Different iOs versions have different implementations of UITableViewor UITableViewController, as an easy fix you'll have to iterate through superviews until you found the desired view class instead of relying it being the N-th superview.

I also had the same issue on iOS8 with getting UITableViewCell via superview from a child view. This is what I came up with for both iOS7 and iOS8 support.
-(void)thumbTapped:(UITapGestureRecognizer*)recognizer {
NSLog(#"Image inside a tableview cell is tapped.");
UIImageView *mediaThumb = (UIImageView*)recognizer.view;
UITableViewCell* cell;
// get the index of container cell's row
// bug with ios7 vs ios8 with superview level!! :(
/*** For your situation the level of superview will depend on the design structure ***/
// The rule of thumb is for ios7 it need an extra superview
if (SYSTEM_VERSION_LESS_THAN(#"8.0")) { // iOS 7
cell = (UITableViewCell*)mediaThumb.superview.superview.superview.superview;
} else { // iOS 8
cell = (UITableViewCell*)mediaThumb.superview.superview.superview;
}
}
Just to clarify, in cellForRowAtIndexPath I assigned the tap gesture recognizer. The original design is very complex in Storyboard with many subviews, buttons.. etc.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MediaCell" forIndexPath:indexPath];
.......
UIImageView *mediaImage = ............
.....................................
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(thumbTapped:)];
[mediaImage addGestureRecognizer:tapGestureRecognizer];
return cell;
}

Related

Correct way to setting a tag to all cells in TableView

I'm using a button inside a tableView in which I get the indexPath.row when is pressed. But it only works fine when the cells can be displayed in the screen without scroll.
Once the tableView can be scrolleable and I scrolls throught the tableview, the indexPath.row returned is a wrong value, I noticed that initially setting 20 objects, for example Check is just printed 9 times no 20.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
lBtnWithAction = [[UIButton alloc] initWithFrame:CGRectMake(liLight1Xcord + 23, 10, liLight1Width + 5, liLight1Height + 25)];
lBtnWithAction.tag = ROW_BUTTON_ACTION;
lBtnWithAction.titleLabel.font = luiFontCheckmark;
lBtnWithAction.tintColor = [UIColor blackColor];
lBtnWithAction.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[cell.contentView addSubview:lBtnWithAction];
}
else
{
lBtnWithAction = (UIButton *)[cell.contentView viewWithTag:ROW_BUTTON_ACTION];
}
//Set the tag
lBtnWithAction.tag = indexPath.row;
//Add the click event to the button inside a row
[lBtnWithAction addTarget:self action:#selector(rowButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
//This is printed just 9 times (the the number of cells that are initially displayed in the screen with no scroll), when scrolling the other ones are printed
NSLog(#"Check: %li", (long)indexPath.row);
return cell;
}
To do something with the clicked index:
-(void)rowButtonClicked:(UIButton*)sender
{
NSLog(#"Pressed: %li", (long)sender.tag);
}
Constants.h
#define ROW_BUTTON_ACTION 9
What is the correct way to get the indexPath.row inside rowButtonClicked or setting a tag when I have a lot of of cells in my tableView?
My solution to this kind of problem is not to use a tag in this way at all. It's a complete misuse of tags (in my opinion), and is likely to cause trouble down the road (as you've discovered), because cells are reused.
Typically, the problem being solved is this: A piece of interface in a cell is interacted with by the user (e.g. a button is tapped), and now we want to know what row that cell currently corresponds to so that we can respond with respect to the corresponding data model.
The way I solve this in my apps is, when the button is tapped or whatever and I receive a control event or delegate event from it, to walk up the view hierarchy from that piece of the interface (the button or whatever) until I come to the cell, and then call the table view's indexPath(for:), which takes a cell and returns the corresponding index path. The control event or delegate event always includes the interface object as a parameter, so it is easy to get from that to the cell and from there to the row.
Thus, for example:
UIView* v = // sender, the interface object
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
UITableViewCell* cell = (UITableViewCell*)v;
NSIndexPath* ip = [self.tableView indexPathForCell:cell];
// and now we know the row (ip.row)
[NOTE A possible alternative would be to use a custom cell subclass in which you have a special property where you store the row in cellForRowAt. But this seems to me completely unnecessary, seeing as indexPath(for:) gives you exactly that same information! On the other hand, there is no indexPath(for:) for a header/footer, so in that case I do use a custom subclass that stores the section number, as in this example (see the implementation of viewForHeaderInSection).]
I agree with #matt that this is not a good use of tags, but disagree with him slightly about the solution. Instead of walking up the button's superviews until you find a cell, I prefer to get the button's origin, convert it to table view coordinates, and then ask the table view for the indexPath of the cell that contains those coordinates.
I wish Apple would add a function indexPathForView(_:) to UITableView. It's a common need, and easy to implement. To that end, here is a simple extension to UITableView that lets you ask a table view for the indexPath of any view that lies inside one of the tableView's cells.
Below is the key code for the extension, in both Objective-C and Swift. There is a working project on GitHub called TableViewExtension-Obj-C that illustrates the uses of the table view extension below.
EDIT
In Objective-C:
Header file UITableView_indexPathForView.h:
#import <UIKit/UIKit.h>
#interface UIView (indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view;
#end
UITableView_indexPathForView.m file:
#import "UITableView_indexPathForView.h"
#implementation UITableView (UITableView_indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view {
CGPoint origin = view.bounds.origin;
CGPoint viewOrigin = [self convertPoint: origin fromView: view];
return [self indexPathForRowAtPoint: viewOrigin];
}
And the IBAction on the button:
- (void) buttonTapped: (UIButton *) sender {
NSIndexPath *indexPath = [self.tableView indexPathForView: sender];
NSLog(#"Button tapped at indexpPath [%ld-%ld]",
(long)indexPath.section,
(long)indexPath.row);
}
In Swift:
import UIKit
public extension UITableView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let origin = view.bounds.origin
let viewOrigin = self.convert(origin, from: view)
let indexPath = self.indexPathForRow(at: viewOrigin)
return indexPath
}
}
I added this as a file "UITableView+indexPathForView" to a test project to make sure I got everything correct. Then in the IBAction for a button that is inside a cell:
func buttonTapped(_ button: UIButton) {
let indexPath = self.tableView.indexPathForView(button)
print("Button tapped at indexPath \(indexPath)")
}
I made the extension work on any UIView, not just buttons, so that it's more general-purpose.
The nice thing about this extension is that you can drop it into any project and it adds the new indexPathForView(_:) function to all your table views without having do change your other code at all.
You are running into the issue of cell-reuse.
When you create a button for the view you set a tag to it, but then you override this tag to set the row number to it.
When the cell get's reused, because the row number is longer ROW_BUTTON_ACTION, you don't reset the tag to the correct row number and things go wrong.
Using a tag to get information out of a view is almost always a bad idea and is quite brittle, as you can see here.
As Matt has already said, walking the hierarchy is a better idea.
Also, your method doesn't need to be written in this way. If you create your own custom cell, then the code you use to create and add buttons and tags isn't needed, you can do it in a xib, a storyboard, or even in code in the class. Furthermore, if you use the dequeue method that takes the index path, you will always get either a recycled cell, or a newly created cell, so there is no need to check that the cell returned is not nil.

Subviews of UITableViewCell seem to randomly disappear after dequeueReusableCellWithIdentifier

In my iOS 7.0 App:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
// AttemptCell is a prototype cell, currently using the "Right Detail" preset
// style and the little information accessory.
static NSString *CellIdentifier = #"AttemptCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
assert(cell != Nil);
if (cell.contentView.subviews.count == 2)
{
UILabel *attemptLabel = (UILabel*)cell.contentView.subviews[0];
attemptLabel.text = attempt.attempt;
UILabel *analysisLabel = (UILabel*)cell.contentView.subviews[1];
analysisLabel.text = [attempt analysis];
cell.tag = indexPath.row;
}
else
{
// Something has gone very wrong.
UILabel *attemptLabel = (UILabel*)cell.contentView.subviews[0];
attemptLabel.text = #"Error";
}
The question is why does the (UILabel*)cell.contentView.subviews[1] sometimes disappear causing the error block to be entered.
This table view shows one custom keyboard entry cell (UITextField) which always appears last. The keyboard entry cell is also prototyped, but with a different dequeue cell identifier. The problem is randomly seen when the keyboard pops up and is closed. Keyboard popping up causes some AttemptCells to go out of view and closing the keyboard causes the AttemptCells to come back into view.
What you are doing is wrong. Don't rely on the view hierarchy of a private class, certainly don't depend on the number of views in a hierarchy and really don't depend on a view being in a certain position of the sub views array. Your error block may not be entered because a sub view has "disappeared" - an extra view could have been added, all you're checking for is that the count of the sub views is equal to 2.
If you're using one of the standard cell layouts, use the textLabel and detailTextLabel properties. If you're using a subclass, use outlets.

scrollToRowAtIndexPath stopped working in iOS 7 - ?UITableViewCellScrollView? Change in UITableViewCell hierarchy

I upgraded to Xcode 5 and my app to target iOS 7 and my app no longer responded to scrollToIndexPath to scroll the appropiate tableviewcell out of the way of the keyboard
My set up is i had a custom tableview cell, with textfield on it, and was using it to generate multiple tableview cells to display and edit parts of an address.
Tapping on a textfield on a cell triggers
- (void)textFieldDidBeginEditing:(UITextField *)textField
And the code to make it scroll to the top , out of the way of the keyboard was as follows
UITableViewCell *cell = (UITableViewCell*) textField.superview.superview;
[self scrollToRowAtIndexPath:[self indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
This worked fine in iOS 6 but doesn't work in iOS 7. It also does not generate any error.
The solution here is a simple one
It appears that there has been a change in the hierarchy of tableviewCells
So adding an extra .superview
UITableViewCell *cell = (UITableViewCell*) textField.superview.superview;
Becomes
UITableViewCell *cell = (UITableViewCell*) textField.superview.superview.superview;
and it works targeting iOS 7.
The textField.superview.superview used to be an object of class UITableViewCell (or more specifically my custom UITableViewCell)
Now it's an object of class UITableViewCellScrollView and you need to get that its superview.
UITableViewCellScrollView is a private subclass that apparently enables the the slide left to reveal the delete button
After working this out i found this blogpost which illustrates it nicely
Hope this stops a few of you pulling your hair out :)
Simon
As the previous answer said, the view hierarchy of a UITableViewCell changed in iOS 7. Instead of using:
UITableViewCell *cell = (UITableViewCell*) textField.superview.superview;
Define the following method:
- (UITableViewCell *)cellForSubview:(UIView *)subview
{
UIView *view = subview;
while (view != nil && ![view isKindOfClass:[UITableViewCell class]]) {
view = [view superview];
}
return view;
}
Then use it as follows:
UITableViewCell *cell = [self cellForSubview:textField];
This should work in iOS 6 and 7.

iOS7's subview trimmed if out of parent view bounds [duplicate]

This question already has answers here:
How to stop UITableView from clipping UITableViewCell contents in iOS 7
(2 answers)
Closed 6 years ago.
I've rumbled through new UI info from apple - didn't help.
Now let the code and the screenshots show you the problem i've ran into.
To ensure that is not my buggy code, i've created a new project, with a single file - a UIViewController that has a tableView inside id. the delegates are set.
I do the following:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"UITableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [NSString stringWithFormat:#"%d",indexPath.row];
// Configure the cell...
UIView * redView = [[UIView alloc] initWithFrame:CGRectMake(0, -10, 100, 20)];
redView.backgroundColor = [UIColor redColor];
[cell addSubview:redView];
return cell;
}
The table view is set on Grouped.
Lets run it on iOS 6:
Duh ofcourse, the Y origin is negative!
Yes, it is, and this is the result I am trying to achieve.
Lets see what it shows on iOS 7:
Hint: that doesn't occur if we add redView to a normal UIView.
Another hint: if i set tableView's background color blue, the gray lines between sections would be blue, not gray (as a demonstration that the gray is not a set header).
Another hint: same goes for ipad.
Why in iOS 7 it cuts everything that goes out of bounds? Please help!
It's because iOS 7 introduced some changes to the view hierarchy of UITableViewCells.
It used to be UITableViewCell view -> contentView.
Now it's more like UITableViewCell view -> scrollView -> contentView.
The solution is to set clipsToBounds = NO on the scrollView (which is set to YES by default). And the way to achieve that is through the superview property.
So basically in iOS6 and prior, to allow content to spill out of the cell bounds, you would do:
self.clipsToBounds = NO; //cell's view
self.contentView.clipsToBounds = NO; //contentView
In iOS7 you have to also prevent the scrollview from not clipping so you'd do something like:
self.clipsToBounds = NO; //cell's view
self.contentView.clipsToBounds = NO; //contentView
self.contentView.superview.clipsToBounds = NO; //scrollView
And the backwards compatible solution I use is:
self.clipsToBounds = NO;
self.contentView.clipsToBounds = NO;
if ([self.contentView.superview isKindOfClass:[NSClassFromString(#"UITableViewCellScrollView") class]]) self.contentView.superview.clipsToBounds = NO;
Keep in mind this is Hacky™ and if the view hierarchy changes again in iOS 8, you might be in trouble. Unfortunately it seems Apple doesn't want us to spill content out of UITableViewCells so AFAIK this is the only workable solution.
The following will fix it:
cell.layer.masksToBounds = NO
However, it will probably break something else, e.g. cell animations.
The problem you are having is caused by the fact that cells just don't support drawing content out of their bounds (actually, using subviews that extend the bounds of their superview is always a hacky solution).
The best advice is to redesign and avoid such funcionality at all.
Another solution could be to add the same view to the bottom of the previous cell or just add them as a subview of UITableView directly.
I don't think that the contents are actually trimmed in a masksToBounds-sense, but instead that the views are covered by the other (opaque) cells. You could try to fix it by changing the order ("z-Index") of the UITableView's subviews, using bringSubviewToFront: and similar methods whenever a cell appears to ensure that the cell closest to the bottom edge of the screen is the frontmost view of all the cells.

UILabel textColor doesn't set properly in iOS 4.3

While working with iOS 5.1 Simulator app felt great, but after testing it on iOS 4.3 Simulator some sort of unintelligible issue appeared:
I use custom UITableViewCell for UITableView, and it has few UILabels and one UITextView. The problem is that textColor property seems not to work at all - all the text on my labels remain being invisible despite the background is shown and NSLog prints right text. I tried changing textColor value both in IB and programmatically but neither helped.
What about UITextView - it's ok. I guess, I could substitute all the labels with textViews, but it's not the good way of programming, is it?
I'll be thankful no end for any kind of your help because it just blows my mind away!
Update (source code added)
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FeedItemCell *cell = [tableView dequeueReusableCellWithIdentifier:[FeedItemCell cellID]];
if (!cell) {
cell = [FeedItemCell cell];
}
NSIndexPath *selectedPath = [tableView indexPathForSelectedRow];
if (selectedPath && (indexPath.row == selectedPath.row) ) {
[self selectRowAtIndexPath:indexPath];
}
[cell fillWithContents:[feedItemsController.fetchedObjects objectAtIndex:indexPath.row]];
return cell;
}
//---
#implementation FeedItemCell
- (void)fillWithNewsItemContents:(FeedItem *)feedItem {
self.nameLabel.text = [feedItem.creator fullName];
self.dateLabel.text = [TimeFormatter newsTimeWithDate:feedItem.postedDate];
self.detailTextView.text = [FeedItemCell detailTextViewStringFromFeedItem:feedItem];
self.commentLabel.text = [self _commentsTitleWithNews:feedItem];
self.avatarImageView.image = [feedItem.creator avatar];
}
All the UILabels are defined in xib-file
It seems like I found the solution. For some reason UILabel doesn't support Helvetica Neue font in iOS 4.3 (in spite of fact that UITextView does).
I just noted it adding other test UILabels and, after success, trying to find difference between new and old ones.
Thus now it takes only to choose another suitable font :)
Thank you all very much and sorry for troubling without worth reason.
This is very weird. But i will suggest you to test on actual iOS 4.3 and 5.1 device. Simulators are 95% accurate not 100 %. So you should do a sanity check on device.
It would be really great to see your code – particularly, I'd like to see:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
Or wherever else you allocate, instantiate and add as a subview these misbehaving UILabels. But I can make a couple guesses as to what might be going wrong:
You might be trying to set elements of the UILabel before it is properly allocated or instantiated. This can happen if, for example, you call:
[myLabel setText:#"Hello"];
before you call:
UILabel *myLabel = [[UILabel alloc] initWithFrame:someFrame];
If you do something like that, it will compile fine and fail silently at runtime.
You might be forgetting to add the label to the proper UIView – in a UITableViewCell, the contents are should typically be added as subviews of the cell's contentView property:
[[myCell contentView] addSubview:myLabel];
There are some other things that could be happening, but I'd rather see the code you're using before making any blind guesses. I'll update my answer then.

Resources