I have a label with UITapGestureRecognizer and longgestureRecognizer:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("labelPressed:"))
let longgestureRecognizer = UILongPressGestureRecognizer(target: self, action: Selector("longLabelPressed:"))
label.addGestureRecognizer(gestureRecognizer)
label.addGestureRecognizer(longgestureRecognizer)
I want to change color like in UIButton, when it pressed:
func longLabelPressed(recognizer:UILongPressGestureRecognizer){
if let label = recognizer.view as? UILabel {
if recognizer.state == .Began {
label.textColor = UIColor.redColor()
}
if recognizer.state == .Ended {
label.textColor = UIColor.blackColor()
}
}
}
But how to detect tap end event ?
func labelPressed(recognizer:UITapGestureRecognizer) {
if let label = recognizer.view as? UILabel {
}
}
My goal is create label like UIButton with touch events.
UserInteractionEnabled for label is by default false . so if you are using its label outlet then enable it from (XIB/ storyboard)
Now to make label same like button using UILongPressGestureRecognizer
and then you event called successfully but you write label color change code in UILongPressGestureRecognizer so its take some time(default time) to detect touch event. recognizer.state == .Began so change minimum time for long pressGesture
longgestureRecognizer.minimumPressDuration = 0.001
using that recognizer.state == .Began will call quickly .
Make a new subclass of UILabel and implement this :
Objective-C
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.textColor = [UIColor blueColor];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.textColor = [UIColor whiteColor];
}
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.textColor = [UIColor blackColor];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.textColor = [UIColor redColor];
}
Swift 4
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
textColor = UIColor.blue
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
textColor = UIColor.white
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
textColor = UIColor.black
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
textColor = UIColor.red
}
Don't forget to set :
Objective-C
label.userInteractionEnabled = YES;
Swift 4
label.isUserInteractionEnabled = true
Add the custom class to your label in your xib and enjoy :)
Reference : UILabel + touchDown
Like the docs mentoined you should enable unser interaction.
let gestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("labelPressed:"))
let longgestureRecognizer = UILongPressGestureRecognizer(target: self, action: Selector("longLabelPressed:"))
label.userInteractionEnabled = YES;
label.addGestureRecognizer(gestureRecognizer)
label.addGestureRecognizer(longgestureRecognizer)
Two things:
You should set userInteractionEnabled property on the label to true (as someone mentioned earlier)
Using UITapGestureRecognizer will only give you information when the gesture is finished. If you want to know when it begins I would suggest using UILongPressGestureRecognizer and setting its minimumPressDuration property to 0.
Related
I want to change color of UIView when it's tap and change it back to it's original color after tap event
I already implemented these 2 methods but their behavior is not giving me required results
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
backgroundColor = UIColor.white
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
backgroundColor = UIColor.gray
}
These 2 methods work but after press tap on UIView for 2 seconds then it works. Moreover it doesn't change the color of UIView back to white after pressing it (In short it stays gray until I restart the app)
I am using tap gestures on UIView
Instead of overriding touchesBegan and touchesEnded methods, you could add your own gesture recognizer. Inspired from this answer, you could do something like:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
setupTap()
}
func setupTap() {
let touchDown = UILongPressGestureRecognizer(target:self, action: #selector(didTouchDown))
touchDown.minimumPressDuration = 0
view.addGestureRecognizer(touchDown)
}
#objc func didTouchDown(gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
view.backgroundColor = .white
} else if gesture.state == .ended || gesture.state == .cancelled {
view.backgroundColor = .gray
}
}
}
I am trying to determine the location of a touch on a PDFView. I have set my pdf view to be user interacted as follows:
pdfView?.isUserInteractionEnabled = true
I then use the following function to detect touches:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: pdfView)
print(position)
}
}
The touchesBegan code is never fired.
Add gesture recognizer to PDFView like below:
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(_:)))
tapGesture.numberOfTapsRequired = 1
if var recognizer = pdfView.gestureRecognizers {
recognizer.append(tapGesture)
pdfView.gestureRecognizers = recognizer
}
How can we implement 3D touch to check if the user taps on UIView or force touch on UIView?
Is there a way to do this with UIGestureRecognize or only with UITouch?
You can do it without a designated gesture recognizer. You do not need to adjust the touchesEnded and touchesBegan method, but simply the touchesMoved to obtain the correct values. getting the force of a uitouch from began/ended will return weird values.
UITouch *touch = [touches anyObject];
CGFloat maximumPossibleForce = touch.maximumPossibleForce;
CGFloat force = touch.force;
CGFloat normalizedForce = force/maximumPossibleForce;
then, set a force threshold and compare the normalizedForce to this threshold (0.75 seems fine for me).
The 3D Touch properties are available on UITouch objects.
You can get these touches by overriding a UIView's touchesBegan: and touchesMoved: methods. Not sure what you see in touchesEnded: yet.
If you're willing to create new gesture recognizers, you have full access to the UITouches as exposed in UIGestureRecognizerSubclass.
I'm not sure how you could use the 3D touch properties in a traditional UIGestureRecognizer. Maybe via the UIGestureRecognizerDelegate protocol's gestureRecognizer:shouldReceiveTouch: method.
With Swift 4.2 and iOS 12, a possible way to solve your problem is to create a custom subclass of UIGestureRecognizer that handles Force Touch and add it to your view next to a UITapGestureRecognizer. The following complete code shows how to implement it:
ViewController.swift
import UIKit
class ViewController: UIViewController {
let redView = UIView()
lazy var tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapHandler))
lazy var forceTouchGestureRecognizer = ForceTouchGestureRecognizer(target: self, action: #selector(forceTouchHandler))
override func viewDidLoad() {
super.viewDidLoad()
redView.backgroundColor = .red
redView.addGestureRecognizer(tapGestureRecognizer)
view.addSubview(redView)
redView.translatesAutoresizingMaskIntoConstraints = false
redView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
redView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
redView.widthAnchor.constraint(equalToConstant: 100).isActive = true
redView.heightAnchor.constraint(equalToConstant: 100).isActive = true
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.forceTouchCapability == UIForceTouchCapability.available {
redView.addGestureRecognizer(forceTouchGestureRecognizer)
} else {
// When force touch is not available, remove force touch gesture recognizer.
// Also implement a fallback if necessary (e.g. a long press gesture recognizer)
redView.removeGestureRecognizer(forceTouchGestureRecognizer)
}
}
#objc func tapHandler(_ sender: UITapGestureRecognizer) {
print("Tap triggered")
}
#objc func forceTouchHandler(_ sender: ForceTouchGestureRecognizer) {
UINotificationFeedbackGenerator().notificationOccurred(.success)
print("Force touch triggered")
}
}
ForceTouchGestureRecognizer.swift
import UIKit.UIGestureRecognizerSubclass
#available(iOS 9.0, *)
final class ForceTouchGestureRecognizer: UIGestureRecognizer {
private let threshold: CGFloat = 0.75
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if let touch = touches.first {
handleTouch(touch)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
if let touch = touches.first {
handleTouch(touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
state = UIGestureRecognizer.State.failed
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
state = UIGestureRecognizer.State.failed
}
private func handleTouch(_ touch: UITouch) {
guard touch.force != 0 && touch.maximumPossibleForce != 0 else { return }
if touch.force / touch.maximumPossibleForce >= threshold {
state = UIGestureRecognizer.State.recognized
}
}
}
Sources:
GitHub / FlexMonkey - DeepPressGestureRecognizer
GitHub / ashleymills - ForceTouchGestureRecognizer
Apple Developer Documentation - Implementing a Discrete Gesture Recognizer
I created a UIGestureRecognizer that emulates the behavior of the Apple Mail app. Upon 3D touch, it starts with a short single pulse vibrate and then an optional secondary action (hardTarget) and pulse called by hard pressing shortly after the initial press.
Adapted from https://github.com/FlexMonkey/DeepPressGestureRecognizer
Changes:
3D touch vibrate pulses like iOS system behavior
touch must come up for it to end, like Apple mail app
threshold defaults to system default level
hard touch triggers hardAction call like mail app
Note: I added the undocumented system sound k_PeakSoundID, but feel free to turn that off if you are uncomfortable using a constant beyond the documented range. I have been using system sounds with undisclosed constants for years, but you are welcomed to turn off the vibration pulses using the vibrateOnDeepPress property.
import UIKit
import UIKit.UIGestureRecognizerSubclass
import AudioToolbox
class DeepPressGestureRecognizer: UIGestureRecognizer {
var vibrateOnDeepPress = true
var threshold: CGFloat = 0.75
var hardTriggerMinTime: TimeInterval = 0.5
var onDeepPress: (() -> Void)?
private var deepPressed: Bool = false {
didSet {
if (deepPressed && deepPressed != oldValue) {
onDeepPress?()
}
}
}
private var deepPressedAt: TimeInterval = 0
private var k_PeakSoundID: UInt32 = 1519
private var hardAction: Selector?
private var target: AnyObject?
required init(target: AnyObject?, action: Selector, hardAction: Selector? = nil, threshold: CGFloat = 0.75) {
self.target = target
self.hardAction = hardAction
self.threshold = threshold
super.init(target: target, action: action)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if let touch = touches.first {
handle(touch: touch)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
if let touch = touches.first {
handle(touch: touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
state = deepPressed ? UIGestureRecognizerState.ended : UIGestureRecognizerState.failed
deepPressed = false
}
private func handle(touch: UITouch) {
guard let _ = view, touch.force != 0 && touch.maximumPossibleForce != 0 else {
return
}
let forcePercentage = (touch.force / touch.maximumPossibleForce)
let currentTime = Date.timeIntervalSinceReferenceDate
if !deepPressed && forcePercentage >= threshold {
state = UIGestureRecognizerState.began
if vibrateOnDeepPress {
AudioServicesPlaySystemSound(k_PeakSoundID)
}
deepPressedAt = Date.timeIntervalSinceReferenceDate
deepPressed = true
} else if deepPressed && forcePercentage <= 0 {
endGesture()
} else if deepPressed && currentTime - deepPressedAt > hardTriggerMinTime && forcePercentage == 1.0 {
endGesture()
if vibrateOnDeepPress {
AudioServicesPlaySystemSound(k_PeakSoundID)
}
//fire hard press
if let hardAction = self.hardAction, let target = self.target {
_ = target.perform(hardAction, with: self)
}
}
}
func endGesture() {
state = UIGestureRecognizerState.ended
deepPressed = false
}
}
// MARK: DeepPressable protocol extension
protocol DeepPressable {
var gestureRecognizers: [UIGestureRecognizer]? {get set}
func addGestureRecognizer(gestureRecognizer: UIGestureRecognizer)
func removeGestureRecognizer(gestureRecognizer: UIGestureRecognizer)
func setDeepPressAction(target: AnyObject, action: Selector)
func removeDeepPressAction()
}
extension DeepPressable {
func setDeepPressAction(target: AnyObject, action: Selector) {
let deepPressGestureRecognizer = DeepPressGestureRecognizer(target: target, action: action, threshold: 0.75)
self.addGestureRecognizer(gestureRecognizer: deepPressGestureRecognizer)
}
func removeDeepPressAction() {
guard let gestureRecognizers = gestureRecognizers else { return }
for recogniser in gestureRecognizers where recogniser is DeepPressGestureRecognizer {
removeGestureRecognizer(gestureRecognizer: recogniser)
}
}
}
The way I am doing this is to use a combination of a UITapGestureRecognizer (provided by Apple) and a DFContinuousForceTouchGestureRecognizer (provided by me).
The DFContinuousForceTouchGestureRecognizer is nice because it provides continuous updates about the pressure changes so you can do things like augment the view as the user varies their pressure on it, as opposed to a single event. If you just want a single event you can ignore eveything in the DFContinuousForceTouchDelegate except the - (void) forceTouchRecognized callback.
https://github.com/foggzilla/DFContinuousForceTouchGestureRecognizer
You can download this and run the sample app on a device that supports force press to see how it feels.
In your UIViewController implement the following:
- (void)viewDidLoad {
[super viewDidLoad];
_forceTouchRecognizer = [[DFContinuousForceTouchGestureRecognizer alloc] init];
_forceTouchRecognizer.forceTouchDelegate = self;
//here to demonstrate how this works alonside a tap gesture recognizer
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[self.imageView addGestureRecognizer:_tapGestureRecognizer];
[self.imageView addGestureRecognizer:_forceTouchRecognizer];
}
implement selector for tap gesture
#pragma UITapGestureRecognizer selector
- (void)tapped:(id)sender {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[[UIAlertView alloc] initWithTitle:#"Tap" message:#"YEAH!!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
});
}
Implement the delegate protocol for force touch:
#pragma DFContinuousForceTouchDelegate
- (void)forceTouchRecognized:(DFContinuousForceTouchGestureRecognizer *)recognizer {
self.imageView.transform = CGAffineTransformIdentity;
[self.imageView setNeedsDisplay];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[[UIAlertView alloc] initWithTitle:#"Force Touch" message:#"YEAH!!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
});
}
- (void)forceTouchRecognizer:(DFContinuousForceTouchGestureRecognizer *)recognizer didStartWithForce:(CGFloat)force maxForce:(CGFloat)maxForce {
CGFloat transformDelta = 1.0f + ((force/maxForce) / 3.0f);
self.imageView.transform = CGAffineTransformMakeScale(transformDelta, transformDelta);
[self.imageView setNeedsDisplay];
}
- (void) forceTouchRecognizer:(DFContinuousForceTouchGestureRecognizer *)recognizer didMoveWithForce:(CGFloat)force maxForce:(CGFloat)maxForce {
CGFloat transformDelta = 1.0f + ((force/maxForce) / 3.0f);
self.imageView.transform = CGAffineTransformMakeScale(transformDelta, transformDelta);
[self.imageView setNeedsDisplay];
}
- (void)forceTouchRecognizer:(DFContinuousForceTouchGestureRecognizer *)recognizer didCancelWithForce:(CGFloat)force maxForce:(CGFloat)maxForce {
self.imageView.transform = CGAffineTransformIdentity;
[self.imageView setNeedsDisplay];
}
- (void)forceTouchRecognizer:(DFContinuousForceTouchGestureRecognizer *)recognizer didEndWithForce:(CGFloat)force maxForce:(CGFloat)maxForce {
self.imageView.transform = CGAffineTransformIdentity;
[self.imageView setNeedsDisplay];
}
- (void)forceTouchDidTimeout:(DFContinuousForceTouchGestureRecognizer *)recognizer {
self.imageView.transform = CGAffineTransformIdentity;
[self.imageView setNeedsDisplay];
}
Note that this will only be useful on a device that supports force touch.
Also you should not add the DFContinuousForceTouchGestureRecognizer to a view if are you running on iOS 8 or under since it uses the new force property on UITouch only available in iOS 9.
If you add this on iOS 8 it will crash, so conditionally add this recognizer based on what iOS version you are running on if you are supporting versions older than iOS 9.
How can one unselect a given segment in a UISegmented control, by pressing the same segment again?
E.g. Press segment 0, it becomes selected and remains highlighted. Press segment 0 again and it becomes unselected and unhighlighted.
The control only fires UIControlEventValueChanged events. Other events don't seem to work with it.
There is a property 'momentary' which when set to YES almost allows the above behavior, excepting that highlighting is only momentary. When momentary=YES pressing the same segment twice results in two UIControlEventValueChanged events, but when momentary=NO only the first press of a given segment results in a UIControlEventValueChanged event being fired. I.e. subsequent presses on the same segment will not fire the UIControlEventValueChanged event.
you can subclass UISegmentedControl:
Swift 3
class ReselectableSegmentedControl: UISegmentedControl {
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let previousSelectedSegmentIndex = self.selectedSegmentIndex
super.touchesEnded(touches, withEvent: event)
if previousSelectedSegmentIndex == self.selectedSegmentIndex {
if let touch = touches.first {
let touchLocation = touch.locationInView(self)
if CGRectContainsPoint(bounds, touchLocation) {
self.sendActionsForControlEvents(.ValueChanged)
}
}
}
}
}
Swift 4
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let previousSelectedSegmentIndex = self.selectedSegmentIndex
super.touchesEnded(touches, with: event)
if previousSelectedSegmentIndex == self.selectedSegmentIndex {
let touch = touches.first!
let touchLocation = touch.location(in: self)
if bounds.contains(touchLocation) {
self.sendActions(for: .valueChanged)
}
}
}
and then
#IBAction func segmentChanged(sender: UISegmentedControl) {
if (sender.selectedSegmentIndex == selectedSegmentIndex) {
sender.selectedSegmentIndex = UISegmentedControlNoSegment;
selectedSegmentIndex = UISegmentedControlNoSegment;
}
else {
selectedSegmentIndex = sender.selectedSegmentIndex;
}
}
Not EXACTLY what you ask for but I was searching for a similar feature and decided on this.
Add doubleTap gesture to UISegmentedControl that sets the selectedSegmentIndex
When you initialize your UISegmentedControl.
let doubleTap = UITapGestureRecognizer(target: self, action #selector(YourViewController.doubleTapToggle))
doubleTap.numberOfTapsRequired = 2
yourSegmentedControl.addGestureRecognizer(doubleTap)
Function to deselect segment
#objc func doubleTapToggle () {
yourSegmentedControl.selectedSegmentIndex = -1
yourSegmentedControl.sendActions(for: valueChanged)
}
Based on #protspace answer, a simpler way, without having to write anything in the #IBAction:
class DeselectableSegmentedControl: UISegmentedControl {
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let previousSelectedSegmentIndex = selectedSegmentIndex
super.touchesEnded(touches, with: event)
if previousSelectedSegmentIndex == selectedSegmentIndex {
let touch = touches.first!
let touchLocation = touch.location(in: self)
if bounds.contains(touchLocation) {
selectedSegmentIndex = UISegmentedControl.noSegment
sendActions(for: .valueChanged)
}
}
}
}
UISegmentedControl can be programmatically deselected with Swift using #IBOutlet weak var mySegmentedControl: UISegmentedControl! and mySegmentedControl.selectedSegmentIndex = -1. As far as detecting the touch of the segment, I do not know. Hopefully someone else will have that answer.
I'm building a sprite kit based game, and the lack of "right click" is really making it hard to convey some important information to my users. As a solution, I'm thinking about gestures like long press, two finger tap, etc.
How can one implement gestures on a SKSpriteNode?
Here's what I'm currently using to get a button-like behavior when an SKSpriteNode is touched.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self selectSkill:YES];
}
Before UIGestureRecognizer, you kept state variables that tracked where touches where and when they started. Here's a swift solution where buttonTouched: is a method that checks whether the UITouch was on the button you're checking.
var touchStarted: NSTimeInterval?
let longTapTime: NSTimeInterval = 0.5
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
if let touch = touches.anyObject() as? UITouch {
if buttonTouched(touch) {
touchStarted = touch.timestamp
}
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
if let touch = touches.anyObject() as? UITouch {
if buttonTouched(touch) && touchStarted != nil {
let timeEnded = touch.timestamp
if timeEnded - touchStarted! >= longTapTime {
handleLongTap()
} else {
handleShortTap()
}
}
}
touchStarted = nil
}
override func touchesCancelled(touches: NSSet!, withEvent event: UIEvent!) {
touchStarted = nil
}
Here is a good component that can help https://github.com/buddingmonkey/SpriteKit-Components
There is no easy way to do it, but one way I can think of would be to add a sub SKView to your SKScene and place an UIImageView as the only thing inside that SKView. Then you can add a gesture recognizer like normal to the SKView.
Here's an example of what I'm talking about:
UIImageView *button = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ButtonSprite"]];
SKView *spriteNodeButtonView = [[SKView alloc] initWithFrame:CGRectMake(100, 100, button.frame.size.width, button.frame.size.height)];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(someMethod:)];
[spriteNodeButtonView addGestureRecognizer:tap];
[spriteNodeButtonView addSubview:button];
You can put the SKView wherever you want and use any of the gesture recognizers on an SKView: UITapGestureRecognizer, UILongPressGestureRecognizer, UISwipeGestureRecognizer, UIPinchGestureRecognizer, UIRotationGestureRecognizer, UIPanGestureRecognizer, or UIScreenEdgePanGestureRecognizer.
Then for your method implementation do something like this:
-(void)someMethod:(UITapGestureRecognizer *)recognizer {
CGPoint touchLoc = [recognizer locationInView:self.view];
NSLog(#"You tapped the button at - x: %f y: %f", touchLoc.x, touchLoc.y);
}