Change of constraint's constant is laggy while using UIPanGestureRecognizer - ios

Here is the code:
#objc func handleSwipe(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: self.view)
if sender.state == .began || sender.state == .changed {
let c = self.cardView.frontCardConstraint.constant - translation.y
if c < 35 && c >= 11 {
self.cardView.frontCardConstraint.constant = c
self.cardView.layoutIfNeeded()
} else {
//
}
} else if sender.state == .ended {
//
}
sender.setTranslation(.zero, in: self.view)
}
It does what I want, but when dragging fast it appears quite laggy. Is there anything I could change or should I switch to a custom gesture recognizer?

Try to use transform property instead of changing the constraint (I guess layout calculation might be inefficient).
Just small changes in your code are needed. First calculate c using this:
let c = self.cardView.transform.ty - translation.y
And then move the cardView by replacing:
self.cardView.frontCardConstraint.constant = c
self.cardView.layoutIfNeeded()
with this:
self.cardView.transform = CGAffineTransform.identity.translatedBy(x: c, y: 0)
When you finish the panning, just set self.cardView.transform = CGAffineTransform.identity to reset it.

Related

UIPanGesture recognizer swiping only in one direction

I am working on UIPanGestureRecognizer and to me it is working. but I have some problem here as I am new to iOS and just shifted from Android to iOS.
First take a look at what I want to do:
What I want: I have a UITableView and I want to perform swiping on the Cells. I just want to drag them from left to right side and move/Delete that cell. Pretty same like it is done in android.
But I just want to move the item only in one direction. And that is "LEFT TO RIGHT". But not from right to left. Now here take a look at what I have done so far
What I have Done:
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
// 1
if recognizer.state == .began {
// when the gesture begins, record the current center location
originalCenter = center
print("Center",originalCenter)
}
// 2
if recognizer.state == .changed {
let translation = recognizer.translation(in: self)
center = CGPoint(x: originalCenter.x+translation.x, y: originalCenter.y)
// has the user dragged the item far enough to initiate a delete/complete?
deleteOnDragRelease = frame.origin.x < -frame.size.width / 2.0
completeOnDragRelease = frame.origin.x > frame.size.width / 2.0
// print ("FrameX = ",frame.origin.x , " , ","Width = ",frame.size.width / 2.0 , "Total = ",frame.origin.x < -frame.size.width / 2.0 )
//print ("DelOnDrag = ",deleteOnDragRelease , " , ","CompOnDrag = ",completeOnDragRelease)
}
// 3
if recognizer.state == .ended {
// the frame this cell had before user dragged it
let originalFrame = CGRect(x: 0, y: frame.origin.y,
width: bounds.size.width, height: bounds.size.height)
if deleteOnDragRelease {
if delegate != nil && clickedItem != nil {
// notify the delegate that this item should be deleted
delegate!.toDoItemDeleted(clickedItem: clickedItem!)
}
} else if completeOnDragRelease {
UIView.animate(withDuration: 8.2, animations: {self.frame = originalFrame})
} else {
UIView.animate(withDuration: 8.2, animations: {self.frame = originalFrame})
}
}
}
I know I can make a check on ".changed" , and calculate if the X value is going towards 0 or lesser then 0. But point is for some time it will move item from right to left.
Question: Is there any way I can get the x value of point of contact? or just some how I can get user want to swipe right to left and just stop user from doing that?? Please share your knowledge
your same code just one changes in your UIGestureRecognizer method replace with this code and your problem solve. only left to right side swap work on your tableview cell . any query regrading this just drop comment below.
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer {
let translation = panGestureRecognizer.translation(in: superview!)
if translation.x >= 0 {
return true
}
return false
}
return false
}
Good Luck.
Keep coding.
You can do this using below extensions
extension UIPanGestureRecognizer {
enum GestureDirection {
case Up
case Down
case Left
case Right
}
func verticalDirection(target: UIView) -> GestureDirection {
return self.velocity(in: target).y > 0 ? .Down : .Up
}
func horizontalDirection(target: UIView) -> GestureDirection {
return self.velocity(in: target).x > 0 ? .Right : .Left
}
}
And you can get direction like below
gestureRecognizer.horizontalDirection(target: self)

Hero animation start delayed

I am trying to implement a transition between two controllers with Hero inside a navigation controller.
I have this method to handle a pan gesture:
#objc func pannedCell(gesture: UIPanGestureRecognizer) {
guard let animatedView = gesture.view as? HotelListingCollectionViewCell else {
return
}
let translation = gesture.translation(in: nil)
let screenHeight = self.rootView.bounds.height
let progress = ((-1 * translation.y) / screenHeight) * 2
print(progress)
switch gesture.state {
case .began:
self.initialViewPostion = animatedView.center
Hero.shared.defaultAnimation = .fade
let vc = ListingSearchViewController()
vc.viewModel.hotels.value = self.viewModel.hotels.value
self.navigationController?.pushViewController(vc, animated: true)
case .changed:
Hero.shared.update(progress)
let pos = CGPoint(
x: animatedView.center.x + translation.x,
y: self.rootView.hotelResultsCollectionView.center.y + translation.y
)
Hero.shared.apply(modifiers: [
.position(pos),
.useGlobalCoordinateSpace,
.useOptimizedSnapshot
], to: animatedView)
default:
if progress > 0.8 {
Hero.shared.finish()
} else {
Hero.shared.cancel()
}
}
}
The problem here is the pushViewController method takes ~ 1 second to execute so when I start dragging my view, it moves under my finger approx. 1 second after I started moving the finger on the screen.
I am doing it wrong ?
Thanks
Ok, I think I found something !
The issue seems to be related to operation I did in .began case of the gesture handler.
I moved the instantiation of the new view controller out of this call back to avoid overloading the UI thread during the animation and everything seems to work as expected.

Set Bounds of GestureRecognizer

I am trying to set the limit the bounds of my image view, Or something to prevent the image/ view from zooming out or to the left as it looks pretty bad. Here is the problem Looks Good, Only should be able to zoom in... Zoomed out, Looks Bad
#IBAction func scaleView(_ sender: UIPinchGestureRecognizer) {
//print(self.my_new_fullimage)
self.view.transform = self.view.transform.scaledBy(x: sender.scale, y: sender.scale)
sender.scale = 1
}
#IBAction func panView(_ gestureRecognizer: UIPanGestureRecognizer) {
// Move the anchor point of the view's layer to the touch point
// so that moving the view becomes simpler.
let piece = gestureRecognizer.view
//self.adjustAnchorPoint(gestureRecognizer: gestureRecognizer)
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
// Get the distance moved since the last call to this method.
let translation = gestureRecognizer.translation(in: piece?.superview)
// Set the translation point to zero so that the translation distance
// is only the change since the last call to this method.
piece?.center = CGPoint(x: ((piece?.center.x)! + translation.x),
y: ((piece?.center.y)! + translation.y))
gestureRecognizer.setTranslation(CGPoint.zero, in: piece?.superview)
}
}
Anything can help.
-Thanks!

Expand UIView with pan gesture in iOS Swift

I want to implement a view that stays at the bottom of the screen and can be expanded with a pan gesture just like in the Uber app for iOS?
Uber Home screen The view will be minimizable when dragged downwards
There are few third party libraries which makes your work easy. This is one of it. LNPopUpController.
Or else, if you want to customise the code and write: Take one view controller and add a UIView on top of it. Now add Pan Gesture to view like below.
func handlePan(pan : UIPanGestureRecognizer) {
let velocity = pan.velocityInView(self.superview).y //; print("Velocity : \(velocity)")
var location = pan.locationInView(self.superview!) //; print("Location : \(location)")
var movement = self.frame
movement.origin.x = 0
movement.origin.y = movement.origin.y + (velocity * 0.05) //; print("Frame.y : \(movement.origin.y)")
if pan.state == .Ended{
print("Gesture Ended")
panGestureEnded()
}else if pan.state == .Began { print("Gesture Began")
let center = self.center
offset.y = location.y - center.y //; print("Offset.y : \(offset.y)")
}else{ print("Gesture else")
animator.removeBehavior(snap)
// Apply the initial offset.
location.x -= offset.x
location.y -= offset.y
//print("location.y : \(location.y)")
// Bound the item position inside the reference view.
location.x = self.superview!.frame.width / 2
// Apply the resulting item center.
snap = UISnapBehavior(item: self, snapToPoint: location)
animator.addBehavior(snap)
}
}

bringSubviewToFront(_:) function breaks a UIPanGestureRecognizer

I have a BoardView class which subclasses UIView. It has several subviews, which all have their own UIPanGestureRecognizer. I use this recognizer to drag around a subview inside my BoardView instance, as such:
func pieceDragged(recognizer: UIPanGestureRecognizer) {
let pieceView = recognizer.view!
if recognizer.state == .Began || recognizer.state == .Changed {
// move the pieceView by the appropriate amount
let translation = recognizer.translationInView(self)
pieceView.center = CGPoint(x: pieceView.center.x + translation.x, y: pieceView.center.y + translation.y)
recognizer.setTranslation(CGPoint.zero, inView: self)
} else if recognizer.state == .Ended {
// do something else
}
}
This works fine.
The problem is that I want the subview that is being dragged around to be on top of all of its siblings, so I made my BoardView instance the delegate of each gesture recognizer and overrode this function:
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
let pieceView = gestureRecognizer.view!
bringSubviewToFront(pieceView)
return true
}
This seems to break the entire gesture recognizer: if I attempt to drag aroudn a subview, the pieceDragged(_:) function is called exactly once (where the state of the recognizer is .Began), but no more than that.
If I comment out bringSubviewToFront(pieceView), my code works as before - I can drag around any subview, but it's below some of its siblings.
I'm clueless. What could be going on here?
The problem was that I replaced all my subviews in the layoutSubviews() method (which is automatically called after bringToFront(_:) is called).
Seems like you are going round the houses as well and implementing a method that doesn't really make sense. The delegate method gestureRecognizerShouldBegin(_:) is all about should this recogniser actually start detecting not a place to perform an action.
You already have a suitable place to perform the action you want you could simply update your code to be
func pieceDragged(recognizer: UIPanGestureRecognizer) {
guard let pieceView = recognizer.view else {
return
}
switch recognizer.state {
case .Began:
pieceView.superview?.bringSubviewToFront(pieceView)
case .Changed:
let translation = recognizer.translationInView(self)
recognizer.setTranslation(CGPoint.zero, inView: self)
pieceView.center = CGPoint(x: pieceView.center.x + translation.x, y: pieceView.center.y + translation.y)
default: ()
}
}

Resources