Use UIPageViewController together with UIPanGestureRecognizer - swift

My application is a page based application using UIPageViewController. Now I would like to able to move subviews vertically on my pages. After implementing a UIPanGestureRecognizer I was able to move the subviews but now my horizontal swiping to move through the pages does not work...
How can I combine those two gestures?
This is my panGestureRecognizer:
ContentViewController.swift
var panSwipe = UIPanGestureRecognizer(target: self, action: Selector("handlePanSwipe:"))
view.addGestureRecognizer(panSwipe)
...
func handlePanSwipe(sender:UIPanGestureRecognizer) {
var translation = sender.translationInView(self.view)
if sender.state == UIGestureRecognizerState.Began {
startLocation = sender.locationInView(self.view)
} else if (sender.state == UIGestureRecognizerState.Changed) {
self.settingsView.frame.origin.y = self.newSettingsFrameOrigin!.y + translation.y
} else if (sender.state == UIGestureRecognizerState.Ended) {
self.newSettingsFrameOrigin = self.settingsView.frame.origin
}
I want to be able to move the dark green subview up and down:
Thanks in advance!

Related

How do I zoom the view when the player zooms in or out?

So I just learned how to make a view zoom in and out, and I did it for my gamescene but theres just a black box right outside the view, like this
enter image description here
Here's my code that I used to allow zooming in and out, this is in my didMove function
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(sender:)))
view.addGestureRecognizer(pinch)
and this is inside no function, just the GameScene class
#objc func handlePinch(sender: UIPinchGestureRecognizer) {
guard sender.view != nil else { return }
if sender.state == .began || sender.state == .changed {
sender.view?.transform = (sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale))!
sender.scale = 1.0
}
}
How would I change the view whenever someone zooms in or out?

Swift - UIPanGestureRecognizer selecting all layers when only want the top layer selected

I have function which creates a drag line to connect 2 buttons to each other. This works fine but if some buttons overlap each other, it will select both if I drag over where they overlap. I only want to connect the top button.
I think the issue is with the sender.location selecting layers on top and below. Is there a way to tell the sender.location to only select the top view? Thanks for any input and direction
func addPanReconiser(view: UIView){
let pan = UIPanGestureRecognizer(target: self, action: #selector(DesignViewController.panGestureCalled(_:)))
view.addGestureRecognizer(pan)
}
#objc func panGestureCalled(_ sender: UIPanGestureRecognizer) {
let currentPanPoint = sender.location(in: self.view)
switch sender.state {
case .began:
panGestureStartPoint = currentPanPoint
self.view.layer.addSublayer(lineShape)
case .changed:
let linePath = UIBezierPath()
linePath.move(to: panGestureStartPoint)
linePath.addLine(to: currentPanPoint)
lineShape.path = linePath.cgPath
lineShape.path = CGPath.barbell(from: panGestureStartPoint, to: currentPanPoint, barThickness: 2.0, bellRadius: 6.0)
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point) {
button.layer.borderWidth = 4
button.layer.borderColor = UIColor.blue.cgColor
} else {
button.layer.borderWidth = 0
button.layer.borderColor = UIColor.clear.cgColor
}
}
case .ended:
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point){
//DO my Action here
lineShape.path = nil
lineShape.removeFromSuperlayer()
}
}
default: break
}
}
}
Note: some of the lines of codes are from custom extensions. I kept them in as they were self explanatory.
Thanks for the help
There is a way to walk around. It seems like you simply want your gesture end up at one button above all the others, thus by adding a var outside the loop and each time a button picked, comparing with the var of its level at z.
case .ended:
var pickedButton: UIButton?
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point){
if pickedButton == nil {
pickedButton = button
} else {
if let parent = button.superView, parent.subviews.firstIndex(of: button) > parent.subviews.firstIndex(of: pickedButton!) {
pickedButton = button
}
}
}
}
//DO my Action with pickedButton here
lineShape.path = nil
lineShape.removeFromSuperlayer()
A UIView has a property called subViews where elements with higher indexes are in front of the ones with lower indexes. For instance, subView at index 1 is in front of subView with index 0.
That being said, to get the button that's on top, you should sort your buttonArray the same way subViews property of UIView is organized. Assuming that your buttons are all siblings of the same UIView (this might not be necessarily the case, but you can tweak them so you get them sorted correctly):
var buttonArray = view.subviews.compactMap { $0 as? UIButton }
Thus, keeping your buttonArray sorted that way, the button you want is the one that contains let point = sender.location(in: button) with higher index in the array.

Conflict between Pan Gesture and Tap Gestures

I'm currently working on a game that uses UIGestureRecognizers. I'm using the pan gesture to move the player around and the tap gesture to detect other UI button touches. Everything seems to work fine except that there is a conflict between the 2 gestures. Whenever the player is on the move (pan gesture gets recognized) the game ignores all my tap gestures (Once the pan gesture is recognized, the view won't recognize tap gestures).
Can someone please show me how to make the 2 gestures work together. I want the player to stop moving when a UI button gets tapped. In another word, I want to cancel the pan gesture whenever a tap gesture is recognized.
Thank you so much in advance!
Here is how I setup the 2 gestures:
let singleTap = UITapGestureRecognizer(target: self, action: #selector(doSingleTap))
singleTap.numberOfTapsRequired = 1
singleTap.delegate = self
self.view?.addGestureRecognizer(singleTap)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
panGesture.minimumNumberOfTouches = 1
panGesture.delegate = self
self.view?.addGestureRecognizer(panGesture)
#objc func handlePan(gestureReconizer: UIPanGestureRecognizer) {
if isPaused || player.isInAction {return}
let translation = gestureReconizer.translation(in: self.view)
if gestureReconizer.state == .changed {
let angle = 180 + (atan2(-translation.x, translation.y) * (180/3.14159))
player.movementAngle = angle
player.atState = .Walk
}
if gestureReconizer.state == .ended {
player.movementAngle = 0.0
if player.atState == .Walk {
player.atState = .Idle
}
}
}
#objc func doSingleTap(gestureReconizer: UITapGestureRecognizer) {
let originaTapLocation = gestureReconizer.location(in: self.view)
let location = convertPoint(fromView: originaTapLocation)
let node = atPoint(location)
switch node.name {
case "HeroAvatar":
//do stuff here
break
case "Fire":
//do stuff here
break
case "Potion":
//do stuff here
break
default:
break
}
}
You need to implement delegate method of UIGestureRecognizerDelegate like below:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// Don't recognize a single tap gesture until a pan gesture fails.
if gestureRecognizer == singleTap &&
otherGestureRecognizer == panGesture {
return true
}
return false
}
Hope this will work for you :)
For more info Apple Doc Preferring One Gesture Over Another

Swift 3 Draggable UIButton

Apologies as I'm still learning the basics of Swift.
I'm trying to move a button when I drag it which sounds simple. I can't figure out how to pass along the sender information to the drag function so I can associate it with the button that is being dragged.
I create multiple one word buttons which are only text and attach a pan gesture recognizer to each of them:
let pan = UIPanGestureRecognizer(target: self, action: #selector(panButton(_:)))
let word = UIButton(type: .system)
word.addGestureRecognizer(pan)
I've created this function to trigger when the button is moved:
func panButton(sender: UIPanGestureRecognizer){
if sender.state == .began {
//wordButtonCenter = button.center // store old button center
} else if sender.state == .ended || sender.state == .failed || sender.state == .cancelled {
//button.center = wordButtonCenter // restore button center
} else {
let location = sender.location(in: view) // get pan location
//button.center = location // set button to where finger is
}
}
I'm getting the following error:
Use of unresolved identifier 'panButton'
First of all, your action needs to be a selector in Swift 3. So that would look something like this:
let pan = UIPanGestureRecognizer(target: self, action: #selector(panButton(_:))
Also, you can't pass the value of a button through a selector, so you would need to change your func to be:
func panButton(sender: UIPanGestureRecognizer){
...
}
If you're wondering how you are supposed to find the button if you can't pass it as a parameter, then you might consider using tags.
As #benjamin points out in Swift 3 you needs to be a selector. I've updated my code to the following in order to extract the button tag:
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGestureHandler(panGesture:)))
panGesture.minimumNumberOfTouches = 1
let word = UIButton(type: .system)
With the following selector:
#objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {
let buttonTag = (recognizer.view?.tag)!
if let button = view.viewWithTag(buttonTag) as? UIButton {
if recognizer.state == .began {
wordButtonCenter = button.center // store old button center
} else if recognizer.state == .ended || recognizer.state == .failed || recognizer.state == .cancelled {
button.center = wordButtonCenter // restore button center
} else {
let location = recognizer.location(in: view) // get pan location
button.center = location // set button to where finger is
}
}
}

How to have a UISwipeGestureRecognizer AND UIPanGestureRecognizer work on the same view

How would you setup the gesture recognizers so that you could have a UISwipeGestureRecognizer and a UIPanGestureRecognizer work at the same time? Such that if you touch and move quickly (quick swipe) it detects the gesture as a swipe but if you touch then move (short delay between touch & move) it detects it as a pan?
I've tried various permutations of requireGestureRecognizerToFail and that didn't help exactly, it made it so that if the SwipeGesture was left then my pan gesture would work up, down and right but any movement left was detected by the swipe gesture.
You're going to want to set one of the two UIGestureRecognizer's delegates to an object that makes sense (likely self) then listen, and return YES for this method:
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
This method is called when recognition of a gesture by either gestureRecognizer or otherGestureRecognizer would block the other gesture recognizer from recognizing its gesture. Note that returning YES is guaranteed to allow simultaneous recognition; returning NO, on the other hand, is not guaranteed to prevent simultaneous recognition because the other gesture recognizer's delegate may return YES.
By default, when the user attempts to swipe, the gesture is interpreted as a pan. This is because a swiping gesture meets the necessary conditions to be interpreted as a pan (a continuous gesture) before it meets the necessary conditions to be interpreted as a swipe (a discrete gesture).
You need to indicate a relationship between two gesture recognizers by calling the requireGestureRecognizerToFail: method on the gesture recognizer that you want to delay
[self.panRecognizer requireGestureRecognizerToFail:self.swipeRecognizer];
Using a pan recognizer to detect swipping and panning:
- (void)setupRecognizer
{
UIPanGestureRecognizer* panSwipeRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanSwipe:)];
// Here you can customize for example the minimum and maximum number of fingers required
panSwipeRecognizer.minimumNumberOfTouches = 2;
[targetView addGestureRecognizer:panSwipeRecognizer];
}
#define SWIPE_UP_THRESHOLD -1000.0f
#define SWIPE_DOWN_THRESHOLD 1000.0f
#define SWIPE_LEFT_THRESHOLD -1000.0f
#define SWIPE_RIGHT_THRESHOLD 1000.0f
- (void)handlePanSwipe:(UIPanGestureRecognizer*)recognizer
{
// Get the translation in the view
CGPoint t = [recognizer translationInView:recognizer.view];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
// TODO: Here, you should translate your target view using this translation
someView.center = CGPointMake(someView.center.x + t.x, someView.center.y + t.y);
// But also, detect the swipe gesture
if (recognizer.state == UIGestureRecognizerStateEnded)
{
CGPoint vel = [recognizer velocityInView:recognizer.view];
if (vel.x < SWIPE_LEFT_THRESHOLD)
{
// TODO: Detected a swipe to the left
}
else if (vel.x > SWIPE_RIGHT_THRESHOLD)
{
// TODO: Detected a swipe to the right
}
else if (vel.y < SWIPE_UP_THRESHOLD)
{
// TODO: Detected a swipe up
}
else if (vel.y > SWIPE_DOWN_THRESHOLD)
{
// TODO: Detected a swipe down
}
else
{
// TODO:
// Here, the user lifted the finger/fingers but didn't swipe.
// If you need you can implement a snapping behaviour, where based on the location of your targetView,
// you focus back on the targetView or on some next view.
// It's your call
}
}
}
Here is a full solution for detecting pan and swipe directions (utilizing 2cupsOfTech's swipeThreshold logic):
public enum PanSwipeDirection: Int {
case up, down, left, right, upSwipe, downSwipe, leftSwipe, rightSwipe
public var isSwipe: Bool { return [.upSwipe, .downSwipe, .leftSwipe, .rightSwipe].contains(self) }
public var isVertical: Bool { return [.up, .down, .upSwipe, .downSwipe].contains(self) }
public var isHorizontal: Bool { return !isVertical }
}
public extension UIPanGestureRecognizer {
var direction: PanSwipeDirection? {
let SwipeThreshold: CGFloat = 1000
let velocity = self.velocity(in: view)
let isVertical = abs(velocity.y) > abs(velocity.x)
switch (isVertical, velocity.x, velocity.y) {
case (true, _, let y) where y < 0: return y < -SwipeThreshold ? .upSwipe : .up
case (true, _, let y) where y > 0: return y > SwipeThreshold ? .downSwipe : .down
case (false, let x, _) where x > 0: return x > SwipeThreshold ? .rightSwipe : .right
case (false, let x, _) where x < 0: return x < -SwipeThreshold ? .leftSwipe : .left
default: return nil
}
}
}
Usage:
#IBAction func handlePanOrSwipe(recognizer: UIPanGestureRecognizer) {
if let direction = recognizer.direction {
if direction == .leftSwipe {
//swiped left
} else if direction == .up {
//panned up
} else if direction.isVertical && direction.isSwipe {
//swiped vertically
}
}
}