I've got a strange bug whereby two UIButtons inside a UIView, which is in turn inside a UIScrollView view are not clickable on iOS 5, but work perfectly fine on iOS 6 (screenshot shows scroller and map underneath)
The only other detail is the scroller view 'slides up' to reveal buttons when a station is selected. I've tried selecting the buttons on iOS 5, and they do get hit (visually), but the event isn't fired.
Edit: If I tap and hold on the button in the simulator, then move the cursor up the screen and release (e.g. to the part of the UIView that was always visible), the event fires.
The scroll view itself has the following settings, the latter two have only been added in order to try and make the buttons work on iOS 5 (tried various combinations):
self.scrollView.clipsToBounds = YES;
self.scrollView.scrollEnabled = YES;
self.scrollView.pagingEnabled = YES;
self.scrollView.delaysContentTouches = NO;
self.scrollView.canCancelContentTouches = NO;
The button events are all wired up correctly, with suitable targets etc:
[_updatePriceButton addTarget:self action:#selector(showPriceUpdateBoxWithPrice:) forControlEvents:UIControlEventTouchUpInside];
[_stationDetailsButton addTarget:self action:#selector(stationDetailsSelected:) forControlEvents:UIControlEventTouchUpInside];
And handlers (here's one as a sample):
- (void)stationDetailsSelected:(id)sender {
if ([self.delegate respondsToSelector:#selector(stationDetailsSelected:)]) {
[self.delegate stationDetailsSelected:_station];
}
}
Any help would be great!
Found the culprit - a misconfigured UITapGestureRecognizer on the UIView the UIButtons sit on:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(stationInfoViewTapped:)];
[infoController.view addGestureRecognizer:tap];
Simply adding the following after init of 'tap' solves the issue in iOS 5:
[tap setCancelsTouchesInView:NO];
Related
I am developing an application in which I have a horizontal scroller.
The scroller area comprise of different colors [which are nothing but UIImageView of colors].
Above the scroller area is a canvas [which is also a UIImageView].
Now, what I want is that when I select any color in the scroller area, it should set on the canvas.
It is pretty much like a color picker but I am already providing solid color options to be selected by the user.
How should I go about it ? Any sample Code to get me started or my mind kicking would be awesome ?
Thanx a ton
To implement it easily.
Use UIView not UIImageView
Set UIView backgroundColor and place it on the scroll
Set UITapGestureRecognizer on views,Refer here
when a purticular view is touched you have its instance of view which
is touched
get the background color of the view and set it in the scroller
Can you take UIButtons instead of UIImageViews inside your scrollView to show colours to be picked.
This will make the implementation easier. User will select any colour button and you will receive a callback in buttons action selector method. Then based on button tag or any property you can set the colour to your canvas.
In current implementation it will be tricky as you need to know the exact content offset where the tap is done to make out which UIImageView was pressed.
coding steps:
In your scroll view add UIButtons instead of UIImageView like:
UIButton* button = [[UIButton alloc] initWithFrame:someRect];
//put some tag to button
button.tag = someInt;
//add selector to each button to get callback
[view addTarget:self action:#selector(btnSelected:) forControlEvents:UIControlEventTouchUpInside];
[scrollView addSubview:button];
In the btnSelected method put this code:
-(IBAction)btnSelected:(id)sender;
{
UIButton* button = (UIButton*) sender;
if (button.tag == someInt) {
//do something like
canvas.backgroundColor = button.backgroundColor;
}
}
Instead of using UIImageViews or UIViews as suggested, I would put custom UIButtons for picking colors.
UIButton * colorButton;
//You will probably do these inside a for loop
colorButton = [UIButton buttonWithType:UIButtonTypeCustom];
[colorButton setFrame:CGRectMake(X, Y, buttonSize, buttonSize)];
[colorButton setTitle:#"" forState:UIControlStateNormal];
[colorButton setBackgroundColor:SOME_COLOR];
//Border visualization would be good if you are putting buttons side-by-side
[colorButton.layer setBorderWidth:1.0];
[colorButton.layer setBorderColor:[[UIColor darkGrayColor] CGColor]];
//You can use this tag to identify different buttons later
[colorButton setTag:INDEX+SOME_OFFSET]; //use loop index with some offset
[colorButton addTarget:self action:#selector(colorButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
-(void) colorButtonPressed:(UIButton *) sender
{
UIColor *selectedColor = sender.backgroundColor; //Or identify with sender.tag
[yourCanvas doSmtOnCanvas:selectedColor];
}
Initialize UIImageView then add gesture recognizer to all imageViews by calling this method.
- (void) setupImageView : (UIImageView *) imageView{
[imageView setUserInteractionEnabled:YES];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(changeUIImageViewBackgroundColor:)];
tapGestureRecognizer.numberOfTapsRequired = 1;
[imageView addGestureRecognizer:tapGestureRecognizer];
}
Now change color of canvasView within the selector by following way:
- (void) changeUIImageViewBackgroundColor : (UITapGestureRecognizer *) recognizer{
UIImageView *imageView = (UIImageView *)[recognizer view];
[canvasView setBackgroundColor:[imageView backgroundColor]];
}
You can put a tap gesture in your scrollview and whenever a tap is made in the scroll check whether the tap is on the imageview. (You will get whether the tap point in on imageview or not) and according you can change the image.
Second alternative is to take a custom button and handle on its click.
Hope it helps
For the past four hours, I have tried many Stack Overlow solutions but none have helped solve my problem.
Here it is,
I have a UIScrollView
Within that ScrollView there is one custom UILabel and 8 custom UIImageViews
I want to detect a long press
Something like this works
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDidFire:)];
longPress.minimumPressDuration = 0.5;
[scroll addGestureRecognizer:longPress]; //scroll defined elsewhere
However, if I replace the scroll with any subviews of scroll, the long press event never fires.
How do I detect a long press of a subview of a scroll view?
This is quite a messy hack, however, since I can detect long presses of a scroll view, is there any way where I can detect the
position of the press so that I can see which specific subview is
being pressed.
Also, (insert subview here).userInteractionEnabled = YES, I set this property for all my subviews of the scroll view, so this should not be a problem.
I have also tried using touchesBegan and touchesEnded method as suggested elsewhere in Stack Overflow.
Also, for the image views, I do set a new UILongPressGestureRecognizer for every custom image view, using a for loop, as I am aware of the 1 view per gesture recognizer rule.
From A First Time iOS Developer,
Graham
P.S. I'd really prefer if I could find a solution for 1. rather than the messy 2.
More Code As Requested:
In the Init of the view Controller
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDidFire:)];
longPress.minimumPressDuration = 0.5;
[self.titleLabel addGestureRecognizer:longPress]; //titleLabel property initialized elsewhere
[mainView addSubview:self.titleLabel];
In a "load images" method
for (NSData * dataImg in results) {
//Does some work turning data into an image, into an image view called img
img.delegate = self;
img.userInteractionEnabled = YES;
UILongPressGestureRecognizer *aLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDidFire:)];
aLongPress.minimumPressDuration = 0.5;
[img addGestureRecognizer:aLongPress];
[imgContainer addSubview:img];
}
Even More Code + Notes
self.view (UIView)
->scroll (UIScrollView)
->->mainView (UIView)
->->->titleLabel (UILabel)
->->->imgContainer (UIView)
->->->->images (UIImageViews)
[self.view addSubview:scroll];
[scroll addSubview:mainView];
[mainView addSubview:self.titleLabel];
[mainView addSubview:imgContainer];
[imgContainer addSubview:img]; //done 8x via for loop
Thanks to #RegularExpression's answer, I now am aware that the mainView is getting pressed, but not its subviews, so I need to find a way to display the mainView's subviews above it. :)
Another update, titleLabel works. ImageViews still don't work though. :(
I know this is a bit late and an answer has been chosen, but in case someone else wants a nice simple solution if you've got iOS7.
Inside your delegate of the UILongPressGestureRecognizer implement the gestureRecognizer:shouldRequireFailureOfGestureRecognizer:otherGestureRecognizer selector
Check if otherGestureRecognizer is a UIPanGestureRecognizer and return YES, otherwise return NO
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
return YES;
}
return NO;
}
The scroll view will actually produce a UIScrollViewPanGestureRecognizer, which is part of the private API, but it's a subclass of UIPanGestureRecognizer so the above works fine.
To support iOS6 or below, then you'll need to loop through the gestureRecognizers of the UIScrollView, detect which one is a UIPanGestureRecognizer and perform the requireGestureRecognizerToFail selector on your UILongPressGestureRecogizer with that.
your code seems to be fine,it should work i think.i used below code and its working fine for me.
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
longPress.delegate = (id)self;
longPress.minimumPressDuration=0.05;
imageView.userInteractionEnabled = YES;
[imageView addGestureRecognizer:longPress];
and its method,
- (IBAction)handleLongPress:(UILongPressGestureRecognizer *)sender {
NSLog(#"detected");
if (sender.state == UIGestureRecognizerStateEnded){
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Alert" message:#"YES" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
}
}
Here i took imageView as subview of scrollview as u said
Since your UIScrollView is the common parent, that's probably where your gesture recognizer needs to be. You can determine which subview is being pressed by looking at the location of the point supplied in your action. So, the individual subviews do not need gesture recognizers.
So, you would do something like this:
- (void)longPressDidFire:(UILongPressGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
CGPoint point = [sender locationInView:scroll];
UIView *tappedView = [scroll hitTest:point withEvent:nil];
So, then you have the view that was long-pressed.
Other things that could cause the action not to fire would be a delegate problem or if the scroll is contained by another view that is intercepting the touch.
HTH
Instead of
[scroll addGestureRecognizer:longPress];
Add the gesture on your subviews, right after you declare them and before you add them to scrollview
[subview addGestureRecognizer:longPress];
Woohoo it works!
The problem was:
imgContainer was a UIView with an empty frame with several UIImageViews as subviews
I was under the impression that as I added a subview to imgContainer, imgContainer would expand.
This is not true.
I had to set imgContainer's frame to the same content frame as the scroll view and then everything became ok.
I hope this answer helps any other future iOS firs timers like me.
I had a project which contains lots of UIButton, using xcode 4.5 and storyboard and ARC. after testing in ios6, everything goes fine. But in ios5, UIButton touch up inside event not working, the action not called. I tried to use touch down and it works. However, I have a lot of UIButton, I cannot change that one by one. what's more, the touch down event does give a good experience.
I used code below in many of my view controllers:
in viewDidLoad:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
-(void)dismissKeyboard {
[aTextField resignFirstResponder];
}
this might be the reason. I will check it out later. But it works in ios6.
Do you know what's wrong ? Thanks very much!
I had the same problem to you.
That's because the touch event in iOS5 is prevented when the gestureRecognizer you registered captures the event.
There are two solutions.
One:
1) Add a new view inside your view. It should have the same level to the buttons. The priority of the buttons should be higher than the new view.
2) change the call of 'addGestureRecognizer' to the new view.
[self.newView addGestureRecognizer:tap];
Two:
1) Implement the code below. You should return NO when the point is in the buttons.
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint point = [gestureRecognizer locationInView:self.view];
NSLog(#"x = %.0f, y = %.0f", point.x, point.y);
if (CGRectContainsPoint(self.testBtn.layer.frame, point))
return NO;
return YES;
}
ps.
you should import QuartzCore.h to access layer's attributes.
#import <QuartzCore/QuartzCore.h>
Just use the property "cancelsTouchesInView" (NO) on UITapGestureRecognizer tap
Example:
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTap)];
gesture.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:gesture];
Override your button parent view pointinside method, setting a new custom frame size that contains your button.
I am programmatically building a UINavigationContoller for iOS and am having problems making it fully accessible. In loadView I create the main UIView and set it as NOT accessible:
- (void)loadView
{
CGRect viewRect = [[UIScreen mainScreen] applicationFrame];
UIView *tmp = [[UIView alloc] initWithFrame:viewRect];
[tmp setIsAccessibilityElement:NO];
I then add additional UIViews that contain just background images and also set those as not accessible. All views and controls are added onto the "tmp" UIView created above. Here is a "background" view example:
UIImage* microphone = [UIImage imageNamed:#"microphone.jpg"];
UIView* microphoneView = [[[UIView alloc] initWithFrame: CGRectMake(0,0,viewRect.size.width, microphone.size.height)] autorelease];
[microphoneView setBackgroundColor:[UIColor colorWithPatternImage:microphone]];
[microphoneView setIsAccessibilityElement:NO];
[tmp addSubview:microphoneView];
Finally I add a UIButton, UILabel and UIButtonBarItem. I add these last so they are on the top of the view hierarchy. I add accessibility labels and traits to them. Here is the UIButton:
self.recordImage = [UIImage imageNamed: #"record_button.png"];
self.stopRecordImage = [UIImage imageNamed: #"stop_button.png"];
self.recordButton.accessibilityTraits |= UIAccessibilityTraitStartsMediaSession;
self.recordButton = [[UIButton alloc ] initWithFrame: CGRectMake((viewRect.size.width - recordImage.size.width)/2 , (microphone.size.height + (grayBkg.size.height - recordImage.size.height)/2), recordImage.size.width, recordImage.size.height)];
[self.recordButton setIsAccessibilityElement:YES];
[self.recordButton setAccessibilityLabel: #"toggle recording start"];
[self.recordButton setImage: recordImage forState:UIControlStateNormal];
[self.recordButton addTarget: self action:#selector(processButton:) forControlEvents:UIControlEventTouchUpInside];
[tmp addSubview:recordButton];
finally
....
[self setView:tmp];
[tmp release];
I did call UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); when I push this view onto the stack.
With voiceover on, when the view is displayed I can swipe and give each of my elements (the UIButtonBarItem, UILabel, and UIButton) focus and I can activate them with double tap. However, VoiceOver speaks no information about the elements. Testing in the simulator with the Accessibility Inspector shows the labels I have set via aControl.accessibilityLabel = #"the label";
This view is used to record audio. If I activate the buttons and record the audio and stop recording, VoiceOver will now speak the labels for the elements when I focus them? Why is VoiceOver not speaking the information when the view first loads? Any clues appreciated!
I am testing on an iPad 2 with iOS 4.3.3.
If you'd like your view to not be accessible, use:
[microphoneView setUserInteractionEnabled:NO];
This view is being used for audio recording. The problem was that I was setting the AVSession Category to AVAudioSessionCategoryRecord in the viewDidLoad method. This was causing VoiceOver not to speak the view information. I modified the code to set the category to AVAudioSessionCategoryRecord only when the record button is pushed. And I set it to AVAudioSessionCategoryPlayAndRecord when recording is finished. Here is the thread that explains it fully: http://lists.apple.com/archives/accessibility-dev/2011/Jul/msg00002.html
For some reason, the button initialized in the addBook method of my viewController won't respond to touches. The selector I've assigned to it never triggers, nor does the UIControlStateHighlighted image ever appear when tapping on the image.
Is there something intercepting touches before they get to the UIButton, or is its interactivity somehow disabled by what I'm doing to it?
- (void)viewDidLoad {
...
_scrollView.contentSize = CGSizeMake(currentPageSize.width, currentPageSize.height);
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.showsVerticalScrollIndicator = NO;
_scrollView.scrollsToTop = NO;
_scrollView.pagingEnabled = YES;
...
}
- (void)addBook {
// Make a view to anchor the UIButton
CGRect frame = CGRectMake(0, 0, currentPageSize.width, currentPageSize.height);
UIImageView* bookView = [[UIImageView alloc] initWithFrame:frame];
// Make the button
frame = CGRectMake(100, 50, 184, 157);
UIButton* button = [[UIButton alloc] initWithFrame:frame];
UIImage* bookImage = [UIImage imageNamed:kBookImage0];
// THIS SECTION NOT WORKING!
[button setBackgroundImage:bookImage forState:UIControlStateNormal];
UIImage* bookHighlight = [UIImage imageNamed:kBookImage1];
[button setBackgroundImage:bookHighlight forState:UIControlStateHighlighted];
[button addTarget:self action:#selector(removeBook) forControlEvents:UIControlEventTouchUpInside];
[bookView addSubview:button];
[button release];
[bookView autorelease];
// Add the new view/button combo to the scrollview.
// THIS WORKS VISUALLY, BUT THE BUTTON IS UNRESPONSIVE :(
[_scrollView addSubview:bookView];
}
- (void)removeBook {
NSLog(#"in removeBook");
}
The view hierarchy looks like this in Interface Builder:
UIWindow
UINavigationController
RootViewController
UIView
UIScrollView
UIPageControl
and presumably like this once the addBook method runs:
UIWindow
UINavigationController
RootViewController
UIView
UIScrollView
UIView
UIButton
UIPageControl
THe UIScrollView might be catching all the touch events.
Maybe try a combination of the following:
_scrollView.delaysContentTouches = NO;
_scrollView.canCancelContentTouches = NO;
and
bookView.userInteractionEnabled = YES;
try to remove the [bookView autorelease]; and do it like this:
// Add the new view/button combo to the scrollview.
// THIS WORKS VISUALLY, BUT THE BUTTON IS UNRESPONSIVE :(
[_scrollView addSubview:bookView];
[bookView release];
_scrollView.canCancelContentTouches = YES; should do the trick
delaysContentTouches - is a Boolean value that determines whether the scroll view delays the handling of touch-down gestures. If the value of this property is YES, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is NO , the scroll view immediately calls touchesShouldBegin:withEvent:inContentView:. The default value is YES.
canCancelContentTouches - is a Boolean value that controls whether touches in the content view always lead to tracking. If the value of this property is YES and a view in the content has begun tracking a finger touching it, and if the user drags the finger enough to initiate a scroll, the view receives a touchesCancelled:withEvent: message and the scroll view handles the touch as a scroll. If the value of this property is NO, the scroll view does not scroll regardless of finger movement once the content view starts tracking.