Swift UIPanGestureRecognizer right to left only - ios

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
}

Related

viewController.view does not showing but it appears when ViewDebugMode

I'm creating Slide Menu using PanGesture and addChild.
Although ParentViewController was able to slide to the left, MenuViewController placed just to the right of it is not displayed.
However, when you check with ViewDebug, it certainly exists and is placed in the expected location.
But why isn't it displayed on the actual screen?
👇 when view was panned
👇 when View Debug and this button tapped
class ParentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.isUserInteractionEnabled = true
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(panGesturePanned)))
let menuViewController = MenuViewController()
menuViewController.view.frame = CGRect(
x: view.frame.maxX,
y: 0,
width: view.bounds.width,
height: UIScreen.main.bounds.height)
view.addSubview(menuViewController.view)
addChild(menuViewController)
menuViewController.didMove(toParent: self)
}
#objc private func panGesturePanned(gesture: UIPanGestureRecognizer) {
guard let gestureView = gesture.view else { return }
switch gesture.state {
case .began:
print()
case .changed:
tabBarController?.view.frame.origin.x = min(max(gesture.translation(in: gestureView).x, -240), 0)
case .cancelled:
print()
case .ended:
print()
default: break
}
}
}
You are moving tabBarController, but as I understand you want to move menuViewController. Try this:
#objc private func panGesturePanned(gesture: UIPanGestureRecognizer) {
guard let gestureView = gesture.view else { return }
switch gesture.state {
case .began: break
case .changed:
tabBarController?.view.frame.origin.x = max(-menuViewController.view.bounds.width, gesture.translation(in: gestureView).x)
menuViewController.view.frame.origin.x = max(0, menuViewController.view.bounds.width - gesture.translation(in: gestureView).x)
case .cancelled: break
case .ended: break
default: break
}
}

Dragging UIView

I need some help with dragging UIView to reveal, menu view underneath main view.
I have two UIViews.
menuView - includes menu buttons and labels
and mainView - located over menuView.
I want to drag the main view from left edge to show menu items and snap the main view to a specific position. I am able to get the dragging to the right working but I cannot set it back to original position when dragging left.
here is my codes I have been playing around with but no success.
I also wanted to make mainView smaller as I drag it to right.
any help will be greatly appreciated.
Note: PanGesture is attached to mainView.
#IBAction func dragMenu(_ sender: UIPanGestureRecognizer) {
let mview = sender.view!
let originalCenter = CGPoint(x: self.mainView.bounds.width/2, y: self.mainView.bounds.height/2)
switch sender.state {
case .changed:
if let mview = sender.view {
mview.center.x = mview.center.x + sender.translation(in: view).x
sender.setTranslation(CGPoint.zero, in: view)
}
case .ended:
let DraggedHalfWayRight = mview.center.x > view.center.x
if DraggedHalfWayRight {
//dragginToRight
showMenu = !showMenu
self.mainViewRight.constant = 200
self.mainTop.constant = 50
self.mainBottom.constant = 50
self.mainLeft.constant = 200
} else //dragging left and set it back to original position.
{
mview.center = originalCenter
showMenu = !showMenu
}
default:
break
}
}
I'd suggest a few things:
When you're done dragging the menu off, make sure to set its alpha to zero (so that, if you were in portrait and go to landscape, you don't suddenly see the menu sitting there).
I personally just adjust the transform of the dragged view and avoid resetting the translation of the gesture back to zero all the time. That's a matter of personal preference.
I'd make the view controller the delegate for the gesture and implement gestureRecognizerShouldBegin (because if the menu is hidden, you don't want to recognize swipes to try to hide it again; and if it's not hidden, you don't want to recognize swipes to show it).
When figuring out whether to complete the gesture or not, I also consider the velocity of the gesture (e.g. so a little flick will dismiss/show the animated view).
Thus:
class ViewController: UIViewController {
#IBOutlet weak var menuView: UIView!
var isMenuVisible = true
#IBAction func dragMenu(_ gesture: UIPanGestureRecognizer) {
let translationX = gesture.translation(in: gesture.view!).x
switch gesture.state {
case .began:
// if the menu is not visible, make sure it's off screen and then make it visible
if !isMenuVisible {
menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
menuView.alpha = 1
}
fallthrough
case .changed:
if isMenuVisible {
menuView.transform = CGAffineTransform(translationX: translationX, y: 0)
} else {
menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width + translationX, y: 0)
}
case .ended:
let shouldComplete: Bool
if isMenuVisible {
shouldComplete = translationX > gesture.view!.bounds.width / 2 || gesture.velocity(in: gesture.view!).x > 0
} else {
shouldComplete = -translationX > gesture.view!.bounds.width / 2 || gesture.velocity(in: gesture.view!).x < 0
}
UIView.animate(withDuration: 0.25, animations: {
if self.isMenuVisible && shouldComplete || !self.isMenuVisible && !shouldComplete {
self.menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
} else {
self.menuView.transform = .identity
}
if shouldComplete{
self.isMenuVisible = !self.isMenuVisible
}
}, completion: { _ in
self.menuView.alpha = self.isMenuVisible ? 1 : 0
})
default:
break
}
}
}
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gesture: UIGestureRecognizer) -> Bool {
guard let gesture = gesture as? UIPanGestureRecognizer else { return true }
let translationX = gesture.translation(in: gesture.view!).x
return isMenuVisible && translationX > 0 || !isMenuVisible && translationX < 0
}
}
Personally, I prefer to use a separate screen edge gesture recognizer to pull them menu back on screen (you don't really want it to recognize gestures anywhere, but just on that right edge). Another virtue of this approach is that by keeping "show" and "hide" in different functions, the code is a lot more readable (IMHO):
class ViewController: UIViewController {
#IBOutlet weak var menuView: UIView!
var isMenuVisible = true
#IBAction func handleScreenEdgeGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
let translationX = gesture.translation(in: gesture.view!).x
switch gesture.state {
case .began:
menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
menuView.alpha = 1
fallthrough
case .changed:
menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width + translationX, y: 0)
case .ended:
let shouldComplete = -translationX > gesture.view!.bounds.width / 2 || gesture.velocity(in: gesture.view!).x < 0
UIView.animate(withDuration: 0.25, delay:0, options: .curveEaseOut, animations: {
if shouldComplete {
self.menuView.transform = .identity
self.isMenuVisible = !self.isMenuVisible
} else {
self.menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
}
}, completion: { _ in
self.menuView.alpha = self.isMenuVisible ? 1 : 0
})
default:
break
}
}
#IBAction func dragMenu(_ gesture: UIPanGestureRecognizer) {
let translationX = gesture.translation(in: gesture.view!).x
switch gesture.state {
case .began, .changed:
menuView.transform = CGAffineTransform(translationX: translationX, y: 0)
case .ended:
let shouldComplete = translationX > gesture.view!.bounds.width / 2 || gesture.velocity(in: gesture.view!).x > 0
UIView.animate(withDuration: 0.25, delay:0, options: .curveEaseOut, animations: {
if shouldComplete {
self.menuView.transform = CGAffineTransform(translationX: gesture.view!.bounds.width, y: 0)
self.isMenuVisible = !self.isMenuVisible
} else {
self.menuView.transform = .identity
}
}, completion: { _ in
self.menuView.alpha = self.isMenuVisible ? 1 : 0
})
default:
break
}
}
}
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIScreenEdgePanGestureRecognizer {
return !isMenuVisible
} else if let gesture = gestureRecognizer as? UIPanGestureRecognizer {
let translationX = gesture.translation(in: gesture.view!).x
return isMenuVisible && translationX > 0
}
return true
}
}

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;
}
}
}

Cutting diagonal movement from pan gesture

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

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
}
}
}
}

Resources