Cutting diagonal movement from pan gesture - ios

I'm trying to lock my image I'm dragging (objectDragging) to the x or y center, depending which way I'm moving or influencing it to move. If I'm influencing it to the right, I want it to lock on its initial Y position, and only allow it to go right (or back left, on a straight line). If I'm influencing it upward, lock its x position and allow it to move straight up or down on the exact line. I'm using a switch for my movement handling, with lots of code in the .end case, so I didn't want to include a lot of code. Just hoping for an idea — wasn't able to find a lot.
switch(recognizer.state) {
objectDragging = (recognizer.view?.tag)!
case .began:
initPosx = Int(recognizer.view!.center.x)
initPosy = Int(recognizer.view!.center.y)
case .changed:
...

After the suggestion of using velocity from Paulw11 I've had success limiting the direction of the image I'm moving by deciding which way we are initially moving the image (in .began) and then locking the x or y (in .changed). Incase anyone comes across this with a similar issue, heres the gist of my solution that is working well for my application:
switch(recognizer.state) {
case .began:
let velocity = recognizer.velocity(in: self.view)
if (velocity.x > velocity.y) && (velocity.x > 0) {
print("moving right")
myBlocks[objectDragging].center.y = CGFloat(initPosy)
movingRight = true
}
else if ((abs(velocity.x)) > velocity.y) && (velocity.x < 0) {
print("moving left")
myBlocks[objectDragging].center.y = CGFloat(initPosy)
movingLeft = true
}
else if (velocity.y > velocity.x) && (velocity.y > 0) {
print("moving down")
myBlocks[objectDragging].center.x = CGFloat(initPosx)
movingDown = true
}
else if ((abs(velocity.y)) > velocity.x) && (velocity.y < 0){
print("moving up")
myBlocks[objectDragging].center.x = CGFloat(initPosx)
print(abs(velocity.y))
movingUp = true
}
case .changed:
if (movingRight == true) {
myBlocks[objectDragging].center.y = CGFloat(initPosy)
}
else if (movingLeft == true) {
myBlocks[objectDragging].center.y = CGFloat(initPosy)
}
else if (movingDown == true) {
myBlocks[objectDragging].center.x = CGFloat(initPosx)
}
else if (movingUp == true) {
myBlocks[objectDragging].center.x = CGFloat(initPosx)
}
case .ended:
movingUp = false
movingLeft = false
movingRight = false
movingDown = false

Related

Bottom Sheet ScrollView Pan & Scroll

I'm interested in how Apple's Maps app is able to transition from the panning gesture on the sheet to scrolling content gesture in a scrollView or tableView. I put this up as a sort of first stab at recreating it. I've been unable to go from panning the entire sheet to scrolling content when the sheet docks without lifting my finger.
There are a few questions like this and a few libraries out there, but I haven't seen the solution to transition part.
#objc func panGesture(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: view)
switch recognizer.state {
// ** Tracking starting offset
case .began: startingOffset = heightConstraint?.constant ?? 0
// ** Toggle panGesture and tableView to properly switch between gestures
case .changed:
let offset = startingOffset - translation.y
var minOffset: CGFloat = 0
mapView.alpha = 1 - (0.5 * (offset / maxOffset))
// This adds elasticity
if offset < 0 {
minOffset = -(0 - offset)/3
}
// ** Track bottom sheet with pan gesture by finding the diff
// be tween translation and starting offset, then constraint
// this value to be between our top margin and min height
let currentOffset = min(maxOffset, max(minOffset, offset))
heightConstraint?.constant = currentOffset
// ** `offset` == 0 means the sheet is minimized
// `offset` == `maxOffset` means the sheet is open
if currentOffset == 0 {
tableView.contentOffset = .zero
tableView.isScrollEnabled = false
} else if currentOffset == maxOffset {
panGesture.isEnabled = false
panGesture.isEnabled = true
tableView.isScrollEnabled = true
}
case .ended, .cancelled:
guard let offset = heightConstraint?.constant else { return }
// ** Handle last position - if nearer to the top finish out the
// animation to the top, and vice versa
var finalOffset: CGFloat = offset > maxOffset/2 ? maxOffset : 0
let velocity = recognizer.velocity(in: view).y
// ** Toggle tableView scrollability
// Handle "flick" action using `velocity`
if velocity < -100 {
finalOffset = maxOffset
tableView.isScrollEnabled = true
} else if offset > maxOffset/2 && velocity > 200 {
finalOffset = 0
tableView.isScrollEnabled = false
} else {
tableView.isScrollEnabled = offset > maxOffset/2
}
// Dismiss keyboard if dismissing sheet
if finalOffset == 0 {
_ = searchField.resignFirstResponder()
}
// ** Animate to top or bottom docking position when gesture ends
// or is cancelled
heightConstraint?.constant = finalOffset
UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2, options: [.curveEaseOut, .allowUserInteraction], animations: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.view.layoutIfNeeded()
strongSelf.mapView.alpha = 1 - (0.5 * (finalOffset / strongSelf.maxOffset))
}, completion: nil)
default: ()
}
}
//UIScrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let offset = heightConstraint?.constant else { return }
// ** Disable panning if scrollView isn't at the top
panGesture.isEnabled = tableView.contentOffset.y <= 0 || offset == 0
// ** Don't scroll if bottom sheet is panning down
if scrollView.contentOffset.y < 0 {
scrollView.isScrollEnabled = false
panGesture.isEnabled = true
}
}
Help?

Move SKSpriteNode with UIPanGestureRecognizer continuously

I am trying to move an SKSpriteNode continuously in a simple 2-dimensional SKScene. This involves both moving an array containing all background-nodes in the opposite direction of a UIGestureRecognizer's pan and animating the node at the same time.
I have already realized the movement by implementing the nodes and recognizer and calling its method when panning:
#objc func didPan(gesture: UIPanGestureRecognizer) {
let pan = gesture.velocity(in: self.view)
if gesture.state == .changed {
for sprites in levelSprites {
if pan.x > 0 && pan.x > pan.y {
sprites.run(SKAction.moveTo(x: sprites.position.x - 40, duration: 0.5))
self.player.run(SKAction(named: "WalkRight")!)
} else if pan.x < 0 && pan.x < pan.y {
sprites.run(SKAction.moveTo(x: sprites.position.x + 40, duration: 0.5))
self.player.run(SKAction(named: "WalkLeft")!)
} else if pan.y < 0 && pan.x > pan.y {
sprites.run(SKAction.moveTo(y: sprites.position.y - 30, duration: 0.5))
self.player.run(SKAction(named: "WalkUp")!)
} else if pan.y > 0 && pan.x < pan.y {
sprites.run(SKAction.moveTo(y: sprites.position.y + 30, duration: 0.5))
self.player.run(SKAction(named: "WalkDown")!)
}
}
}
}
The problem I'm facing is that this method only moves and animates the sprites for the value I set and the limited time that I have to maintain to accurately match the animations length, when I actually want to move them for the duration of the pan.
I deliberately implemented a recognizer for pan indstead of swipes, scince the scene will also contain a joystick for controlling the movement later on.
Is there maybe an easier solution to what I'm trying to accomplish not using a UIGestureRecognizer?
didPan() is called multiple times with state .changed during a single pan from the user, but not at a regular frequency.
Instead, you should declare two global state variables that save the current horizontal and vertical directions (left to right, or right to left, for the first one, for instance) and update these variables each time didPan() is called.
You should run an SKAction instance only when a direction changes and those SKAction instances should loop indefinitely, not have a one-time only specific duration of 0.5 sec (see SKAction.repeatForever() in the SceneKit API manual).
Also, you need to be sure to remove the already running moving action before running the next movement action.
Finally, call removeAllActions() when didPan() is called with state .ended.
enum VerticalState{
case .none
case .topToBottom
case .bottomToTop
}
enum HorizontalState{
case .none
case .leftToRight
case .rightToLeft
}
var verticalState = VerticalState.none
var horizontalState = HorizontalState.none
#objc func didPan(gesture: UIPanGestureRecognizer) {
let pan = gesture.velocity(in: self.view)
if gesture.state == .changed {
for sprites in levelSprites {
if pan.x > 0 && pan.x > pan.y && horizontalState <> .leftToRight{
horizontalState = .none
verticalState = .none
} else if pan.x < 0 && pan.x < pan.y && horizontalState <> .rightToLeft{
horizontalState = .rightToLeft
verticalState = .none
} else if pan.y < 0 && pan.x > pan.y && verticalState <> .bottomToTop{
horizontalState = .none
verticalState = .bottomToTop
} else if pan.y > 0 && pan.x < pan.y && verticalState <> topToBottom{
horizontalState = .none
verticalState = .topToBottom
}
switch horizontalState
{
case .leftToRight:
sprites.removeActionForKey("movement")
self.player.removeActionForKey("movement")
sprites.run(SKAction.repeatForever(SKAction.moveBy(x: -40, y:0, duration: 0.5), forKey:"movement"))
self.player.run(SKAction(named: "WalkRight")!, forKey:"movement")
case .rightToLeft:
sprites.removeActionForKey("movement")
self.player.removeActionForKey("movement")
sprites.run(SKAction.repeatForever(SKAction.moveBy(x: 40, y:0,, duration: 0.5), forKey:"movement"))
self.player.run(SKAction(named: "WalkLeft")!, forKey:"movement")
case .none:
break
}
switch verticalState
{
case .topToBottom:
sprites.removeActionForKey("movement")
self.player.removeActionForKey("movement")
sprites.run(SKAction.repeatForever(SKAction.moveBy(x: 0, y:-30, duration: 0.5), forKey:"movement"))
self.player.run(SKAction(named: "WalkUp")!, forKey:"movement")
case .bottomToTop:
sprites.removeActionForKey("movement")
self.player.removeActionForKey("movement")
sprites.run(SKAction.repeatForever(SKAction.moveBy(x: 0, y:30,, duration: 0.5), forKey:"movement"))
self.player.run(SKAction(named: "WalkDown")!, forKey:"movement")
case .none:
break
}
}
}else if gesture.state == .ended {
sprites.removeActionForKey("movement")
self.player.removeActionForKey("movement")
}
}

Snapchat like tableview scrolling over header

i have been trying to implement scrolling behaviour like snapchat tableview where on scrolling down the tableview moves behind top header and the lists scrolls.
I have tried below code :
But it is not as smooth as snapchat, if anyone has any idea please share.
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let CONTAINER_HEIGHT:CGFloat = 44
let translation = scrollView.panGestureRecognizer.translation(in: scrollView.panGestureRecognizer.view!)
if(translation.y <= 0)
{
//UP DIRECTION
let percent = -translation.y / scrollView.panGestureRecognizer.view!.bounds.size.height
print("PERCENT : \(percent)")
switch (scrollView.panGestureRecognizer.state)
{
case .began:
break;
case .changed:
if(percent <= 1.0 && percent >= 0)
{
var panSize = (CONTAINER_HEIGHT)*percent
panSize = CONTAINER_HEIGHT-panSize
print("Pan Value : \(panSize) -- Percent : \(percent)" )
//panSize += 44
self.bottomSlideTopConstraint.constant = panSize
self.view.layoutIfNeeded()
}
break;
// User is currently dragging the scroll view
case .ended,.cancelled:
let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView.panGestureRecognizer.view)
if (percent > 0.5 && velocity.y == 0) || velocity.y > 0
{
print("ENDED TRUE")
self.bottomSlideTopConstraint.constant = 0
UIView.animate(withDuration: 0.2, animations: {
self.view.layoutIfNeeded()
})
}
else
{
print("CANCELLED")
self.bottomSlideTopConstraint.constant = 44
UIView.animate(withDuration: 0.2, animations: {
self.view.layoutIfNeeded()
})
}
default:
break;
}
}
}

Detect UILongPressGestureRecognizer direction not compare initial point

if (gesture.state == UIGestureRecognizerStateBegan) {
_initial = [gesture locationInView:self.view];
}else if (gesture.state == UIGestureRecognizerStateChanged){
CGPoint p = [gesture locationInView:self.view];
double dy = p.y - _initial.y;
if (dy > 0) {
NSLog(#"Finger moved to the up");
}
else {
NSLog(#"Finger moved to the down");
}
}
This is my method to detect UILongPressGestureRecognizer direction, but I want
detect direction not compare initial point but compare stateChanged point.
It's hard to describe, like : form 0 to 7 is up, but 7 to -5 is down and then -5 to -2 is up.
You should use translationInView.
CGPoint translation = [gesture translationInView:view.superview];
if (translation.y>0) {
// moving up
} else {
// moving down
}
A friend of mine give me some advice and it works well:
CGPoint point = [gestureRecognizer locationInView:self.view];
if(gestureRecognizer.state == UIGestureRecognizerStateBegan){
_lastY = point.y;
}else if(gestureRecognizer.state == UIGestureRecognizerStateChanged) {
double y = point.y - _lastY;
if (y < 0.f) {
NSLog(#"****up*****");
}else{
NSLog(#"****down*****");
}
_lastY = point.y;
}
I know it's not a good one but it works, any idea?
Swift 3
Here's a complete function to determine left or right movement of UILongPressGestureRecognizer:
#objc func swipePress (_ sender: UILongPressGestureRecognizer) {
let location = sender.location(in: self.view)
print ("Location : \(location.x)")
if sender.state == .ended {
print("swipe ended")
buttonCreated = false
animateRemoveArrows()
if gestureDirection == "right" {
print("going forward")
if myWebView.canGoForward {
myWebView.goForward() //go forward
} else {
}
} else if gestureDirection == "left" {
print("going backward")
if myWebView.canGoBack {
myWebView.goBack() //go back
} else {
}
} else {
}
} else if sender.state == .began {
gestureDirection = "" // reset direction
print("swipe started")
firstPoisitionX = location.x
firstPoisitionY = location.y
print ("Last x: \(firstPoisitionX)")
} else if sender.state == .changed {
if location.y > (firstPoisitionY + 100) {
print("going up or down")
} else if location.y < (firstPoisitionY - 100) {
print("going up or down")
} else if location.x > (firstPoisitionX + 50) {
print("going right")
if buttonCreated == false { // only create one button
showArrow(direction: "right")
print(rightArrow.center)
gestureDirection = "right"
buttonCreated = true
}
} else if location.x < (firstPoisitionX - 50) {
print("going left")
if buttonCreated == false { // only create one button
showArrow(direction: "left")
print(leftArrow.center)
gestureDirection = "left"
buttonCreated = true
}
}
}
}

Swift UIPanGestureRecognizer right to left only

I'm building a slide out menu using a tutorial from Ray Wenderlich and can't figure out how to disable left to right panning but keep right to left? I remove addLeftPanelViewController() but it still shows the pan, just not working correctly. I don't want anything to move, or unhide on the left to right.
Thanks for any help.
override func viewDidLoad() {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: "handlePanGesture:")
centerNavigationController.view.addGestureRecognizer(panGestureRecognizer)
}
let gestureIsDraggingFromLeftToRight = (recognizer.velocityInView(view).x > 0)
 
switch(recognizer.state) {
case .Began:
if (currentState == .BothCollapsed) {
if (gestureIsDraggingFromLeftToRight) {
addLeftPanelViewController()
} else {
addRightPanelViewController()
}
 
showShadowForCenterViewController(true)
}
case .Changed:
recognizer.view!.center.x = recognizer.view!.center.x + recognizer.translationInView(view).x
recognizer.setTranslation(CGPointZero, inView: view)
case .Ended:
if (leftViewController != nil) {
// animate the side panel open or closed based on whether the view has moved more or less than halfway
let hasMovedGreaterThanHalfway = recognizer.view!.center.x > view.bounds.size.width
animateLeftPanel(shouldExpand: hasMovedGreaterThanHalfway)
} else if (rightViewController != nil) {
let hasMovedGreaterThanHalfway = recognizer.view!.center.x < 0
animateRightPanel(shouldExpand: hasMovedGreaterThanHalfway)
}
default:
break
}
try this
add Expending on the SlideOutState enum
enum SlideOutState {
case BothCollapsed
case SettingPanelExpanded
case Expanding
}
then
switch(recognizer.state) {
case .Began:
if (currentState == .BothCollapsed) {
if (gestureIsDraggingFromLeftToRight) {
addSettingPanelViewController()
showShadowForCenterViewController(true)
currentState = .Expanding
}
}
if (currentState == .SettingPanelExpanded) {
if (gestureIsDraggingFromLeftToRight==false) {
currentState = .Expanding
}
}
case .Changed:
if (currentState == .Expanding){
println(recognizer.velocityInView(view).x)
recognizer.view!.center.x = recognizer.view!.center.x + recognizer.translationInView(view).x
recognizer.setTranslation(CGPointZero, inView: view)
}
case .Ended:
if (settingViewController != nil) {
// animate the side panel open or closed based on whether the view has moved more or less than halfway
let hasMovedGreaterThanHalfway = recognizer.view!.center.x > view.bounds.size.width
animateSettingPanel(shouldExpand: hasMovedGreaterThanHalfway)
}
default:
break
}
Wrap your switch statement in an if condition:
if currentState == .LeftPanelExpanded || gestureIsDraggingFromLeftToRight {
//your existing switch statement
}

Resources