Stop UIView leaving container with UIPanGesture? - ios

I have managed to stop my UIView going outside of the container with the pan gesture but what I am struggling to achieve is to stop it at its edges. Currently it goes right to the last pixel edge and not the edge of the moveable view.
I tried changing the frame value which I think is the way to go but couldn't produce the right results. Not sure how close I have got or if there is an easier way?
How would I stop it at its edges of the moveable view instead of the last pixel?
//Container is self.sliderContainer
//Green Block is sender.view which has a UIGesture applied
var location = sender.locationInView(self.view)
var newFrame = CGRectMake(0, 0, self.sliderContainer.frame.width, self.sliderContainer.frame.height)
let obstacleViewFrame = self.view.convertRect(self.sliderContainer.frame, fromView: self.sliderContainer.superview)
// Check if the touch is inside the obstacle view
if CGRectContainsPoint(obstacleViewFrame, location) {
sender.view!.center.x = sender.view!.center.x + translation.x
sender.setTranslation(CGPointZero, inView: self.view)
}

I would recommend doing it the simplest way, not limiting the gesture, only making sure the rect won't go out of bounds:
let movingView = sender.view!
let minCenterX = movingView.frame.size.width / 2
let maxCenterX = self.sliderContainer.frame.width - movingView.frame.size.width / 2
let newCenterX = movingView.center.x + translation.x
movingView.center.x = min(maxCenterX, max(minCenterX, newCenterX))

It looks like you are basing the movement of the sender view in the slide based on the coordinates of the touch and not the frame of sender.view.
let senderViewFrame = self.view.convertRect(sender.view!.frame, fromView: sender.view!.superview)
let insetSenderViewFrame = CGRectInset(senderViewFrame, 0.0, 10.0 /*or whatever's needed*/)
let obstacleViewFrame = self.view.convertRect(self.sliderContainer.frame, fromView: self.sliderContainer.superview)
// Check if the sender view is inside the obstacle view
if CGRectContainsRect(obstacleViewFrame, insetSenderViewFrame) {
sender.view!.center.x = sender.view!.center.x + translation.x
sender.setTranslation(CGPointZero, inView: self.view)
}

Related

Pan Gesture making view jump away from finger on drag

I'm having a problem when trying to drag a view using a pan gesture recognizer. The view is a collectionViewCell and the dragging code is working, except when the drag starts the view jumps up and to the left. My code is below.
In the collectionViewCell:
override func awakeFromNib() {
super.awakeFromNib()
let panRecognizer = UIPanGestureRecognizer(target:self, action:#selector(detectPan))
self.gestureRecognizers = [panRecognizer]
}
var firstLocation = CGPoint(x: 0, y: 0)
var lastLocation = CGPoint(x: 0, y: 0)
#objc func detectPan(_ recognizer:UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
firstLocation = recognizer.translation(in: self.superview)
lastLocation = recognizer.translation(in: self.superview)
case .changed:
let translation = recognizer.translation(in: self.superview)
self.center = CGPoint(x: lastLocation.x + translation.x, y: lastLocation.y + translation.y)
default:
UIView.animate(withDuration: 0.1) {
self.center = self.firstLocation
}
}
}
The first image is before the drag starts, the second is what happens when dragging up.
You're using self.center instead of using self.frame.origin.x and self.frame.origin.y then later you're setting your translation and adding it to the lastLocation.
Effectively what's happening is that your view is calculating the position changed from the center of the view, as if you were perfectly dragging from that location and then translating + lastLocation. I'm sure just by reading that you're aware of the issue.
The fix is simple.
self.frame.origin.x = translation.x
self.frame.origin.y = translation.y
The difference is the starting calculation with the translation. Origin will grab the x/y position based on where the touch event begins. Whereas the .center always goes from the center.

UIViewContainer transformation changes after UIButton title is changed

I have the storyboard in the below screenshot.
The gray area is a UIViewContainer. UIGestureRegonizers are enabling the zooming and the moving of the UIViewContainer through Pan-Pinch gestures. The location of the buttons are fixed in the bottom with constraints, the UIViewContainer has flexible size and connected to buttons and the safe are with constraints.
So here is the problem: First, I zoom into a part of the UIViewContainer and then, I change the title of the up-right button. Right after that, for some reason that I need help with, the container moves about 10 pixels to the right. I am sure that no lifecycle function is called after the button.setTitle() function. I think it can be the problem that the container does some kind of a relayout.
Here is a dummy app where I reproduced the same behavior. After you move the container to somewhere with a pan gesture and click the button, the button title changes and the view is moved back to center. I want the view to stay where it is.
How can I disable the layout after the button title is changed?
#IBAction func handlePinchGestures(pinchGestureRecognizer: UIPinchGestureRecognizer) {
if let view = pinchGestureRecognizer.view {
switch pinchGestureRecognizer.state {
case .changed:
let pinchCenter = CGPoint(x: pinchGestureRecognizer.location(in: view).x - view.bounds.midX,
y: pinchGestureRecognizer.location(in: view).y - view.bounds.midY)
let transform = view.transform.translatedBy(x: pinchCenter.x, y: pinchCenter.y)
.scaledBy(x: pinchGestureRecognizer.scale, y: pinchGestureRecognizer.scale)
.translatedBy(x: -pinchCenter.x, y: -pinchCenter.y)
view.transform = transform
pinchGestureRecognizer.scale = 1
imageLayerContainer.subviews.forEach({ (subview: UIView) -> Void in subview.setNeedsDisplay() })
default:
return
}
}
}
#IBAction func handlePanGesturesWithTwoFingers(panGestureRecognizer: UIPanGestureRecognizer) {
let translation = panGestureRecognizer.translation(in: self.view)
if let view = panGestureRecognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
panGestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
}

Restrict pan gesture from moving outside of the left and right sides of the screen’s frame

I have an image view that pops up in the centre of the screen. You can pinch to zoom in or zoom out the image as well as move it horizontally. All these actions work perfectly. However I want to restrict the panning movements so users can't swipe beyond the left or right edges of the image. Below Ive posted screenshots along with explanations to show what I mean
Original View
Image moved to the right. At the moment you can see you can move it beyond the left edge of the image and it show black a black space. If the image width is equal to the screen width then I obviously want the pan gesture to be disable
Image zoomed in
Imaged moved to the left but again I want to be able to restrict the pan gesture to the edge of the image so there is no black space on the right of the image.
Ive tried looking around but I can't find anything that can help with my specific problem. Ive posted my code below. Any help would be much appreciated! Thanks in advance
func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let zoomView = gestureRecognizer.view else{return}
if gestureRecognizer.state == UIGestureRecognizerState.began || gestureRecognizer.state == UIGestureRecognizerState.changed {
let translation = gestureRecognizer.translation(in: self.view)
if(gestureRecognizer.view!.center.x < 300) {
gestureRecognizer.view!.center = CGPoint(x:gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y)
}else{
gestureRecognizer.view!.center = CGPoint(x:299, y: gestureRecognizer.view!.center.y)
}
gestureRecognizer.setTranslation(CGPoint.zero, in: zoomView)
}
}
I followed this youtube video to get it done.
I can't help you with the zoom but I can help you stop the imageView from moving outside of the left and right sides when NOT zooming.
You didn't give any context as to wether or not your imageView was created in Storyboard or programmatically. The one in my example is programmatic and it's named orangeImageView.
Create a new project then just copy and paste this code inside of the View Controller. When you run the project you will see an orange imageView that you can move around but it won't go beyond the left or right side of the screen. I didn't bother with the top or bottom because it wasn't part of your question.
Follow Steps 1 - 8 inside #objc func panGestureHandler(_ gestureRecognizer: UIPanGestureRecognizer) for an explanation of what each step does and how it works.
Replace the orangeImageView with the imageView your using:
class ViewController: UIViewController {
// create frame in viewDidLoad
let orangeImageView: UIImageView = {
let imageView = UIImageView()
imageView.isUserInteractionEnabled = true
imageView.backgroundColor = .orange
return imageView
}()
var panGesture: UIPanGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
// create a frame for the orangeImageView and add it as a subview
orangeImageView.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
view.addSubview(orangeImageView)
// initialize the pangesture and add it to the orangeImageView
panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureHandler(_:)))
orangeImageView.addGestureRecognizer(panGesture)
}
#objc func panGestureHandler(_ gestureRecognizer: UIPanGestureRecognizer){
// 1. use these values to restrict the left and right sides so the orangeImageView won't go beyond these points
let leftSideRestrction = self.view.frame.minX
let rightSideRestriction = self.view.frame.maxX
// 2. use these values to redraw the orangeImageView's correct size in either Step 6 or Step 8 below
let imageViewHeight = self.orangeImageView.frame.size.height
let imageViewWidth = self.orangeImageView.frame.size.width
if gestureRecognizer.state == .changed || gestureRecognizer.state == .began {
let translation: CGPoint = 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.zero, in: self.view)
/*
3.
-get the the upper left hand corner of the imageView's X and Y origin to get the current location of the imageView as it's dragged across the screen.
-you need the orangeImageView.frame.origin.x value to make sure it doesn't go beyond the left or right edges
-you need the orangeImageView.frame.origin.y value to redraw it in Steps 6 and 8 at whatever Y position it's in when it hits either the left or right sides
*/
let imageViewCurrentOrginXValue = self.orangeImageView.frame.origin.x
let imageViewCurrentOrginYValue = self.orangeImageView.frame.origin.y
// 4. get the right side of the orangeImageView. It's computed using the orangeImageView.frame.origin.x + orangeImageView.frame.size.width
let imageViewRightEdgePosition = imageViewCurrentOrginXValue + imageViewWidth
// 5. if the the orangeImageView.frame.origin.x touches the left edge of the screen or beyond it proceed to Step 6
if imageViewCurrentOrginXValue <= leftSideRestrction {
// 6. redraw the orangeImageView's frame with x: being the far left side of the screen and Y being where ever the current orangeImageView.frame.origin.y is currently positioned at
orangeImageView.frame = CGRect(x: leftSideRestrction, y: imageViewCurrentOrginYValue, width: imageViewWidth, height: imageViewHeight)
}
// 7. if the the orangeImageView.frame.origin.x touches the right edge of the screen or beyond it proceed to Step 8
if imageViewRightEdgePosition >= rightSideRestriction{
// 8. redraw the orangeImageView's frame with x: being the rightSide of the screen - the orangeImageView's width and y: being where ever the current orangeImageView.frame.origin.y is currently positioned at
orangeImageView.frame = CGRect(x: rightSideRestriction - imageViewWidth, y: imageViewCurrentOrginYValue, width: imageViewWidth, height: imageViewHeight)
}
}
}
}

Get distance from the edge in swift 3

I've got a moveable image using UIPanGestureRecognizer and i need to make the image more transparent the closer it get's to the edge of the screen.
Below is the code I'm using to move and rotate the image the further it get's away from the center.
Adding the UIPanGestureRecognizer to the UIView the image is in:
let moveImage = UIPanGestureRecognizer(target: self, action: #selector(self.detectPan))
moveImage.cancelsTouchesInView = false
MainImageView.addGestureRecognizer(moveImage)
The function that get's called when the UIPanGestureRecognizer starts.
func detectPan(gesture: UIPanGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.began || gesture.state == UIGestureRecognizerState.changed {
let translation = gesture.translation(in: self.view)
gesture.view!.center = CGPoint(x: gesture.view!.center.x + translation.x, y: gesture.view!.center.y)
gesture.setTranslation(CGPoint(x: 0,y: 0), in: self.view)
let newValue = CGFloat(((gesture.view!.center.x + translation.x) - (self.view.bounds.width * 0.50)) / 500)
MainImageView.transform = MainImageView.transform.rotated(by: -lastValue)
MainImageView.transform = MainImageView.transform.rotated(by: newValue)
lastValue = newValue
}
}
I'm assuming that " further it get's away from the center" is relative to the center of the view's parent (or if it's relative to something else, you can adjust my example accordingly).
Since you seem to only be moving the image horizontally, the distance should be a simple first degree calculation.
Something like:
let distanceToCenter = abs(gesture.view!.center.x - gesture.view!.parent!.center.x)
If you were moving both vertically and horizontally, you would need a second degree calculation to get the distance:
let deltaX = gesture.view!.center.x - gesture.view!.parent!.center.x
let deltaY = gesture.view!.center.y - gesture.view!.parent!.center.y
let distanceToCenter = sqrt(deltaX*deltaX + deltaY*deltaY)
You could then compute the alpha for fading out as follows:
let alpha = 1 - 2 * distanceToCenter / gesture.view!.parent!.bounds.size.width
Note that Swift may need some type casting for these calculations (at least until it goes to math school and figures out number theory)

Dragging a UILabel within a UIView and maintaining it's new coordinates

So I have a UILabel on a UIView nib file and I am trying to give the user the ability to drag the label around and when they stop dragging the label stays there and if they try to drag it again they can.
I am able to drag the Label just fine using the UIPanGestureRecognizer and when I stop dragging it the label stays where it is. The problem I am having is that when I attempt to drag it again the Label jumps back to the original starting position at the center of the UIView.
I understand that using the UIPanGestureRecognizer.translation(in: UIVIew) gives the original coordinates as (x: 0.0, y: 0.0) when the actual coordinates that the Label starts at are (x: 150.0, y: 90.0).
#IBOutlet var cardView: UIView!
#IBOutlet weak var testLBL: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// trying to move the label
let gesture = UIPanGestureRecognizer(target: self, action: #selector(self.wasDragged(gestureRecognizer:)))
testLBL.isUserInteractionEnabled = true
cardView.clipsToBounds = true
testLBL.addGestureRecognizer(gesture)
}
Below is the function that is used to handle the action, I have several other things I have tried commented out. I am also printing the current coordinates of the Label within the cardView
// function that helps drag the label around
func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: cardView)
//print(translation)
// currently taking it from the original point each time you try to drag it
testLBL.center = CGPoint(x: self.bounds.width / 2 + translation.x, y: self.bounds.height / 2 + translation.y)
//how you will know what position the label was moved to
print(["x",self.testLBL.frame.origin.x,"y", self.testLBL.frame.origin.y])
//let newTranslation = gestureRecognizer.translation(in: cardView)
//var coordinates = CGPoint(x: self.testLBL.frame.origin.x, y: self.testLBL.frame.origin.y)
}
The testLBL.center = CGPoint(x: self.bounds.width / 2 + translation.x, y: self.bounds.height / 2 + translation.y) operation is what allows the label to move and works find I just want it to not jump back to the middle of the UIView when I try to drag it again.
Any help is greatly appreciated because I am still pretty new at coding, Thank You!
// function that helps drag the label around
#objc
func wasDragged(gestureRecognizer: UIPanGestureRecognizer) {
let translation = gestureRecognizer.translation(in: self.view)
let selectedLabel = gestureRecognizer.view!
selectedLabel.center = CGPoint(x: selectedLabel.center.x + translation.x,
y: selectedLabel.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
print(["1 x",self.testLbl.frame.origin.x,"y", self.testLbl.frame.origin.y])
}
Tested this code and it works, hope it helps anyone!
this might help
CGPoint translation = [recognizer translationInView:self.superview];
testLBL.center = CGPointMake(self.center.x + translation.x, self.center.y + translation.y);
[recognizer setTranslation:CGPointZero inView:self];
apply it on swift you won't find that hard

Resources