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)
Related
Want to create an animation were user can slide the view from bottom similar to Apple Maps application in iOS. But having issues while handling gesture.
The code in my mainVC in which panVC is added as subview from bottom which works fine.(The panVC correctly comes at bottom)
func displayFromBottom {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
listVC = storyboard.instantiateViewController(withIdentifier: “PanViewContr") as! PanViewContr
var startingFrame = self.view.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
var finalFrame = self.view.bounds;
finalFrame.origin.y = finalFrame.size.height - 100; //100 from the bottom of the parent.
listVC.view.frame = startingFrame
listVC.willMove(toParentViewController: self)
self.addChildViewController(listVC)
self.view.addSubview(listVC.view)
listVC.didMove(toParentViewController: self)
UIView.animate(withDuration: 0.5, animations: {
self.listVC.view.frame = finalFrame
}) { complete in
print("done”)
}
}
The code for PanVC were pan gesture is handled.
func slideViewVerticallyTo(_ y: CGFloat) {
self.view.frame.origin = CGPoint(x: 0, y: y)
}
#IBAction func panGesture(_ panGesture: UIPanGestureRecognizer) {
switch panGesture.state {
case .began, .changed:
// If pan started or is ongoing then
// slide the view to follow the finger
let translation = panGesture.translation(in: view)
let y = max(0, translation.y) //what should be value of y to make it dragable smoothly
self.slideViewVerticallyTo(y)
break
case .ended:
break
}
Any hint in right direction is highly appreciated.
I had created a movable extension that adds the ability to move UIView's around a screen the code for which you can see here: https://github.com/szweier/SZUtilities/blob/master/SZUtilities/Classes/UIView/Movable/UIView%2BMovable.swift you may be able to see something in this code that you are missing in your own. I'm sorry my answer isn't more specific but hopefully it gets you moving towards the correct answer.
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.
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!
I have two UICollectionVews
One of them (The parent one) is a full-screen-cell paginated collection view.
The other one (The child one) is a filter inside the "page"
Both have the same scroll direction
My problem is that when I'm scrolling the child one, and it reaches the end, the parent one starts moving. And I would like to avoid that. I tried many things
*ScrollView delegates
*Touchesbegan
Any ideas?
Thanks!
I think it's easy .
To set the parent UICollectionView
collectionView.scrollEnabled = NO;
Or it sounds unreasonable . If the parent UICollectionView could scroll, how could you archive your goal? Because UICollectionView is consisted of the cells (The child one). The cell will affect the parent UICollectionView inevitably.
Maybe The child one is located in part of the cell , you could use the
UIScrollViewDelegate's method: scrollViewDidScroll , to set the parent collectionView's scrollEnabled property.
I think you prefer this answer.
As we know a pan gesture recognizer is built in UIScrollView.
We could add an other coordinated pan gesture.
Because the apple says: 'UIScrollView's built-in pan gesture recognizer must have its
scroll view as its delegate.'
let pan = UIPanGestureRecognizer(target: self, action: #selector(ViewController.panGestureRecognizerAction(recognizer:)))
pan.delegate = self
mainScrollView.addGestureRecognizer(pan)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
// So we can get the argus of the pan gesture while not affecting the scroll
after the setting.
var mainScrollEnabled = false
var subScrollEnabled = false
// Then we define two BOOL values to identify the scroll of the collectionView
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == mainScrollView {
if scrollView.contentOffset.y >= maxOffsetY {
scrollView.setContentOffset(CGPoint(x: 0, y: maxOffsetY), animated: false)
mainScrollView.isScrollEnabled = false
subScrollView.isScrollEnabled = true
subScrollEnabled = true
mainScrollEnabled = false
}
}else {
if scrollView.contentOffset.y <= 0 {
scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
subScrollView.isScrollEnabled = false
mainScrollView.isScrollEnabled = true
mainScrollEnabled = true
subScrollEnabled = false
}
}
}
// Then we handle the situation that the collectionView reaches the end , by the pan gesture's recognizer .
var currentPanY: CGFloat = 0
func panGestureRecognizerAction(recognizer: UIPanGestureRecognizer) {
if recognizer.state != .changed{
currentPanY = 0
// clear the data of last time after finishing the scroll
mainScrollEnabled = false
subScrollEnabled = false
}else {
let currentY = recognizer.translation(in: mainScrollView).y
// So the collectionView reaches the end.
if mainScrollEnabled || subScrollEnabled {
if currentPanY == 0 {
currentPanY = currentY //get the y
}
let offsetY = currentPanY - currentY //get the offsetY
if mainScrollEnabled {
let supposeY = maxOffsetY + offsetY
if supposeY >= 0 {
mainScrollView.contentOffset = CGPoint(x: 0, y: supposeY)
}else {
mainScrollView.contentOffset = CGPoint.zero
}
}else {
subScrollView.contentOffset = CGPoint(x: 0, y: offsetY)
}
}
}
}
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)
}
}