Swift : Set UIPanGestureRecognizer to UIView - ios

Im trying to set up a UIPanGestureRecognizer to a specific UIView
self.halfViewBlue.frame = CGRectMake(0, 0, self.bounds.width / 2, self.bounds.height)
self.halfViewBlue.backgroundColor = UIColor.blueColor()
halfViewBlue.alpha = 1.0
self.addSubview(self.halfViewBlue)
self.halfViewRed.frame = CGRectMake(self.frame.midX, 0, self.bounds.width / 2, self.bounds.height)
self.halfViewRed.backgroundColor = UIColor.redColor()
halfViewRed.alpha = 1.0
self.addSubview(self.halfViewRed)
//Pan Gesture for Red
let panGestureRed = UIPanGestureRecognizer()
panGestureRed.addTarget(self, action: "handlePanRed:")
self.addGestureRecognizer(panGestureRed)
self.userInteractionEnabled = true
//Pan Gesture for Blue
let panGestureBlue = UIPanGestureRecognizer()
panGestureBlue.addTarget(self, action: "handlePanBlue:")
self.addGestureRecognizer(panGestureBlue)
self.userInteractionEnabled = true
}
func handlePanRed(gesture : UIPanGestureRecognizer){
if (gesture.state == UIGestureRecognizerState.Changed || gesture.state == UIGestureRecognizerState.Ended){
let velocity : CGPoint = gesture.velocityInView(halfViewRed)
//Pan Down
if(velocity.y > 0){
print("Panning down")
hideRedBar()
produceBlueBar()
}
//Pan Up
if(velocity.y < 0){
print("panning up")
hideBlueBar()
produceRedBar()
}
}
}
func handlePanBlue(gesture : UIPanGestureRecognizer){
if (gesture.state == UIGestureRecognizerState.Changed || gesture.state == UIGestureRecognizerState.Ended){
let velocity : CGPoint = gesture.velocityInView(halfViewBlue)
//Pan Down
if(velocity.y > 0){
print("Panning down")
hideBlueBar()
produceRedBar()
}
if(velocity.y < 0){
print("panning up")
hideRedBar()
produceBlueBar()
}
}
}
Here I simply create two UIViews, both split vertically across the screen(equally).
I then add two panGestures and a function to follow it. My issue is that the only panGesture that gets recognized is the "panGestureBlue" and weirdly i can control it on any part of the screen, not just the half where the "halfViewBlue" is.
So I'm assuming that both panGestures are being added to self.view and not the UIView it is suppose to be put with, and the "panGestureBlue" is being read in because it is added after "panGestureRed". Instead of "self" in the "panGestureBlue.addTarget" i tried putting "halfViewBlue", but it crashed.
How can i fix this issue!?

You should add the pan gesture on the subviews not the superview, like this:
self.halfViewRed.addGestureRecognizer(panGestureRed)
self.halfViewBlue.addGestureRecognizer(panGestureBlue)

Related

require(toFail) not working for PanGestureRecognizer and PageViewController

Here's the problem I'm having:
I have a PageViewController that navigates between 4 views horizontally and a PanGestureRecognizer on a UIView that brings it up and down vertically. The issue is that the UIView can be panned up, but then it is stuck there. The user can only swipe horizontally with the PageViewController.
The goal:
Be able to pan the UIView up and down AND swipe left and right on the PageViewController. Basically, make the UIView not get stuck on screen.
This is what I have now:
I have a PanGestureRecognizer function in the UIView file that looks
like this:
func handlePan(recognizer: UIPanGestureRecognizer) {
let vel = recognizer.velocity(in: self)
print("HANDLING PAN")
switch recognizer.state {
case .began:
if vel.y < 0 && !scanIsUp {
animator = UIViewPropertyAnimator(duration: 1, curve: .easeOut, animations: {
self.frame = self.frame.offsetBy(dx: 0, dy: -603)
})
scanIsUp = true
isScanVisible = true
} else if vel.y > 0 && scanIsUp {
print("vel.y hehehe")
animator = UIViewPropertyAnimator(duration: 1, curve: .easeOut, animations: {
self.frame = self.frame.offsetBy(dx: 0, dy: 603)
})
scanIsUp = false
isScanVisible = false
}
animator?.pauseAnimation()
print("paused?")
case .changed:
let translation = recognizer.translation(in: backgroundImage)
if vel.y < 0 && scanIsUp {
animator?.fractionComplete = translation.y / -603
} else if vel.y > 0 && !scanIsUp {
print("vel.y hehehe pt 2")
animator?.fractionComplete = translation.y / 603
}
case .ended:
animator?.continueAnimation(withTimingParameters: nil, durationFactor: 0)
case .possible: break
default: break
}
}
I also have an array of gestureRecognizers that control the PageViewController. I cycle through them and require them to fail so that the user can pan the UIView up and down like this:
if #available(iOS 10.0, *) {
self.panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(PFScan.handlePan))
self.panGestureRecognizer!.maximumNumberOfTouches = 1
self.panGestureRecognizer!.cancelsTouchesInView = false
self.addGestureRecognizer(self.panGestureRecognizer!)
for recognizer in self.scrollViewGestureRecognizers {
recognizer.require(toFail: panGestureRecognizer!)
}
}
Finally, I initialize the UIView on the main view controller part of the PageViewController like this:
func setUpScanPage(scrollViewGestureRecognizers: [UIGestureRecognizer]) {
scanPage = PFScan(scrollViewGestureRecognizers: scrollViewGestureRecognizers)
self.view.addSubview(scanPage)
}
Does anyone know why the UIView gets stuck?? I have tried everything I know of. Any help would be immensely appreciated.
Thanks a ton in advanced. Cheers,
Theo

Make a pan gesture respond on vertical swipe downs

I implemented a pan gesture in my application to dismiss a view. Basically when a user swipes down, the view dismisses.
However, I want it to work so that it doesn't dismiss if a user swipes diagonally down, or semi-down. I only want it to be responsive on a strict vertical swipe down. Here is my code so far.
var originalPosition: CGPoint?
var currentPositionTouched: CGPoint?
func panGestureAction(_ panGesture: UIPanGestureRecognizer) {
if currentScreen == 0 {
let translation = panGesture.translation(in: view)
if panGesture.state == .began {
originalPosition = view.center
//currentPositionTouched = panGesture.location(in: view)
} else if panGesture.state == .changed {
view.frame.origin = CGPoint(
x: view.frame.origin.x,
y: view.frame.origin.y + translation.y
)
panGesture.setTranslation(CGPoint.zero, in: self.view)
} else if panGesture.state == .ended {
let velocity = panGesture.velocity(in: view)
if velocity.y >= 150 {
UIView.animate(withDuration: 0.2
, animations: {
self.view.frame.origin = CGPoint(
x: self.view.frame.origin.x,
y: self.view.frame.size.height
)
}, completion: { (isCompleted) in
if isCompleted {
self.dismiss(animated: false, completion: nil)
}
})
} else {
UIView.animate(withDuration: 0.2, animations: {
self.view.center = self.originalPosition!
})
}
}
}
}
You are on the right track with currentPositionTouched. In your .began block, set currentPositionTouched to the panGesture's location
currentPositionTouched = panGesture.location(in: view)
Then, in your .changed and .ended block, use a check to determine if the x value is the same and develop your logic accordingly.
if currentPositionTouched.x == panGesture.location(in: view).x
You can use gestureRecognizerShouldBegin. You can set it up to only recognize vertical gestures (i.e. those for which the angle is less than a certain size). For example, in Swift, it is:
extension ViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let gesture = gestureRecognizer as? UIPanGestureRecognizer else { return false }
let translation = gesture.translation(in: gesture.view!)
if translation.x != 0 || translation.y != 0 {
let angle = atan2(abs(translation.x), translation.y)
return angle < .pi / 8
}
return false
}
}
Just make sure to set the delegate of the pan gesture recognizer.
Note, I'm not checking to see if it's absolutely vertical (because of variations in the gesture path, it will likely rarely be perfectly vertical), but within some reasonable angle from that.
FYI, this is based upon this Objective-C rendition.

How to drag and zoom UIImageView inside UIView in swift 3.0?

I want to drag and zoom UIImageView inside UIView not in UIViewController (self.view) using swift 3.0.
Here I have declare mainView and imageView,
var lastScale: CGFloat = 0.0
#IBOutlet var mainView: UIView!
#IBOutlet var imageView: UIImageView!
I have set Pan and Pinch Gesture for zoom and drag imageview inside mainView
let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(ViewController.handlePinchGesture(_:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(pinchGestureRecognizer)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePanGesture(recognizer:)))
imageView.addGestureRecognizer(panGestureRecognizer)
Function for pan and pinch gesture
func handlePinchGesture(_ gestureRecognizer: UIPinchGestureRecognizer) {
if gestureRecognizer.state == .began {
lastScale = gestureRecognizer.scale
}
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let currentScale: CGFloat = (gestureRecognizer.view!.layer.value(forKeyPath: "transform.scale")! as! CGFloat)
// Constants to adjust the max/min values of zoom
//let kMaxScale: CGFloat = 2.0
let kMinScale: CGFloat = 1.0
var newScale: CGFloat = 1 - (lastScale - gestureRecognizer.scale)
//newScale = min(newScale, kMaxScale / currentScale)
newScale = max(newScale, kMinScale / currentScale)
let transform = gestureRecognizer.view?.transform.scaledBy(x: newScale, y: newScale)
gestureRecognizer.view?.transform = transform!
lastScale = gestureRecognizer.scale
}
}
func handlePanGesture(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: mainView)
var recognizerFrame = recognizer.view?.frame
recognizerFrame?.origin.x += translation.x
recognizerFrame?.origin.y += translation.y
// Check if UIImageView is completely inside its superView
if mainView.bounds.contains(recognizerFrame!) {
recognizer.view?.frame = recognizerFrame!
}
else {
// Check vertically
if (recognizerFrame?.origin.y)! < mainView.bounds.origin.y {
recognizerFrame?.origin.y = 0
}
else if (recognizerFrame?.origin.y)! + (recognizerFrame?.size.height)! > mainView.bounds.size.height {
recognizerFrame?.origin.y = mainView.bounds.size.height - (recognizerFrame?.size.height)!
}
// Check horizantally
if (recognizerFrame?.origin.x)! < mainView.bounds.origin.x {
recognizerFrame?.origin.x = 0
}
else if (recognizerFrame?.origin.x)! + (recognizerFrame?.size.width)! > mainView.bounds.size.width {
recognizerFrame?.origin.x = mainView.bounds.size.width - (recognizerFrame?.size.width)!
}
}
// Reset translation so that on next pan recognition
// we get correct translation value
recognizer.setTranslation(CGPoint(x: CGFloat(0), y: CGFloat(0)), in: mainView)
}
When I run this code, And I'm zoom imageView it's go outside of mainView.
So, How can I fixed?
Whenever you scale your subview or move your subview, the frame value of subview needs to be changed. Because frame is a property of UIView, you can use KVO to observe it and set some rules for its changes like your way done for pan etc.
For KVO of frame, you can find better explanation from here

Swift: move UIView on slide gesture

I am trying to move a UIView on slide up gesture from its initial position to a fixed final position. The image should move with the hand gesture, and not animate independently.
I haven't tried anything as I have no clue where to start, which gesture class to use.
Finally did it like below.
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged:"))
slideUpView.addGestureRecognizer(gesture)
slideUpView.userInteractionEnabled = true
gesture.delegate = self
The following function is called when the gesture is detected, (here I am restricting the view to have a maximum centre.y of 555, & I'm resetting back to 554 when the view moves past this point)
func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.Began || gestureRecognizer.state == UIGestureRecognizerState.Changed {
let translation = gestureRecognizer.translationInView(self.view)
print(gestureRecognizer.view!.center.y)
if(gestureRecognizer.view!.center.y < 555) {
gestureRecognizer.view!.center = CGPointMake(gestureRecognizer.view!.center.x, gestureRecognizer.view!.center.y + translation.y)
}else {
gestureRecognizer.view!.center = CGPointMake(gestureRecognizer.view!.center.x, 554)
}
gestureRecognizer.setTranslation(CGPointMake(0,0), inView: self.view)
}
}
You probably want to use a UIPanGestureRecognizer.
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged:"))
customView.addGestureRecognizer(gesture)
gesture.delegate = self
And to drag the object only along the y-axis:
func wasDragged(gesture: UIPanGestureRecognizer) {
let translation = gesture.translationInView(self.view)
// Use translation.y to change the position of your customView, e.g.
customView.center.y = translation.y // Customize this.
}
Swift 4:
#objc func wasDragged(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizer.State.began || gestureRecognizer.state == UIGestureRecognizer.State.changed {
let translation = gestureRecognizer.translation(in: self.view)
print(gestureRecognizer.view!.center.y)
if(gestureRecognizer.view!.center.y < 555) {
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x, y: gestureRecognizer.view!.center.y + translation.y)
}else {
gestureRecognizer.view!.center = CGPoint(x:gestureRecognizer.view!.center.x, y:554)
}
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
Call
let gesture = UIPanGestureRecognizer(target: self, action: self.wasDragged(gestureRecognizer:))
customView.addGestureRecognizer(gesture)
gesture.delegate = self
Update for Swift 3.x
When assigning the selector, the syntax has changed and now requires #selector
let gesture = UIPanGestureRecognizer(target: self, action: #selector(navViewDragged(gesture:)))
self.navLayoutView.addGestureRecognizer(gesture)
self.navLayoutView.isUserInteractionEnabled = true
gesture.delegate = self
Function implementation:
func navViewDragged(gesture: UIPanGestureRecognizer){
//Code here
}
Move view anywhere in Swift 3
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragged(gestureRecognizer:)))
demoView.isUserInteractionEnabled = true
demoView.addGestureRecognizer(panGesture)
Function
#objc func dragged(gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {
let translation = gestureRecognizer.translation(in: self.view)
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
This is how you really do it like the news view in the Stocks app
First add 2 constraints in Storyboard to the sliding view, one for it's state when it's fully opened and one for when it's closed. Don't forget to leave one of the constraints disabled / not installed so that your view will look opened or closed when the scene is reached.
Reference them in your code
#IBOutlet weak var optionsOpenedConstraint: NSLayoutConstraint!
#IBOutlet weak var optionsVisiableConstraint: NSLayoutConstraint!
now add the UIPanGestureRecognizer to your view in the viewDidLoad function.
let gesture = UIPanGestureRecognizer(target: self, action: #selector(type(of: self).wasDragged(gestureRecognizer:)))
optionsView.addGestureRecognizer(gesture)
finally add this callback and 2 functions:
#objc func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
let distanceFromBottom = screenHeight - gestureRecognizer.view!.center.y
if gestureRecognizer.state == UIGestureRecognizer.State.began || gestureRecognizer.state == UIGestureRecognizer.State.changed {
optionsOpenedConstraint.isActive = false
optionsVisiableConstraint.isActive = false
let translation = gestureRecognizer.translation(in: self.view)
if((distanceFromBottom - translation.y) < 100) {
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
}
}
if gestureRecognizer.state == UIGestureRecognizer.State.ended{
if distanceFromBottom > 6{
openOptionsPanel()
}else{
closeOptionsPanel()
}
}
}
func openOptionsPanel(){
optionsOpenedConstraint.isActive = true
optionsVisiableConstraint.isActive = false
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
func closeOptionsPanel(){
optionsOpenedConstraint.isActive = false
optionsVisiableConstraint.isActive = true
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
and voalá
#IBAction func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
if MainView.bounds.contains(mainImage.frame) {
let recognizerCenter = recognizer.location(in: MainView)
mainImage.center = recognizerCenter
}
if MainView.bounds.intersection(mainImage.frame).width > 50 && MainView.bounds.intersection(mainImage.frame).height > 0 {
let recognizerCenter = recognizer.location(in: MainView)
print(recognizerCenter)
mainImage.center = recognizerCenter
}
}
#IBAction func handlePinchGesture(_ recognizer: UIPinchGestureRecognizer) {
mainImage.transform = mainImage.transform.scaledBy(x: recognizer.scale, y: recognizer.scale)
recognizer.scale = 1.0
}
#IBAction func handleRotateGesture(_ recognizer: UIRotationGestureRecognizer) {
mainImage.transform = mainImage.transform.rotated(by: recognizer.rotation)
recognizer.rotation = 0.0
}

Changing height of UIView in response to finger drag

I understand that the title is a bit confusing so I'll try my best to explain it well. I have a UIView that has a fixed width of 100, and a variable height that starts at 0. What I want to be able to do is drag my finger from the base of the UIView, towards the top of the screen, and the UIView changes its height/extends to the position of my finger. If that's still to complicated just imagine it as a strip of paper being pulled out from under something.
If you can help, that would be really great. I don't think it should be too hard, but I'm only a beginner and I can understand if I haven't explained it well!
This should be straight-forward with a UIPanGestureRecognizer and a little math. To change the view to the correct frame (I'm using the name _viewToChange, so replace that with your view later), simply add:
UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(pan:)];
pan.maximumNumberOfTouches = pan.minimumNumberOfTouches = 1;
[self addGestureRecognizer:pan];
to your init method for the super view, and define the method:
- (void)pan:(UIPanGestureRecognizer *)aPan; {
CGPoint currentPoint = [aPan locationInView:self];
[UIView animateWithDuration:0.01f
animations:^{
CGRect oldFrame = _viewToChange.frame;
_viewToChange.frame = CGRectMake(oldFrame.origin.x, currentPoint.y, oldFrame.size.width, ([UIScreen mainScreen].bounds.size.height - currentPoint.y));
}];
}
This should animate the view up as the users finger moves. Hope that Helps!
Swift version using UIPanGestureRecognizer
Using PanGesture added to UIView from Storyboard and delegate set to self.
UIView is attached to bottom of the screen.
class ViewController: UIViewController {
var translation: CGPoint!
var startPosition: CGPoint! //Start position for the gesture transition
var originalHeight: CGFloat = 0 // Initial Height for the UIView
var difference: CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
originalHeight = dragView.frame.height
}
#IBOutlet weak var dragView: UIView! // UIView with UIPanGestureRecognizer
#IBOutlet var gestureRecognizer: UIPanGestureRecognizer!
#IBAction func viewDidDragged(_ sender: UIPanGestureRecognizer) {
if sender.state == .began {
startPosition = gestureRecognizer.location(in: dragView) // the postion at which PanGestue Started
}
if sender.state == .began || sender.state == .changed {
translation = sender.translation(in: self.view)
sender.setTranslation(CGPoint(x: 0.0, y: 0.0), in: self.view)
let endPosition = sender.location(in: dragView) // the posiion at which PanGesture Ended
difference = endPosition.y - startPosition.y
var newFrame = dragView.frame
newFrame.origin.x = dragView.frame.origin.x
newFrame.origin.y = dragView.frame.origin.y + difference //Gesture Moving Upward will produce a negative value for difference
newFrame.size.width = dragView.frame.size.width
newFrame.size.height = dragView.frame.size.height - difference //Gesture Moving Upward will produce a negative value for difference
dragView.frame = newFrame
}
if sender.state == .ended || sender.state == .cancelled {
//Do Something
}
}
}
This is my swift solution in doing it using autolayout and a height constraint on the view you want to resize, while setting a top and bottom limit based on the safe areas
private var startPosition: CGPoint!
private var originalHeight: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(viewDidDrag(_:)))
resizableView.addGestureRecognizer(panGesture)
}
#objc private func viewDidDrag(_ sender: UIPanGestureRecognizer) {
if sender.state == .began {
startPosition = sender.location(in: self.view)
originalHeight = detailsContainerViewHeight.constant
}
if sender.state == .changed {
let endPosition = sender.location(in: self.view)
let difference = endPosition.y - startPosition.y
var newHeight = originalHeight - difference
if view.bounds.height - newHeight < topSafeArea + backButtonSafeArea {
newHeight = view.bounds.height - topSafeArea - backButtonSafeArea
} else if newHeight < routeDetailsHeaderView.bounds.height + bottomSafeArea {
newHeight = routeDetailsHeaderView.bounds.height
}
resizableView.constant = newHeight
}
if sender.state == .ended || sender.state == .cancelled {
//Do Something if interested when dragging ended.
}
}

Resources