I set up a Swipe Gesture Recognizer and I connected it to the code, so that an UIImageView rotates when the user swipes to the left.
#IBAction func swipeToLeft(sender: AnyObject) {
UIView.animateWithDuration(1.0, animations: {
self.image.transform = CGAffineTransformRotate(self.image.transform, -3.14159265358979 )
})
}
I made sure the viewDidLoad method looked like this:
image.userInteractionEnabled = true
However, the UIImageView only gets transformed only once.
You can download the demo of the project from this link. Why is that happening?
I guess, the problem is that the moment you rotated your image, the gesture recognizer, associated with it, also got rotated. You can make sure yourself:
Make a swipe right-to-left. The image will rotate. Then make a swipe left-to-right. It will rotate again.
If you want to always handle a swipe right-to-left, you can do it in a couple of ways:
If your view is always rotated by 180 degrees, the easiest way is to change the gesture recognizer orientation (#LyndseyScott was faster than me to right the code for this one, you can check her answer :) ).
Another option (especially, if there might be a situation, when you rotate your view by some arbitrary angle), is to create a UIView on top of the view you want to rotate (but not as its subview!), and to add the gesture recognizer to it instead.
Just to elaborate on FreeNickname's answer, since the gesture recognizer "rotates" along with the UIImageView, you can change your code to the following so that the swipe gesture swaps directions along with the image and you can continue swiping from right to left to trigger the animation:
#IBAction func swipeToLeft(sender: UISwipeGestureRecognizer) {
UIView.animateWithDuration(1.0, animations: {
self.doubleDot.transform = CGAffineTransformRotate(self.doubleDot.transform, -3.14159265358979 )
}, completion: {
(value: Bool) in
if sender.direction == UISwipeGestureRecognizerDirection.Left {
sender.direction = UISwipeGestureRecognizerDirection.Right
} else {
sender.direction = UISwipeGestureRecognizerDirection.Left
}
})
}
Related
Edit: it seems the swipe gesture can only takes one direction a time now. If someone knows another way to handle multiple directions at once, I’d still appreciate information!
Eidt: I find a way to deal with multiple directions concisely in this [answer] (https://stackoverflow.com/a/46104997/9645644) It uses an array literal with forEach loop. It’s much more convenient than adding gestures and dragging actions separately from storyboard.
I’m trying to get swift swipe gestures to work, everything’s fine until I tried to detect the direction of the swipe. Below is my code. I don’t understand why this isn’t working and would appreciate help!
In a view controller’s viewDidLoad I set up and added the swipe gesture recognizer, with direction[.left, .right]. After that I implemented the handler method which needs to detect the direction of the swipe. There’s no other stuff in this view controller.
After it failed to work(no response when swipe), I added a few prints, and got the output in the title.
override func viewDidLoad() {
super.viewDidLoad()
let swipeGestureRecognizer = UISwipeGestureRecognizer(target:self, action:#selector(swipeHandler(recognizer: )))
swipeGestureRecognizer.direction = [.left, .right]
view.addGestureRecognizer(swipeGestureRecognizer)
}
#objc func swipeHandler (recognizer: UISwipeGestureRecognizer){
switch recognizer.state{
case .ended:
let direction = recognizer.direction
print(direction)
if direction == .left {print(”left”)}
else if direction == .right {print(“right”)}
else {print(print(“none”)}
defaul: break
}
No matter left or right I swipe, it always prints “none”. And the direction print always give a “UISwipeGestureRecognizerDirection(rawValue: 3)”
The direction property tells the gesture when to trigger. for example if direction == .right then the swipe will trigger only on a swipe to the right. (It does not tell you the direction detected)
You need to detect one direction at a time. I would also suggest to add a method to control each swipe direction. For example.
func setUpGestures() {
// Gesture that define a left swipe.
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(Scene.swipeLeft))
swipeLeft.direction = .left
view?.addGestureRecognizer(swipeLeft)
// Do the same for the rest of the directions.
}
#objc func swipeLeft() {
// Do something when the user swipe left.
}
Hope it helps!
I made a simple project, with swipe gesture recogniser and animation. I made my label to move and every 3 second increase number. With every swipe I need to decrease the number. My gesture recogniser object is tied with label, i.e. it works only in label bounds. When prog is working without animation everything is ok, but when it;s animated an is moving my gesture recogniser is doing nothing. How to make a gesture recogniser work at the same time as animation, i.e. while animated to respond to my swipes. Need help.
`
#IBOutlet weak var label1: UILabel!
var number : Int = 0
var timer = Timer()
#IBAction func label1SwipeRight(_ sender: UISwipeGestureRecognizer) {
number += 1
label1.text = String(number)
}
func animate1() {
UIView.animate(withDuration: 4.0, delay: 0.0, options: .allowUserInteraction, animations: {
let num1 : CGFloat = CGFloat(arc4random_uniform(667))
let num2 : CGFloat = CGFloat(arc4random_uniform(375))
self.label1.frame.origin.y = num1
self.label1.frame.origin.x = num2
}, completion: {(bool) in
self.animate1()
print("Animation1 completed")
})
}
func timerExample() {
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
label1.text = String(Int(label1.text!)! + 1)
}`
By default view objects block user interaction while an animation is "in flight". You need to use one of the "long form" animation methods, and pass in the option .allowUserInteraction. Something like this:
UIView.animate(duration: 0.5,
delay: 0.0,
options: .allowUserInteraction,
animations: {
myView.alpha = 0.5
})
Note, however, that if what you're animating is a view's position, the user won't be able to tap on the view object as it moves. That's because a position animation does not really animate the object from one place to another over time. It just creates that appearance. Behind the scenes, the object actually jumps to it's final position the moment the animation begins.
If you need to be able to tap/drag/swipe on objects while they're moving you will have to do that yourself. What you do is put a gesture recognizer on the parent view that encloses the entire range of motion (possibly the whole screen.) Then you need to use the presentation layer of your animating view's layer, translate the coordinates of the point from the gesture recognizer's coordinate space to the layer's coordinate space, and use the layer's hitTest method to figure out if the point is on the layer or not.
I have a project on Github called iOS-CAAnimation-group-demo that does something like that (It animates an image view along a complex path and you can tap on the image view to pause the animation while it's "in flight".
It's from several years ago, so it's written in Objective-C, but it should help to at least illustrate the technique.
I made a simple project, with swipe gesture recogniser and animation. I made my label to move and every 3 second increase number. With every swipe I need to decrease the number. My gesture recogniser object is tied with label, i.e. it works only in label bounds. When prog is working without animation everything is ok, but when it;s animated an is moving my gesture recogniser is doing nothing. How to make a gesture recogniser work at the same time as animation, i.e. while animated to respond to my swipes. Need help.
`
#IBOutlet weak var label1: UILabel!
var number : Int = 0
var timer = Timer()
#IBAction func label1SwipeRight(_ sender: UISwipeGestureRecognizer) {
number += 1
label1.text = String(number)
}
func animate1() {
UIView.animate(withDuration: 4.0, delay: 0.0, options: .allowUserInteraction, animations: {
let num1 : CGFloat = CGFloat(arc4random_uniform(667))
let num2 : CGFloat = CGFloat(arc4random_uniform(375))
self.label1.frame.origin.y = num1
self.label1.frame.origin.x = num2
}, completion: {(bool) in
self.animate1()
print("Animation1 completed")
})
}
func timerExample() {
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
label1.text = String(Int(label1.text!)! + 1)
}`
By default view objects block user interaction while an animation is "in flight". You need to use one of the "long form" animation methods, and pass in the option .allowUserInteraction. Something like this:
UIView.animate(duration: 0.5,
delay: 0.0,
options: .allowUserInteraction,
animations: {
myView.alpha = 0.5
})
Note, however, that if what you're animating is a view's position, the user won't be able to tap on the view object as it moves. That's because a position animation does not really animate the object from one place to another over time. It just creates that appearance. Behind the scenes, the object actually jumps to it's final position the moment the animation begins.
If you need to be able to tap/drag/swipe on objects while they're moving you will have to do that yourself. What you do is put a gesture recognizer on the parent view that encloses the entire range of motion (possibly the whole screen.) Then you need to use the presentation layer of your animating view's layer, translate the coordinates of the point from the gesture recognizer's coordinate space to the layer's coordinate space, and use the layer's hitTest method to figure out if the point is on the layer or not.
I have a project on Github called iOS-CAAnimation-group-demo that does something like that (It animates an image view along a complex path and you can tap on the image view to pause the animation while it's "in flight".
It's from several years ago, so it's written in Objective-C, but it should help to at least illustrate the technique.
I found that using navigation controller, the default back swipe gesture will be introduced when user swipes from the left side of the screen, which will make the app go back to the previous view.
Is there a way to start from any point on the screen other than starting from the left side of the screen?
Thanks.
Just add your own gesture recogniser and watch for gestures which velocity is greater in the x direction and going from left to right.
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer.isMemberOfClass(UIPanGestureRecognizer)){
let point:CGPoint = (gestureRecognizer as UIPanGestureRecognizer).velocityInView(self)
if (abs(point.x) > abs(point.y)){
return true
}
}
}
Handle the gesture over here:
func handlePanGestureRecognizer(gesture: UIPanGestureRecognizer){
let translation:CGPoint = gesture.translationInView(self)
if(translation.x > 0){
//its going from left to right...
//...Do stuff over here to handle the gesture, for eg. push the view from the stack
}
}
I’m trying to create an application which duplicates the ability of Apple’s Photos app (iPhone) to zoom, pan and scroll through photographic images. (I also want to use the same controls when viewing pdfs and other documents.) I got the tap gesture to show/hide the navigation bar and the swipe gesture to scroll through the images from left to right & vice versa. Then I got the pinch gesture to zoom in and out, but when I added the pan gesture to move around within a zoomed image, the swipe gesture quit working.
I found potential solutions elsewhere on StackOverflow including the use of shouldRecognizeSimultaneouslyWithGestureRecognizer, but so far I have not been able to resolve the conflict. Any suggestions?
Here's the code:
func gestureRecognizer(UIPanGestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer UISwipeGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
#IBAction func handlePinch(sender: UIPinchGestureRecognizer) {
sender.view!.transform = CGAffineTransformScale(sender.view!.transform, sender.scale, sender.scale)
sender.scale = 1
}
#IBAction func handlePan(sender: UIPanGestureRecognizer) {
self.view.bringSubviewToFront(sender.view!)
var translation = sender.translationInView(self.view)
sender.view!.center = CGPointMake(sender.view!.center.x + translation.x, sender.view!.center.y + translation.y)
sender.setTranslation(CGPointZero, inView: self.view)
}
#IBAction func handleSwipeRight(sender: UISwipeGestureRecognizer) {
if (self.index == 0) {
self.index = ((photos.count) - 1);
}
else
{
self.index--;
}
// requireGestureRecognizerToFail(panGesture)
setImage()
}
You do not want shouldRecognizeSimultaneouslyWithGestureRecognizer: (which allows two gestures to happen simultaneously). That's useful if you want to, for example, simultaneously pinch and pan. But the simultaneous gestures will not help in this scenario where you are panning and swiping at the same time. (If anything, recognizing those simultaneously probably confuses the situation.)
Instead, you might want to establish precedence of swipe and pan gestures (e.g. only pan if swipe fails) with requireGestureRecognizerToFail:.
Or better, retire the swipe gesture entirely and use solely the pan gesture, which, if you're zoomed out will be an interactive gesture to navigate from one image to the next, and if zoomed in, pans the image. Interactive pan gestures generally a more satisfying UX, anyway; e.g., if swiping from one photo to the next, be able to stop mid pan gesture and go back. If you look at the Photos.app, it's actually using a pan gesture to swipe from one image to another, not a swipe gesture.
I discovered a tutorial at http://www.raywenderlich.com/76436/use-uiscrollview-scroll-zoom-content-swift that does a great job of introducing UIScrollView as a way of combining zooming, panning and paging in Swift. I recommend it for anyone trying to learn how to make these gestures work well together.
In similar case I've used another approach: extended pan gesture to support swipe:
// in handlePan()
switch recognizer.state {
struct Holder {
static var lastTranslate : CGFloat = 0
static var prevTranslate : CGFloat = 0
static var lastTime : TimeInterval = 0
static var prevTime : TimeInterval = 0
}
case .began:
Holder.lastTime = Date.timeIntervalSinceReferenceDate
Holder.lastTranslate = translation.y
Holder.prevTime = Holder.lastTime
Holder.prevTranslate = Holder.lastTranslate
//perform appropriate pan action
case .changed:
Holder.prevTime = Holder.lastTime
Holder.prevTranslate = Holder.lastTranslate
Holder.lastTime = Date.timeIntervalSinceReferenceDate
Holder.lastTranslate = translation.y
//perform appropriate pan action
case .ended ,.cancelled:
let seconds = CGFloat(Date.timeIntervalSinceReferenceDate) - CGFloat(Holder.prevTime)
var swipeVelocity : CGFloat = 0
if seconds > 0 {
swipeVelocity = (translation.y - Holder.prevTranslate)/seconds
}
var shouldSwipe : Bool = false
if Swift.abs(swipeVelocity) > velocityThreshold {
shouldSwipe = swipeVelocity < 0
}
if shouldSwipe {
// perform swipe action
} else {
// perform appropriate pan action
}
default:
print("Unsupported")
}
All you need to do is to find appropriate velocityTreshold for your swipe gesture