I have a UISlider on screen, and I need to be able to detect when the user stops touching it. (so I can fade some elements away).
I have tried using:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
but this did not work when ending touches on a slider.
You can detect when a touch ends using two control events; try
[slider addTarget:self action:#selector(touchEnded:)
forControlEvents:UIControlEventTouchUpInside];
or
[slider addTarget:self action:#selector(touchEnded:)
forControlEvents:UIControlEventTouchUpOutside];
If you want to detect both types of the touchesEnd event, use
[slider addTarget:self action:#selector(touchEnded:)
forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside];
Instead of using touchesEnded: (which shouldn't be used for this purpose anyway), attach an action to the UISlider's UIControlEventValueChanged event and set the continuous property of the UISlider to NO, so the event will fire when the user finishes selecting a value.
mySlider.continuous = NO;
[mySlider addTarget:self
action:#selector(myMethodThatFadesObjects)
forControlEvents:UIControlEventValueChanged];
I couldn't get anything to capture both the start and end of the touches, but upon RTFD-ing, I came up with something that will do both.
#IBAction func sliderAction(_ sender: UISlider, forEvent event: UIEvent) {
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .began:
print("touches began")
sliderTouchBegan()
case .ended:
print("touches ended")
sliderTouchEnded()
default:
delegate?.sliderValueUpdated(sender.value)
}
}
}
sliderTouchBegan() and sliderTouchEnded() are just methods I wrote that handle animations when the touch begins and when it ends. If it's not a begin or end, it's a default and the slider value updates.
Related
I need to do action for next and previous button event for ios video player, here am using mpmovieplayerviewcontroller.
MPMoviePlayerController fires off MPMoviePlayerPlaybackStateDidChangeNotification when either the Prev or Next is tapped. There's no way to be notified whether each one is tapped.
The only way I found, was to create my own custom controls for backward and forward, adding a target to it to perform an action:
[prevBtn addTarget:self action:#selector(onClick:)
forControlEvents:UIControlEventTouchUpInside];
[nextBtn addTarget:self action:#selector(onClick:)
forControlEvents:UIControlEventTouchUpInside];
Then in your onClick method:
(void)onClick:(UIButton*)sender
{
if (sender == prevBtn)
{
// Do whatever when prevBtn is tapped
}
else if (sender == nextBtn)
{
// Do whatever when nextBtn is tapped
}
}
FYI: you must set the player's controlStyle property to MPMovieControlStyleNon to hide the default controls.
I often have a need to fire a sequence of events as a result of holding down a button. Think of a + button that increments a field: tapping it should increment it by 1, but tap & hold should say increment this by 1 every second until the button is released. Another example is the scrubbing function when holding down the backwards or forwards button in an audio player type app.
I usually resort to the following strategy:
On touchDownInside I set up a repeating timer with my desired interval.
On touchUpInside I invalidate and release the timer.
But for every such button I need a separate timer instance variable, and 2 target-actions, and 2 method implementations. (This is assuming I'm writing a generic class and don't want to impose restrictions on the max number of simultaneous touches).
Is there a more elegant way to solve this that I'm missing?
Register the events for every button by:
[button addTarget:self action:#selector(touchDown:withEvent:) forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:#selector(touchUpInside:withEvent:) forControlEvents:UIControlEventTouchUpInside];
For each button, set the tag attribute:
button.tag = 1; // 2, 3, 4 ... etc
In the handler, do whatever you need. Identify a button by the tag:
- (IBAction) touchDown:(Button *)button withEvent:(UIEvent *) event
{
NSLog("%d", button.tag);
}
I suggest UILongPressGestureRecognizer.
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(addOpenInService:)];
longPress.delegate = self;
longPress.minimumPressDuration = 0.7;
[aView addGestureRecognizer:longPress];
[longPress release];
longPress = nil;
On firing the event, you can get the call in
- (void) addOpenInService: (UILongPressGestureRecognizer *) objRecognizer
{
// Do Something
}
Likewise you can use UITapGestureRecognizer for recognizing user taps.
Hope this helps. :)
I have a Transparent NSWindow with an simple icon in it that can be dragged around the screen.
My code is:
.h:
#interface CustomView : NSWindow{
}
#property (assign) NSPoint initialLocation;
.m
#synthesize initialLocation;
- (id) initWithContentRect: (NSRect) contentRect
styleMask: (NSUInteger) aStyle
backing: (NSBackingStoreType) bufferingType
defer: (BOOL) flag{
if (![super initWithContentRect: contentRect
styleMask: NSBorderlessWindowMask
backing: bufferingType
defer: flag]) return nil;
[self setBackgroundColor: [NSColor clearColor]];
[self setOpaque:NO];
[NSApp activateIgnoringOtherApps:YES];
return self;
}
- (void)mouseDragged:(NSEvent *)theEvent {
NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame];
NSRect windowFrame = [self frame];
NSPoint newOrigin = windowFrame.origin;
// Get the mouse location in window coordinates.
NSPoint currentLocation = [theEvent locationInWindow];
// Update the origin with the difference between the new mouse location and the old mouse location.
newOrigin.x += (currentLocation.x - initialLocation.x);
newOrigin.y += (currentLocation.y - initialLocation.y);
// Don't let window get dragged up under the menu bar
if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height)) {
newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height);
}
// Move the window to the new location
[self setFrameOrigin:newOrigin];
}
- (void)mouseDown:(NSEvent *)theEvent {
// Get the mouse location in window coordinates.
self.initialLocation = [theEvent locationInWindow];
}
I want to display a NSPopover when the users clicks the image of the transparent window. But, as you can see in the code, the mouseDown event is used to get the mouse location (the code above was taken from an example).
What can i do to know when the user clicks the icon just to drag it around or simply clicked it to display the NSPopover?
Thank you
This is the classic situation of receiving the defining event after you need it in order to begin the action. Specifically, you can't know if the mouseDown is the beginning of a drag until after the drag starts. However, you want to act upon that mouseDown if a drag doesn't start.
In iOS (I realize that's not directly relevant to the code here, but it is instructional), there's an entire API built around letting iOS attempt to make these kinds of decisions for you. The entire Gesture system is based on the idea that the user starts to do something that might be one of many different actions, and thus needs to be resolved over time, possibly resulting in cancelled actions during the tracking period.
On OS X, we don't have many systems to help out with this, so if you have something that needs to handle a click and a drag differentially, you will need to defer your next action until a guard time has passed, and if that passes, you can perform the original action. In this case, you will likely want to do the following:
In the mouseDown, begin an NSTimer set for an appropriate guard time (not so long that people will accidentally move the pointer, and not so short that you'll trigger before the user drags) in order to call you back later to trigger the popover.
In the mouseDragged, use a guard area to make sure that if the user just twitches a little, it doesn't count as a drag. This can be irritating, as it sometimes results in needing to drag something farther than it seems necessary in order to begin a drag, so you'll want to either find a magic constant somewhere, or do some experimentation. When the guard area is exceeded, then begin your legitimate drag operation by canceling the NSTimer with [timer invalidate] and do your drag.
In the callback for the timer, display your popover. If the user dragged, the NSTimer will have been invalidated, causing it not to fire, and so the popover won't be displayed.
I must be missing something obvious here but ...
UIControl has a method
- (void)addTarget:(id)target action:(SEL)action forControlEvents: (UIControlEvents)controlEvents
which lets you add an action to be called when any of the given controlEvents occur. ControlEvents are a bitmask of events which tell you if a touch went down, or up inside, or was dragged etc., there's about 16 of them, you or them together and get called when any of them occur.
The selector can have one of the following signatures
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)
none of those tell you what the control event bitmask was. The UIEvent is something slightly different, it's related to the actual touch event and doesn't (I think) contain the UIControlEvent. The sender (UIControl) doesn't have a way to find the control events either.
I'd like to have one method which deals with a number of control events as I have some common code regardless of which event or events happened but I still need to know what the UIControlEvents were for some specific processing.
Am I missing a way to find out what UIControlEvents were used when the action was called or do I really have to separate my code into
- (void)actionWithUIControlEventX;
- (void)actionWithUIControlEventY;
I encountered the same problem, and came up with a solution. It's not amazingly pretty, but it works quite well. It is a UIControl category that stores the last UIControlEvent fired to its own tag property. You can get it from the link below. For further reference, here's the doc from my category, for a more detailed description of what's going on.
Hopefully this helps! Cheers/J
UIControl+CaptureUIControlEvents
git gist at: http://gist.github.com/513796
PROBLEM: upon firing, UIControlEvents are not passed into the target action
assigned to the particular event. This would be useful in order to have only
one action that switches based on the UIControlEvent fired.
SOLUTION: add a way to store the UIControlEvent triggered in the UIEvent.
PROBLEM: But we cannot override private APIs, so:
(WORSE) SOLUTION: have the UIControl store the UIControlEvent last fired.
The UIControl documentation states that:
When a user touches the control in a way that corresponds to one or more
specified events, UIControl sends itself sendActionsForControlEvents:.
This results in UIControl sending the action to UIApplication in a
sendAction:to:from:forEvent: message.
One would think that sendActionsForControlEvents: can be overridden (or
subclassed) to store the flag, but it is not so. It seems that
sendActionsForControlEvents: is mainly there for clients to trigger events
programatically.
Instead, I had to set up a scheme that registers an action for each control
event that one wants to track. I decided not to track all the events (or in
all UIControls) for performance and ease of use.
USAGE EXAMPLE:
On UIControl setup:
UIControlEvents capture = UIControlEventTouchDown;
capture |= UIControlEventTouchDown;
capture |= UIControlEventTouchUpInside;
capture |= UIControlEventTouchUpOutside;
[myControl captureEvents:capture];
[myControl addTarget:self action:#selector(touch:) forControlEvents:capture];
And the target action:
- (void) touch:(UIControl *)sender {
UIColor *color = [UIColor clearColor];
switch (sender.tag) {
case UIControlEventTouchDown: color = [UIColor redColor]; break;
case UIControlEventTouchUpInside: color = [UIColor blueColor]; break;
case UIControlEventTouchUpOutside: color = [UIColor redColor]; break;
}
sender.backgroundColor = color;
}
When you create your UIControl, set a value for the tag property. Then in your action function, you can determine the tag of the UIControl that called it using [sender tag]. Here's an example:
-(void)viewDidLoad {
UIButton *button1 = [[UIButton alloc] initWithFrame(CGRectMake(0.0,0.0,100.0,100.0)];
button1.tag = 42;
[button1 addTarget:self action:#selector(actionWithUIControlEvent:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button1];
UIButton *button2 = [[UIButton alloc] initWithFrame(CGRectMake(100.0,0.0,100.0,100.0)];
button2.tag = 43;
[button2 addTarget:self action:#selector(actionWithUIControlEvent:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button2];
}
-(void)actionWithUIControlEvent:(id)sender {
switch([sender tag]) {
case 42:
//Respond to button 1
break;
case 43:
//Respond to button 2
break;
default:
break;
}
}
In one of my iPhone projects, I have three views that you can move around by touching and dragging. However, I want to stop the user from moving two views at the same time, by using two fingers. I have therefore tried to experiment with UIView.exclusiveTouch, without any success.
To understand how the property works, I created a brand new project, with the following code in the view controller:
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
UIButton* a = [UIButton buttonWithType:UIButtonTypeInfoDark];
[a addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
a.center = CGPointMake(50, 50);
a.multipleTouchEnabled = YES;
UIButton* b = [UIButton buttonWithType:UIButtonTypeInfoDark];
[b addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
b.center = CGPointMake(200, 50);
b.multipleTouchEnabled = YES;
a.exclusiveTouch = YES;
[self.view addSubview:a];
[self.view addSubview:b];
}
- (void)hej:(id)sender
{
NSLog(#"hej: %#", sender);
}
When running this, hej: gets called, with different senders, when pressing any of the buttons - even though one of them has exclusiveTouch set to YES. I've tried commenting the multipleTouchEnabled-lines, to no avail. Can somebody explain to me what I'm missing here?
Thanks,
Eli
From The iPhone OS Programming Guide:
Restricting event delivery to a single view:
By default, a view’s exclusiveTouch property is set to NO. If you set
the property to YES, you mark the view so that, if it is tracking
touches, it is the only view in the window that is tracking touches.
Other views in the window cannot receive those touches. However, a
view that is marked “exclusive touch” does not receive touches that
are associated with other views in the same window. If a finger
contacts an exclusive-touch view, then that touch is delivered only if
that view is the only view tracking a finger in that window. If a
finger touches a non-exclusive view, then that touch is delivered only
if there is not another finger tracking in an exclusive-touch view.
It states that the exclusive touch property does NOT affect touches outside the frame of the view.
To handle this in the past, I use the main view to track ALL TOUCHES on screen instead of letting each subview track touches. The best way is to do:
if(CGRectContainsPoint(thesubviewIcareAbout.frame, theLocationOfTheTouch)){
//the subview has been touched, do what you want
}
I was encountering an issue like this where taps on my UIButtons were getting passed through to a tap gesture recognizer that I had attached to self.view, even though I was setting isExclusiveTouch to true on my UIButtons. Upon reviewing the materials here so far, I decided to put some code in my tap gesture code that checks if the tap location is contained in any UIButton frame and if that frame is also visible on the screen at the same time. If both of those conditions are true, then the UIButton will already have handled the tap, and the event triggered in my gesture recognizer can then be ignored as a pass through of the event. My logic allows me to loop over all subviews, checking if they are of type UIButton, and then checking if the tap was in that view and the view is visible.
#objc func singleTapped(tap: UITapGestureRecognizer)
{
anyControlsBreakpoint()
let tapPoint = tap.location(in: self.view)
// Prevent taps inside of buttons from passing through to singleTapped logic
for subview in self.view.subviews
{
if subview is UIButton {
if pointIsInFrameAndThatFrameIsVisible(view: subview, point: tapPoint)
{
return // Completely ignores pass through events that were already handled by a UIButton
}
}
}
Below is the code that checks if point was inside a visible button. Note that I hide my buttons by setting their alpha to zero. If you are using the isHidden property, your logic might need to look for that.
func pointIsInFrameAndThatFrameIsVisible(view : UIView, point : CGPoint) -> Bool
{
return view.frame.contains(point) && view.alpha == 1
}