I'm currently trying to use a UIButton titled "X" as a way to remove a Sprite from the view.
Basically, my code works so that when a Sprite is touched, a message is sent to the delegate (View Controller) that passes the (Sprite *)sprite which has been selected. In this method, I draw a UIButton on top of that sprite. So far, so good.
However, the issue is I now want my UIButton to run a #selector to remove that sprite whenever the button is touched down.
Initially I tried this:
-(void)spriteSelected:(Sprite *)sprite{ //delegate method implementation
[sprite.button addTarget:self action:#selector(removeSprite:sprite) forControlEvents: UIControlEventTouchDown]
}
-(void)removeSprite:(Sprite *)sprite{
[sprite removeFromSuperView];}
However, it seems I can't put arguments into the selector like that. Any ideas on how I can adjust this?
Thanks
You should do this in the Sprite class, so in the original class:
-(void)spriteSelected:(Sprite *)sprite{
[sprite youAreSelected];
}
In the Sprite class:
-(void)youAreSelected {
[self.button addTarget:self action:#selector(removeMe:) forControlEvents: UIControlEventTouchDown];
}
-(void)removeMe:(id)sender {
[self removeFromSuperView];
}
A selector is simply a method identifier, not an invocation of the method, so you can't include parameters as though it were a method call.
The usual way to manage something like this would be for the view controller to look at the button that was touched, figure out which sprite it's associated with, and remove the sprite. It was probably the view controller that put both the sprite and the button in the view in the first place, so it should have all the information it needs.
Related
I have a scrollview in my Main view and I have three subviews on my scrollview. And I have UIButtons in all my subviews.
Now, I want to drag those buttons from one subview to another subview (while dragging the buttons, the scrollview should not scroll).
How do I do this?
I'm not completely sure if this snippet works for this particular case (an UIControl inside a UIScrollView), but my understanding of UIResponder chain suggests me that it should :)
- (void)viewDidLoad { // or wherever you initialize your things
...
// Add swipe event to UIButton so it will capture swipe intents
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] init];
[panGR addTarget:self action:#selector(panEvent:)];
[button addGestureRecognizer:panGR];
[panGR release];
}
- (void)panEvent:(id)sender {
button.center = [sender locationInView:self.view];
}
If this works (can't test it right now, but it did work for me in a similar situation), then you should add more code to handle the drag & drop related events (maybe disable Clip Subviews option in the UIScrollView, add the button to the new superview if the location intersects with the CGRect of the destination, return the button to the original location if it doesn't, etc).
So, what's happening in those lines? When you begin touching the UIButton, the order doesn't get to the UIScrollView because the event could follow as a touch event (handled by the UIButton), or as a pan event (handled by the UIScrollView). When you move your finger, the event is dismissed by the UIButton's responder because there's no Gesture Recognizer that knows how to proceed if the finger is moved.
But when you add a Gesture Recognizer to the UIButton who actually knows what to do when the finger is moved, everything is different: the UIButton will not dismiss the event, and the UIScrollView will never realize that there was a touch moving over it.
I hope my explanation is accurate and comprensible enough. Let me know if a) it doesn't work or b) there's something unclear.
Good luck :)
Try
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (allowAppDrag && [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
return NO;
}
return YES;
}
I'm using an UIPageViewController in my application and I wanted to have a few UIButtons inside it, sort of like a menu. The problem I have is that when I put an UIButton (or any other interactive element) near the edges of the screen and tap it, instead of the UIButton action being applied, what happens is that the page changes (because the tap on the edge of the screen changes the page on the UIPageViewController). I'd like to know if there's a way to make it so that the UIButton has higher priority than the UIPageViewController so that when I tap the button, it applies the appropriate action instead of changing the page.
I came here with the same problem. Split’s link has the answer.
Make your root view controller the delegate of each of the UIPageViewController’s gesture recognizers, then prevent touches from being delivered if they occur inside any UIControl:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return ([touch.view isKindOfClass:[UIControl class]] == NO);
}
UIPageViewController has two UIGestureRecognizers. You can access them via gestureRecognizers property. Determine which one is UITapGestureRecognizer and then use this. Hope this helps.
For people that just want to copy/paste code, here is mine :
// I don't want the tap on borders to change the page
-(void) desactivatePageChangerGesture {
for (UIGestureRecognizer* gestureRecognizer in self.pageViewController.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
gestureRecognizer.enabled = NO;
}
}
}
Just call this function after the UIPageViewController creation.
I had this same problem, and was unsure how to handle the UIGestureRecognizer delegate methods. This short example assumes you are using the "Page Based Application" project type in Xcode 4. Here is what I did:
In RootViewController.h, I made sure to announce that RootViewController would handle the UIGestureRecognizerDelegate protocol:
#interface RootViewController : UIViewController <UIPageViewControllerDelegate, UIGestureRecognizerDelegate>
In RootViewController.m, I assigned RootViewController as the delegate for the UITapGestureRecognizer. This is done at the end of the viewDidLoad method. I did this by iterating over each gestureRecognizer to see which one was the UITapGestureRecognizer.
NSEnumerator *gestureLoop = [self.view.gestureRecognizers objectEnumerator];
id gestureRecognizer;
while (gestureRecognizer = [gestureLoop nextObject]) {
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[(UITapGestureRecognizer *)gestureRecognizer setDelegate:self];
}
}
Finally, I added the gestureRecognizer:shouldReceiveTouch method to the bottom of RootViewController.m (This is copied directly from Split's link):
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isKindOfClass:[UIControl class]]) {
// we touched a button, slider, or other UIControl
return NO; // ignore the touch
}
return YES; // handle the touch
}
Comment out these line from your code
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
or use UIGestureRecognizer as told by Split
Hope this will help you
OLD ANSWER: If your UIPageViewController has a transitionStyle of UIPageViewControllerTransitionStyleScroll and you are in iOS 6.0+, then you can't use the gestureRecognizer:shouldReceiveTouch: method, because there is no way to set the delegate to self on the gestureRecognizers since pageViewController.gestureRecognizers will return nil. See UIPageViewController returns no Gesture Recognizers in iOS 6 for more information about that.
If you simply want to make sure your UIPageViewController passes along button touch events to a UIButton, you can use
for (UIScrollView *view in _pageViewController.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
view.delaysContentTouches = NO;
}
}
if you have a transitionStyle of UIPageViewControllerTransitionStyleScroll and you are in iOS 6.0+.
See this answer about why delaysContentTouches = NO is needed for some cases of a UIButton in a UIScrollView
UPDATE: After doing a little more research it appears that if your issue is that the UIButton click seems to only be called sometimes, then that is actually probably the desired behavior inside a UIScrollView. A UIScrollView uses the delaysContentTouches property to automatically determine if the user was trying to scroll or trying to press a button inside the scroll view. I would assume it is best to not alter this behavior to default to NO since doing so will result in an inability to scroll if the user's finger is over a button.
None of the solutions here where you intercept the UIPageViewController's tap gesture recognizers worked for me. I'm targeting iOS 8 and 9.
What worked is to override the functions touchesBegan, touchesCancelled, touchesMoved, and touchesEnded in my custom button which is a subclass of UIControl. Then I just manually send the .TouchUpInside control event if the touch began and ended within the frame of my custom button.
I didn't have to do anything special for the containing page view controller, or the view controller that contains the page view controller.
Swift 5 answer here should do the job.
pageViewController.view.subviews.compactMap({ $0 as? UIScrollView }).first?.delaysContentTouches = false
How do i pass an event from a subview to trigger UIControlEventTouchUpInside of its superview(a subclass of UIControl)? The subview has a triangular area of a certain size. If I remove my finger from within the triangle I want the UIControl superview to trigger its action. I tried to forward touchesEnd from the subview to the superview with nextResponder but it didn't work.
To programmatically fire an event on a UIControl, use this method:
- (void)sendActionsForControlEvents:(UIControlEvents)controlEvents
for example:
- (void)touchUpInside {
// will need to cast superview to UIControl
[[self superview] sendActionsForControlEvents:UIControlEventTouchUpInside];
}
more info on this method in the docs.
I'm making different UIView's tappable (they're not inheriting from UIControl) using the following code:
UITapGestureRecognizer* gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(userTappedOnLink:)];
[labelView setUserInteractionEnabled:YES];
[labelView addGestureRecognizer:gesture];
but I'd also like to change the style when they're highlighted. How do I do that?
Attach UILongPressGestureRecognizer instead of UITapGestureRecognizer to parent view and set it's properties to your liking. The way to track and respond to selection is to implement userTappedOnLink method in appropriate way. This method will be called lots of times in short amount of time when gesture recognizer is activated and you know what's happening by tracking recognizer states.
Implement UIView subclass and create methods, like select and deselect, and customize view properties for each. Then it's only matter of finding which UIView subclass to select or deselect and that's easily done with UIGestureRecognizer method returning point in parent view and iterating trough it's subviews while checking if touch point is inside of particular subview frame.
- (IBAction)userTappedOnLink:(UIGestureRecognizer*)sender
{
switch (sender.state)
{
case UIGestureRecognizerStateBegan:
{
CGPoint touchPoint = [sender locationInView:self.parentView];
for (UIView *subView in [self.parentView subViews)
{
if (CGRectContainsPoint(subView.frame, tapPoint))
{
self.activeSubView = self.subview;
break;
}
}
[self.activeSubView select];
case UIGestureRecognizerStateChanged:[self.activeSubView doNothing];; break;
case UIGestureRecognizerStateEnded:[self.activeSubView deSelect]; self.activeSubView = nil; break;
}
}
There are two ways of handling events in iOS. The first one is to use UIView subclassing and override the methods that are inherited by UIView from UIResponder class (i.e. touchesBegan:withEvent, touchesMoved:withEvent, touchesEnded:withEvent, touchesCancelled:withEvent).
The second way is to create new instance of gesture recognizer class and add it to your UIView object (just like you did) and then create a handler.
- (IBAction)userTappedOnLink:(UIGestureRecognizer *)sender {
// Changing view properties.
}
In both cases you can change the UIView properties. You can find some useful information in "Event handling guide" by Apple. There is a lot of reading but you can have a look only at the "related sample code" (Touches and SimpleGestureRecognizers).
The way you change the style properties of the interface elements in your application depends on what those properties are. Sometimes they can be animated sometimes they're not. Usually the code that changes view properties is placed inside the touchesBegin function or gesture recognizer handler. MoveMe sample code shows how to change views properties and animate them. In the Gesture recognizers chapter of the "Event handling guide" frame properties are changed.
I managed to solve this by adding an UIControl as a subview in the UIView. The UIControl is the same size has a transparent background that changes when it's highlighted. Works like a charm!
well, I have not tested it, just a suggestion, please handle touchesbegin for this view,and call [labelView addGestureRecognizer:gesture]; function.
maybe you should use another function, - (void)removeGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer .
How can I position a button on my view dynamically?
Try this code
for(UIView * view in self.view.subviews)
{
if([view isKindOfClass:[UIButton class]])
{
// view is button
}
}
You can also use tags
Set the tag property of the UIButton (e.g. in interface builder, or programatically). Then you can walk the children of the owning view:
for (UIView *view in myView.subviews) {
if (view.tag == 101 /*or whatever you set it to*/) {
// things...
}
}
Ok, now we understand the question better...
You can place a button/UIComponent at runtime by settings its frame. This will place it at a given coordinate within the view containing the button.
myButton.frame = CGRectMake(newX, newY, myButton.frame.width, myButton.frame.height);
If you wish to do a linear style animation (no curves), look up CATransitions, see iPhone UIView Animation Best Practice. If you need to support pre-iOS4 devices, you can't use CATransitions, and have to do it the old way using UIView animations.
To do more complex animations, you might have to mess around with timers, which can slightly juddery effects if you're trying to do complicated animation paths.
Get a array of all the subviews of a view using:
[myView subviews]
iterate through that array and check for your butotn by some unique characteristic. (You can set the button's tag attribute to some unique value and check for that value)