My code:
#IBAction func handlePan(gesture: UIPanGestureRecognizer) {
let transition = gesture.translationInView(self.view)
switch gesture.state{
case .Changed:
if let view = gesture.view {
view.center = CGPoint(x: view.center.x + transition.x, y: view.center.y + transition.y)
}
gesture.setTranslation(CGPointZero, inView: self.view)
default:break
}
}
So I can drag a big button in the screen around. Everything works find till I comment out gesture.setTranslation(CGPointZero, inView: self.view).
I thought that one line of code only tells the app to remember the last position of the button on screen and move from there next time, but...
Then I ran the project again on a simulator, when I clicked on that button and tried to move a bit, the button just flew in the same direction and disappeared off screen, why?
As you pan around, the transition value is from where you first began the gesture.
Look at how you move the button. You just keep adding the larger and larger transition to each updated center. What you need to do is add the latest transition to the center as it was when the pan gesture was first recognized.
So either reset the translation (like in your posted code), or save the original center of the button when the gesture's state is .Began and apply the translation to the original center value.
Related
I have a sticker which is draggable as I drag it and while dragging it when my finger reaches below the white board and that delete imageview appears and when I have reached there location. How do I detect touch on that imageview.
I am using pan gesture for dragging and pinch and rotation gestures also at the same time. But how will I know that I have touched my delete ImageView
Here is the image which has a stickerView on the back which is draggable and has a delete imageView on the front of the stickerView which is fixed.
#objc func handlePanGesture(_ gestureRecognizer:UIPanGestureRecognizer){
switch gestureRecognizer.state {
case .began:
// panGestureInStickerViewClosure?(true)
break
case .changed:
// panGestureInStickerViewClosure?(true)
let deleteImageView = gestureRecognizer.view?.superview?.subviews.filter{$0.isKind(of: UIImageView.self)}[safe: 0]
deleteImageView?.addGestureRecognizer(BlockGestureRecognizer())
let stickerImageViewFrame = gestureRecognizer.view?.superview?.subviews.filter{$0.isKind(of: HBStickerView.self)}[safe: 0]
let translation = gestureRecognizer.translation(in: self)
print("translation=\(translation),deleteImageView?.frame=\(deleteImageView?.frame),stickerImageViewFrame?.frame=\(stickerImageViewFrame?.frame)")
break
case .ended:
break
default:
break
}
print(gestureRecognizer.numberOfTouches)
if gestureRecognizer.numberOfTouches == 1{
panGestureInStickerViewClosure?(true)
}else if gestureRecognizer.numberOfTouches > 1{
panGestureInStickerViewClosure?(false)
}
let translation = gestureRecognizer.translation(in: self.superview)
self.center = CGPoint(x: self.center.x + translation.x, y: self.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.superview)
print(gestureRecognizer.location(in: self.superview))
//print("self.frame.maxY=\(self.frame.maxY),self.superview?.frame.maxY=\(self.superview?.frame.maxY),self.center=\(self.center),gestureRecognizer.location(in: self)=\(gestureRecognizer.location(in: self))")
// panGestureInStickerViewClosure?(true)
}
I have above code written for pan gesture
I am using this gesture recognizer for panning. But getting translation location also does not work for me. If I can get the gesture location for my thumb or any finger and perform my need.
All I want to get touch event on that delete ImageView in front of all Subview while I am dragging the StickerView below that Subview.
Let me give you an example of Instagram. An Instagram has stickers which you can drag around and pinch and rotate and when it comes to deleting you have to touch on that delete button then the delete button gets scaled to max size and StickerView gets to scaled-down and finally is removed from the superView.
How can I make the scroll view stop moving when dragging on it, after that, how can I make it start scrolling immediately when finger already touching the scroll view?
self.scrollView.notMovingWhileDragging()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.scrollView.resumeMovingForTheAlreadyTouchingFinger()
}
For the above two functions, I set isScrollEnabled to false then true, but the scroll view not move if finger already touching it when above callback is called.
The result I want is like the iOS Maps app, when the sheet is on top, and the scroll view is been dragged a little distance from top. Now I start dragging down the scroll view, scroll view move down, when scroll view is been scroll to top, then keep dragging down, the sheet down(scroll view stop moving when dragging), then dragging up, the sheet view up, and when the sheet view is on top, keep dragging up, the scroll view start scrolling again (scroll view resume moving when finger already on it).
It's not possible. The isScrollEnabled property of a UIScrollView simply updates the isEnabled property of the UIScrollView's panGesture. UIPanGesture has a defined lifecycle that begins with touchesBegan as a touch down. and its state moves to .began, so enabling it while a touch has already begun will not cause it to fire.
In your original question you asked how to allow scrolling after handling the positioning of a sheet-style interface a la Apple Maps. This can be done by adding a target your scroll view's panGesture. For example, you can add a target like so tableView.panGestureRecognizer.addTarget(self, action: #selector(handlePan(_:))); then in your action you can say something like:
#objc func handlePan(_ sender: UIPanGestureRecognizer) {
let velocity = sender.velocity(in: view)
let translation = sender.translation(in: view)
switch sender.state {
case .began: break
case .changed:
// shouldMoveSheet can check to see if the sheet should move or scroll view should scroll based on content offset, pan translation, etc.
if shouldMoveSheet(for: translation) {
bottomConstraint.constant = newConstant
// while moving sheet set the content offset to the top so the scroll view does not scroll
tableView.setContentOffset(CGPoint(x: 0.0, y: tableView.adjustedContentInset.top), animated: false)
sender.setTranslation(.zero, in: view)
}
case .ended:
// Depending on if the sheet has been moved when the gesture ends move the sheet to the up or down position
if velocity.y < 0 && tableView.contentOffset.y <= tableView.adjustedContentInset.top {
bottomConstraint.constant = constantForSheetUp
} else if tableView.contentOffset.y <= tableView.adjustedContentInset.top {
bottomConstraint.constant = constantForSheetDown
}
UIView.animate(duration: 0.3) {
view.layoutIfNeeded()
}
case .failed, .cancelled, .possible:
break
}
}
Which will enable a pan up to some threshold then scrolling:
As described in the title, for some reason the position of all objects that have been moved is reset to its original position after .setBackgroundImage is called on some UIButton.
The original position of UI elements is set via StoryBoard after that I am changing the position of some elements using PanGestureRecognizer (For example menu slides up)
Simple like that:
#IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPoint.zero, in: self.view)
}
After that I have some button with background picture which is changed when user chooses theme change, .setBackgroundImage is preformed on this UIButton like this:
button.setBackgroundImage(image, for: UIControlState.normal)
After the image is changed on the button the position of translated object (menu that is slid up) is set back to its original position set via storyboard I want this menu to stay open even after image on some none related button is changed.
I'm using a rotation gesture recognizer to rotate a UIView. After I've rotated the view the x and y coordinates of the pan gesture are off their axis and I can't see why.
Here is my current gesture setup:
open func handleRotate(sender: UIRotationGestureRecognizer) {
if sender.state == .began || sender.state == .changed {
sender.view?.transform = (sender.view?.transform)!.rotated(by: sender.rotation)
sender.rotation = 0
}
}
#objc fileprivate func handleDrag(sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: self)
sender.view!.center = CGPoint(x: sender.view!.center.x + translation.x, y: sender.view!.center.y + translation.y)
sender.setTranslation(.zero, in: self)
}
So when I rotate the UIView the x and y panning are off (Moving up moves the uiview to the left or right depending upon the UIview's rotated state).
Thanks
There's quite a lot to unpack with the code example provided, but for starters the drag handler is setting the center point of the view, which might be a hint as to why the rotation gesture is askew.
Rather than setting the transform property of the view independently in each handler, you might get the result you're looking for if you modify the existing transforms.
The SO answer here goes into a bit more depth, and might help:
Using multiple UIGestureRecognizers simultaneously like UIRotationGestureRecognizer & UIPanGestureRecognizer in Swift 3
I have a movable UIAction Button similar to Reddit's Scroll up button and the Assistive Touch Feature. Right now I'm having a hard time finding a way to either:
1. Not go out of bounds in the first place or
2. Bounce Back to the screen similar tot he way that Assistive Touch does.
Here's what I have in my UIPanGesture function:
#IBAction func dragSettingsButton(_ sender: UIPanGestureRecognizer) {
if sender.state == .began || sender.state == .changed {
if displayView.frame.contains(sender.location(in: self.view)) {
let translation = sender.translation(in: displayView)
// note: 'view' is optional and need to be unwrapped
sender.view!.center = CGPoint(x: sender.view!.center.x + translation.x, y: sender.view!.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: displayView)
}
else {
print("WOW BRO YOURE OUT OF BOUNDS")
}
}
}
Things to note: I have a ViewController where there are 3 views: A view to act as a custom navigation bar, a view to act as a custom segmented view, and a display view to display different views when the segmented view is switched.
There is also a Tab bar on the bottom
To bounce back:
On sender.state == .end use the same logic to determine if its out of bounds. If so calculate where it should be. Then animate it to that location.