iOS 7 UITextView - layoutSubviews infinite loop - ios7

I have UITextView:
textView = [[UITextView alloc] init];
textView.scrollEnabled = NO;
I have a problem only on iOS7:
On every keyboard keystroke, layoutSubviews is called on superview. After writing several characters, it gets stuck in infinite loop and keeps calling layoutSubviews constantly.
When I remove textView.scrollEnabled = NO; line of code, everything works the way it is supposed to.
I would really like to disable scrolling on my text view. Does anybody have an idea how to do it?

In the end I hacked the whole thing by placing a Pan Gesture on UITextView. This way all functionalities of editing text view were preserved and scrolling was disabled.
When I wanted to disable scrolling of text view, I didn't set scrollEnabled = NO. Instead I added Pan Gesture recognizer on UITextView. When I wanted to enable scrolling, I just removed that same Pan Gesture recognizer from text view.
Like this:
init method...
_scrollDisablerGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(textViewScrollAction:)];
// method textViewScrollAction does nothing, empty implementation
[_textView addGestureRecognizer:_scrollDisablerGR];
somewhere else in code:
if (shouldEnableScrolling) {
[self.textView removeGestureRecognizer:_scrollDisablerGR];
} else {
if (!_scrollDisablerGR.view) {
[self.textView addGestureRecognizer:_scrollDisablerGR];
}
}
Works great ;)

Related

ScrollView is eating Swipe Gesture Recognizer

i have a details view controller with a scrollview on it. And i have load UILabel, UIImageView on top of UIScrollView. The scrollview is set to scroll vertically only. and the view need to be able to recognize swipe left and right to navigate to next/previous page by adding
self.leftGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeRecognizer:)];
[self.leftGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.view addGestureRecognizer:self.leftGestureRecognizer];
So when i swipe within UILabel, it is working. If i swipe start from UIScrollView, it is not working. I guess it is the UIScrollView conflict the swipe gesture.
In short, swipe gesture is only working on the subview but not UIScrollView .Does anyone have any idea on this?
UPDATE:
If i swipe start from scrollview first then end at UILabel, it won't recognize the swipe gesture. If i swipe within UILabel(start and end in the UILAbel) it is able to recognize.
Make sure that if the UIImage allows zooming, the swipe recognizer won't work, since it is trying to zoom. If this is the case, you will need to enable the zoom only in certain case. hope it helps.
Initialize the scroll view with scrolling disabled. Then you need to disable scrolling in the scroll view when the image is not zoomed in, and renabled it when it is zoomed in. This provides the expected behavior.
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
if (scrollView.zoomScale != 1.0) {
scrollView.scrollEnabled = YES;
} else {
scrollView.scrollEnabled = NO;
}
}
To enable zoom provide an image on a delegate.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return myImage;
}
Also add the gesture to instance of scrollview like this -
leftGestureRecognizer.delaysTouchesBegan = YES;
[myScrollView addGestureRecognizer:leftGestureRecognizer];
OR You can try the following -
[scrollView.panGestureRecognizer requireGestureRecognizerToFail: leftGestureRecognizer]

UIButton inside UIView doesn't respond to touch events

I've put a UIButton inside a custom UIView and the button is not receiving any touch events (it doesn't get into the highlighted state, so my problem is not about being unable to wire up a touch inside up handler). I've tried both putting it into the XIB in Interface Builder, and also tried programatically adding the UIButton into the UIView seperately, both ended with no luck. All my views are inside a UIScrollView, so I first though UIScrollView may be blocking them, so I've also added a button programatically exactly the same way I add my custom view into UIScrollView, and the button worked, elimination the possibility of UIScrollView could be the cause. My View's hiearchy is like this:
The button is over the image view, and the front layer isn't occupying my button completely, so there's no reason for me not be physically interacting with the button. At my custom view's code side, I'm creating my view as such:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
UIView *sub = [[[NSBundle mainBundle] loadNibNamed:#"ProfileView" owner:self options:nil] objectAtIndex:0];
[self addSubview:sub];
[sub setUserInteractionEnabled:YES];
[self setUserInteractionEnabled:YES];
CALayer *layer = sub.layer;
layer.masksToBounds = YES;
layer.borderWidth = 5.0;
layer.borderColor = [UIColor whiteColor].CGColor;
layer.cornerRadius = 30.0;
/*layer.shadowOffset = CGSizeZero;
layer.shadowRadius = 20.0;
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowOpacity = 0.8;
*/
}
return self;
}
I've tried all combinations of setUserInteractionsEnabled, and had no luck. (Yes, also set them to checked in Interface Builder too). I've also read in another question with a similar problem that I should try overriding 'canBecomeFirstResponder' to return 'YES' and I've also done that too. But the problem persists, I can't click the button. I've not given any special properties, settings to the button, it's just a regular one. My other objects in the view (labels below, image view behind the button etc.) are working properly without problems. What could be possibly wrong here?
Thanks,
Can.
UPDATE: Here is a quick reproduction of the problem: https://dl.dropbox.com/u/79632924/Test.zip
Try to run and click the button.
Looking at the test project, I believe your problem in the way you create TestView, you do not specify the frame for it, so basically the parent view is 0 size, and the subviews you see from XIB extending out of the parent view and thus do not get anything in responder chain.
You should either specify the frame when creating TestView, or adjust the frame after loading XIB file.
I have had this problem as well. The cause for me was that the UIButton superview frame was of height 0, so I believe that even though a touch was happening, it was not being passed down to the button.
After making sure that the button's superview took a larger rectangle as a frame the button actions worked.
The root cause for this problem on my side was a faulty auto layout implementation (I forgot to set the height constraint for the button's superview).
I've found the solution. I was initializing my custom view as:
MyView *view = [[MyView alloc] init];
I've initialized it instead with a frame of my view's size, and it started responding to events:
CGRect rect = CGRectMake(0,0,width,height);
MyView *view = [[MyView alloc] initWithFrame:rect];
Storyboard Solution
Just for anyone wanting a solution to this when using storyboards and constraints.
Add a constraint between the superview (containing the button) and the UIButton with an equal heights constraint.
In my case, I had selected embed UIButton in a UIView with no inset on the storyboard. Adding the additional height constraint between the UIButton and the superview allowed the UIButton to respond to touches.
You can confirm the issue by starting the View Debugger and visually confirm that the superview of the UIButton is not selectable.
(Xcode 11, *- Should also work in earlier versions)

UITextField -- Adding UIView for leftView - Can't get Focus

The UITextFields in my app have placeholder text defined (in Interface Builder), and I cannot cause these fields to acquire focus (i.e. show the keyboard and allow editing) when I tap on the area occupied by the placeholder text. If I tap on the textfields in an area just outside the that of placeholder text (though still within the bounds of the textfiled itself), it acts as normal (i.e. the keyboard pops up and I can edit the content of the textfield). How can I fix this?
Thanks.
EDIT 1
Ok, I think I've got it. I'm also setting a blank view to the "leftView" property of these UITextFields. If I remove this, you can touch the UITextFields in the area of the placeholder text and it reacts as expected; I need this view for the leftView though. If you change the background color of this spacer view to red, you can see that it doesn't get in the way at all, so I don't know what's going wrong.
Why does this code cause this problem?
Thanks.
+(UIView*)getTextFieldLeftSpacerViewWithBackgroundColor:(UIColor*)backgroundColor andHeight:(CGFloat)height
{
UIView *leftWrapper = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 8.0f, height)];
leftWrapper.autoresizingMask = UIViewAutoresizingNone;
[leftWrapper setOpaque:YES];
if(backgroundColor){leftWrapper.backgroundColor = backgroundColor;}
else{leftWrapper.backgroundColor = [UIColor clearColor];}
return [leftWrapper autorelease];
}
+(void)setTextFieldLeftSpacerForTextFieled:(UITextField*)textField
{
if(textField)
{
UIView *spacer = [MYViewController getTextFieldLeftSpacerViewWithBackgroundColor:nil andHeight:textField.bounds.size.height];
textField.leftView = spacer;
textField.leftViewMode = UITextFieldViewModeAlways;
}
}
Just ran into the same problem and didn't want to subclass, just had to use :
leftWrapper.userInteractionEnabled = NO;
I abandoned this approach. Instead of using an invisible view to offset the text, I opted to subclass UITextField and provide offset CGRects for the bounds of the text within theUITextField. The following SO post was very helpful:
Indent the text in a UITextField

UITableViewCell subclass, drawn in code, animate Delete button in

I'm working on a custom UITableViewCell subclass, where everything is drawn in code rather than using UILabels etc. (This is part learning exercise and partly because drawing in code is much faster. I know that for a couple of labels it wouldn't make a huge difference, but eventually I'll want to generalise this to more complex cells.)
Currently I'm struggling with the delete button animation: how to animate the cell shrinking as the delete button slides in.
Firstly, I am drawing in a custom subview of the cell's contentView. Everything is drawn in that one subview.
I am setting the subview's size by catching layoutSubviews on the cell itself, and doing:
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect b = [self.contentView bounds];
[subcontentView setFrame:b];
}
I'm doing this rather than just setting an autoresizing mask because it seemed more reliable in testing, but I can use the autoresizing mask approach in testing if needed.
Now, the default thing that happens when someone hits the minus is the view gets squished.
I can avoid that by, when setting up my cell, calling
subcontentView.contentMode = UIViewContentModeRedraw;
That gives me the correct end result (my custom view redraws with the new size, and is laid out properly, like the first image I posted), but the animation of the transition is unpleasant: it looks like the cell stretches and shrinks back to size.
I know why the animation is working like that: Core Animation doesn't ask your view to redraw for each frame, it gets it to redraw for the end position of the animation and then interpolates to find the middle bits.
Another solution is to do
subcontentView.contentMode = UIViewContentModeLeft;
That just draws the delete button over my cell, so it covers part of it.
If I also implement
- (void) didTransitionToState:(UITableViewCellStateMask)state
{
[self setNeedsDisplay];
}
then once the delete button has slid in the cell 'jumps' to the correct size. That way there's no nice slidey animation, but at least I get the correct result at the end.
I guess I could run my own animation in parallel with the delete button appearing, temporarily creating another view with a copy of the image of my view in the old size, setting mine to the new size, and fading between them — that way there would be a nice cross fade instead of a sharp jump. Anyone use such a technique?
Now, you might ask why I can't use the contentStretch property and give it a region to resize. The problem with that is I'm making something to be reasonably generic, so it's not always going to be possible. In this particular example it'd work, but a more complex cell may not.
So, my question (after all of this background) is: what do you do in this situation? Does anyone have the animating delete button working for custom drawn cells? If not, what's the best compromise?
This worked for me finally. in subclass of UITableViewCell
subDrawContentView.contentMode = UIViewContentModeLeft;
overide layout subviews method
- (void)layoutSubviews {
CGRect b = [subDrawContentView bounds];
b.size.width = (!self.showingDeleteConfirmation) ? 320 : 300;
[subDrawContentView setFrame:b];
[subDrawContentView setNeedsDisplay];
[super layoutSubviews];
}
So I will paste the code first and then I will explain:
-(void)startCancelAnimation{
[cancelButton setAlpha:0.0f];
[cancelButton setFrame:CGRectMake(320., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
cancelButton.hidden=NO;
[UIView animateWithDuration:0.4
animations:^(void){
[progressView setFrame:CGRectMake(progressView.frame.origin.x, progressView.frame.origin.y, 159.0, progressView.frame.size.height)];
[text setFrame:CGRectMake(text.frame.origin.x, text.frame.origin.y, 159.0, text.frame.size.height)];
[cancelButton setFrame:CGRectMake(244., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
[cancelButton setAlpha:1.0f];
} ];
}
-(void)stopCancelAnimation{
[UIView animateWithDuration:0.4
animations:^(void){
[cancelButton setFrame:CGRectMake(320., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
[cancelButton setAlpha:0.0f];
}completion:^(BOOL completion){
cancelButton.hidden=YES;
[cancelButton setAlpha:1.0f];
[progressView setFrame:CGRectMake(progressView.frame.origin.x, progressView.frame.origin.y, DEFAULT_WIDTH_PROGRESS, progressView.frame.size.height)];
[text setFrame:CGRectMake(text.frame.origin.x, text.frame.origin.y, DEFAULT_WIDTH_TEXT, text.frame.size.height)];
}
];
}
-(void)decideAnimation{
if([cancelButton isHidden]){
[self startCancelAnimation];
}
else{
[self stopCancelAnimation];
}
}
So what I have there is a button that looks like this:
I have an IBOutlet to it. And what I am doing is resizing a UIProgressView and a UITextField (you can resize whatever you want). As for the code is pretty simple, but if you need any help to understand what's going on, please ask. Also, don't forget to add the Swip Gesture to the UITableView... Like this:
UISwipeGestureRecognizer *gesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(didSwipe:)];
gesture.numberOfTouchesRequired=1;
gesture.direction = UISwipeGestureRecognizerDirectionRight;
[table addGestureRecognizer:gesture];
[gesture release];
Finally the method that does it all:
-(void)didSwipe:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
//Get the cell of the swipe...
CGPoint swipeLocation = [gestureRecognizer locationInView:self.table];
NSIndexPath *swipedIndexPath = [self.table indexPathForRowAtPoint:swipeLocation];
UITableViewCell* swipedCell = [self.table cellForRowAtIndexPath:swipedIndexPath];
//Make sure its a cell with content and not an empty one.
if([swipedCell isKindOfClass:[AMUploadsTableViewCell class]]){
// It will start the animation here
[(AMUploadsTableViewCell*)swipedCell decideAnimation];
// Do what you want :)
}
}
}
So as you can see the whole animation is created manually, so you can control exactly what you want. :)

UITapGestureRecognizer only working for items visible on stage at initialization

So, I iterate through a loop and create UIViews which contain UIImageViews (So that I can selectively show any given part). These UIViews are all stored in a UIScrollView.
I add gesture recognizers to the UIViews in the loop where I created them.
When I run the program, only the items initially visible within the UIScrollView have their gestures recognized. If I scroll over to previously hidden items and then tap on them, nothing happens at all (the gesture is never recognized or attempted to be).
Initialization code:
UITapGestureRecognizer* gestRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gestRec.delegate = self;
[imageholder addGestureRecognizer:gestRec];
Code that deals with the gesture:
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
float count = [self._imageHolders count];
NSLog(#"handling gesture: %f",count);
while(count--){
UIView* object = (UIView*) [self._imageHolders objectAtIndex:count];
// NSLog(#"Whats going on: %#, %#, %b",object,gestureRecognizer.view, object == gestureRecognizer.view);
if(object == gestureRecognizer.view){
object.alpha = .1;
count = 0;
}
// [object release];
}
}
Any ideas?
---- Update :
I've explored a variety of the available functions in scrollview, UIView and the gesture recognizer and have tried messing with the bounds in case something was cut off that way... Interestingly enough, if there is one item only partially visible and you move it over so it's completely visible, only the portion originally visible will recognize any gestures.
I don't know enough about how the gesture recognizer works within the UIKit architecture to understand this problem. The Apple example for a scrollview with gestures doesn't seem to have this problem, but I can't find any real differences, except that I am nesting my UIImageViews within their own UIViews
I had a similar problem and found it was caused by adding the sub-views to a top-level view then adding that top-level view to the scroll-view. The top-level view had to be sized to the same dimensions as the contentSize (not the bounds) of the scroll-view otherwise it wouldn't pass on touch events to its subviews even when they had scrolled into view.
Try to set the cancelsTouchesInView property to NO.
UITapGestureRecognizer* gestRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gestRec.delegate = self;
**gestRec.cancelsTouchesInView = NO;**
[imageholder addGestureRecognizer:gestRec];