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: ()
}
}
Related
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 am rather new to iOS and swift 3 programming. At the moment I am working on a little learning project. I have the following problem.
On my screen I have 4 UIViews and 4 UIImageViews. The app allows to “drag and drop” an image onto one of the UIViews. This works pretty well so far. I use Pan Gesture Recognisers for the dragging of UIImages. But for some reason one of the UIViews seems to be in front of the image, meaning that I can drop the image to this area, but I cannot see it - it is layered under the UIView. The other three behave properly, the UIImageView is layered on top of the UIView. In the viewDidLoad() function of the view controller I set the background color of the UIViews to grey. If I don’t do that, the image will be displayed even on the fourth UIView.
I have checked for differences between the four UIView objects but cannot find any. I also tried to recreate the UIView by copying it from one of the the other three.
So my question is: Is there any property which defines that the UIView is layered on top or can be sent to back so that other objects are layered on top of it? Or is there anything else I am missing? Any hints would be very appreciated.
I was not quite sure which parts of the code are relevant, so I posted some of it (but still guess that this is rather a property of the UIView…)
This is where I set the background of the UIViews
override func viewDidLoad() {
super.viewDidLoad()
lbViewTitle.text = "\(viewTitle) \(level) - \(levelPage)"
if (level==0) {
print("invalid level => EXIT")
} else {
loadData(level: level)
originalPositionLetter1 = letter1.center
originalPositionLetter2 = letter2.center
originalPositionLetter3 = letter3.center
originalPositionLetter4 = letter4.center
dropArea1.layer.backgroundColor = UIColor.lightGray.cgColor
dropArea2.layer.backgroundColor = UIColor.lightGray.cgColor
dropArea3.layer.backgroundColor = UIColor.lightGray.cgColor
dropArea4.layer.backgroundColor = UIColor.lightGray.cgColor
}
}
This is the code that moves the image:
#IBAction func hadlePan1(_ sender: UIPanGestureRecognizer) {
moveLetter(sender: sender, dropArea: dropArea4, movedImage: letter1, originalPosition: originalPositionLetter1)
}
func moveLetter(sender: UIPanGestureRecognizer, dropArea : UIView, movedImage : UIImageView, originalPosition : CGPoint) {
let translation = sender.translation(in: self.view)
if let view = sender.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
if sender.state == UIGestureRecognizerState.ended {
let here = sender.location(in: self.view)
if (dropArea.frame.contains(here)) {
movedImage.center = dropArea.center
} else {
movedImage.center = originalPosition
}
}
}
sender.setTranslation(CGPoint.zero, in: self.view)
}
There are two methods that may be useful to you: UIView.sendSubview(toBack:) and UIView.bringSubview(toFront:).
What I think you should do is to call bringSubview(toFront:) when you start dragging the image:
func moveLetter(sender: UIPanGestureRecognizer, dropArea : UIView, movedImage : UIImageView, originalPosition : CGPoint) {
self.view.bringSubview(toFront: movedImage) // <--- add this line!
let translation = sender.translation(in: self.view)
if let view = sender.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
if sender.state == UIGestureRecognizerState.ended {
let here = sender.location(in: self.view)
if (dropArea.frame.contains(here)) {
movedImage.center = dropArea.center
} else {
movedImage.center = originalPosition
}
}
}
sender.setTranslation(CGPoint.zero, in: self.view)
}
I'm not sure whether this has been asked or not, but I failed to find a solution. I'm implementing panning gesture on a button, but the idea is: the button is fixed to a position, and when the user drags it, a copy of the button is created and moving with the gesture; the original one stays at its initial place (so there'll be 2 buttons in the view). When the panning ends, the new button is used for some processing, and after that it should disappear (the original one stays as it is; so this whole process can repeat). Currently what I have is as below:
private func addPanGesture() {
for btn in self.selectors { //selectors is a list of buttons which needs this gesture
let pan = UIPanGestureRecognizer(target: self, action:#selector(self.panDetected(_:)))
pan.minimumNumberOfTouches = 1
pan.maximumNumberOfTouches = 1
btn.addGesturerecognizer(pan)
}
}
#objc private func panDetected(_ panGesture: UIPanGestureRecognizer) {
var translation = panGesture.translation(in: view)
panGesture.setTranslation(CGPoint(x: 0, y: 0), in: view)
var newButton = UIButton()
if let initButton = panGesture.view as? UIButton {
print ("Button recognized!") // this msg is printed out
newButton.center = CGPoint(x: initButton.center.x + translation.x, y: initButton.center.y + translation.y)
newButton.setImage(UIImage(named: "somename"), for: .normal)
}
if panGesture.state == UIGestureRecognizerState.began {
self.view.addSubview(newButton)
}
if panGesture.state == UIGestureRecognizerState.ended {
//some other processing
}
if panGesture.state == UIGestureRecognizerState.changed {
self.view.addSubview(newButton)
}
// printed-out msgs show began, ended, changed states have all been reached
}
But the new button doesn't show up in my view. May I know how to solve this?
You need to create and add the new button as a subview only on .began and remove it on .ended.
Therefore you need to keep a reference to the new button.
You are setting the new button's center but not it's size. You might set its .frame.
You do not need to set a translation to the pan gesture. When you get var translation = panGesture.translation(in: view) you get everything you need.
I have wrote the below code for only one button, but if you are going to allow simultaneous dragging of buttons, you would need to keep a list of moving buttons instead of var movingButton: UIButton?
private func addPanGesture() {
let pan = UIPanGestureRecognizer(target: self, action:#selector(self.panDetected(_:)))
pan.minimumNumberOfTouches = 1
pan.maximumNumberOfTouches = 1
btn.addGestureRecognizer(pan)
}
#objc private func panDetected(_ panGesture: UIPanGestureRecognizer) {
let translation = panGesture.translation(in: view)
let initButton = panGesture.view as! UIButton
if panGesture.state == UIGestureRecognizerState.began {
// this is just copying initial button
// this might be overkill
// just make sure you set the frame, title and image of the new button correctly
let initButtonData = NSKeyedArchiver.archivedData(withRootObject: initButton)
let newButton = NSKeyedUnarchiver.unarchiveObject(with: initButtonData) as! UIButton
// we store new button's reference since we will just move it while it is added to view
movingButton = newButton
self.view.addSubview(movingButton!)
}
if panGesture.state == UIGestureRecognizerState.ended {
//some other processing
// when we are done just we just remove it from superview
movingButton!.removeFromSuperview()
}
if panGesture.state == UIGestureRecognizerState.changed {
// at any change, all we need to do is update movingButton's frame
var buttonFrame = initButton.frame;
buttonFrame.origin = CGPoint(x: buttonFrame.origin.x + translation.x, y: buttonFrame.origin.y + translation.y)
movingButton!.frame = buttonFrame
}
}
Hard to say without debugging it, but a few things I see:
You create a new button every time through panDetected, and add it to the view each time. You should only create an add the button in the .began state.
You should use init(frame:) to create your button, and initialize it to the size of the image.
It looks like you're attaching the pan gestures to the buttons. Then you get the pan coordinates in the button's coordinate system, which doesn't make sense. You should be converting the pan gesture to the button's superview's coordinate system, and should not be calling setTranslation except when the pan gesture's state is .began.
You should be setting the button's coordinates to the new location of the pan gesture each time you get a 1st.changed` message.
I’m using UIPanGestureRecognizer to move UIImageView. Now i’m need to check if user moved my object within other vectored-PDF UIImageView.
For example we have red bar that we are moving and we need to move red bar inside blue bar (blue bar is vector PDF). How can we check that?
Example image: http://i.imgur.com/PKXKHBi.png
Example code:
#IBAction func moveRedBox(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self.view)
recognizer.view!.center = CGPoint(x:recognizer.view!.center.x + translation.x,
y:recognizer.view!.center.y + translation.y)
recognizer.setTranslation(CGPointZero, inView: self.view)
if recognizer.state == UIGestureRecognizerState.Ended {
// Here we will check that redBox inside blueBox
}
}
To check if two view's overlap you can use the CGRectIntersectsRect function.
Update: Just found out that there is also an intersects method on CGRect in Swift
let doRectsIntersect = rect1.intersects(rect2)
Check this out. This helped me:
if view1.frame.contains(view2.center) {
// do your thing
}
Full example:
#IBAction func moveRedBox(recognizer: UIPanGestureRecognizer) {
...
switch recognizer.state {
case .Ended:
if blueBox.frame.contains(recognizer.view!.center) {
print("Yep")
}
}
}
Check position of UIImageView
image.frame.origin.x >= view.frame.origin.x
image.frame.origin.y >= view.frame.origin.y
put this code when the movement done
I'm making an iOS8 app using Swift. I'd like the user to be able to use gestures to reveal certain parts of the interface. So for example, the user slides their finger up and the view they slid their finger up moves out of the way, following their finger to reveal another view underneath.
What I'd like is a gesture to give a result similar to the notification box that you can pull down from the top of the screen. I've been looking at the documentation and I can't seem to find a suitable gesture.
I saw one called UISwipeGestureRecogniser, but the only problem is, it doesn't follow your finger, it simply runs a function when I slide my finger up / down.
Here's the documentation page:
https://developer.apple.com/documentation/uikit/uigesturerecognizer
You're looking for the UIPanGestureRecognizer. You'll find the Apple Documentation here.
Here's a sample handler that will move a view with your finger. In Interface Builder, add a UIPanGestureRecognizer to a view that you want to be able to drag. Set the delegate to your ViewController. Set the action to this action:
Swift 2.X:
#IBAction func handlePan(gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .Began || gestureRecognizer.state == .Changed {
let translation = gestureRecognizer.translationInView(self.view)
// note: 'view' is optional and need to be unwrapped
gestureRecognizer.view!.center = CGPointMake(gestureRecognizer.view!.center.x + translation.x, gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPointMake(0,0), inView: self.view)
}
}
Swift 3:
#IBAction func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.view)
// note: 'view' is optional and need to be unwrapped
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)
}
}
Of course, you can add the UIPanGestureRecognizer programmatically:
In viewDidLoad for your ViewController, create the recognizer and add it to the view you want to be able to drag:
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
self.someDraggableView.addGestureRecognizer(gestureRecognizer)