I'm try to implement this in my project. But I have some troubles with it. I plan to use UiPanGestureRecognizer for change size of rectangle. As I understand, I should be use UIVIew and custom drawRect method?
Add an action for your UIPanGestureRecognizer and use translation:
func wasDragged(gesture: UIPanGestureRecognizer) {
let translation = gesture.translationInView(self.view)
// Do your resizing here, e.g. from a
customView.frame.size.width = currentFrame.width + translation.x
customView.frame.size.height = currentFrame.height + translation.y
if gesture.state == .Ended {
currentFrame = customView.frame
}
}
Using this method, add a CGRect variable to store the currentFrame.
Related
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 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: ()
}
}
I have been trying to implement a Snapchat-like edit text on an image.
What I did so far is implement a UILabel in the center of the UIImageView and I added 3 gestures to this UILabel: UIPanGestureRecognizer, UIPinchGestureRecognizer & UIRotationGestureRecognizer.
I have managed to implement the Pan method, but I am having hard time to make the Pinch + Rotation as smooth as they do, I get horrible results T_T
How do you guys think this was made? which components are involved in this & if you have any reading / watching material I could use to accomplish this.
Thanks :)
EDIT:
These are the methods I implemented to handle Pinch & Rotation:
func handlePinch(recognizer: UIPinchGestureRecognizer) {
if let view = recognizer.view as? UILabel {
view.transform = CGAffineTransformScale(view.transform, recognizer.scale, recognizer.scale)
}
}
func handleRotate(recognizer: UIRotationGestureRecognizer) {
if let view = recognizer.view as? UILabel {
view.transform = CGAffineTransformRotate(view.transform, recognizer.rotation)
}
}
Preview video of how the pinch I implemented works:
https://drive.google.com/file/d/0B-AVM51jxsvUY2RUUHdWbGo5QlU/view?usp=sharing
Found a solution, apparently all I needed to do in the Rotation & Pinch is to always reset the view's rotation / scale.
For instance, setting my recognizer.scale to 1.0 and recognizer.rotation to 0.0.
Here is an example of my code:
func handlePan(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(view)
if let view = recognizer.view {
view.center = CGPoint(x:view.center.x + translation.x,
y:view.center.y + translation.y)
}
recognizer.setTranslation(CGPointZero, inView: view)
}
func handlePinch(recognizer: UIPinchGestureRecognizer) {
if let view = recognizer.view as? UILabel {
let pinchScale: CGFloat = recognizer.scale
view.transform = CGAffineTransformScale(view.transform, pinchScale, pinchScale)
recognizer.scale = 1.0
}
}
func handleRotate(recognizer: UIRotationGestureRecognizer) {
if let view = recognizer.view as? UILabel {
let rotation: CGFloat = recognizer.rotation
view.transform = CGAffineTransformRotate(view.transform, rotation)
recognizer.rotation = 0.0
}
}
For "it scales up / down way too much in a very aggressive way":
You need to handle the pinch gesture scale value according to your need.
From your recogniser method, you get the scale value as:
var pinchScale: CGFloat = recogniser.scale
You need to modify this value either like decrease it by /10 or /100 as per your need, and use this value in the label transformation instead of using the pangesture scale.
The issue you have is that your code takes the current transform and adds another transform based on the current "movement", so you accumulate changes (compound them, really) as you move during a single gesture.
Keep instance variables for rotation, scale, and movement, update the relevant one in each of your gesture recognizer's actions (you'll also need to store the state of each at the beginning of each gesture, so you can apply the delta to the initial state), and create the transform from scratch using those three variables. The transform creating should of course be factorized in a separate function, since you're going to use it several times.
I'm not sure if this is exactly what you're looking for (I've never used snapchat), but this could give you some ideas
https://github.com/danielinoa/DIImageView
I'm not sure this one has a pinch gesture, but I'm not entirely sure how you mean it to be used anyway.
Here is a demo of that project:
https://www.cocoacontrols.com/controls/diimageview
In general, I recommend checking cocoacontrols every time you venture to implement something like this. There are usually solid examples to get you started, and sometimes, you'll find exactly what you need.
I am developing a SpriteKit application in iOS using Swift, and I have implemented pan and pinch gestures for my map. The code looks like this:
//Zoom recognizer
let pinch: UIPinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: Selector("pinch:"))
view.addGestureRecognizer(pinch)
// Move recognizer
let pan: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("pan:"))
view.addGestureRecognizer(pan)
func pinch(sender: UIPinchGestureRecognizer){
var anchorPoint: CGPoint = sender.locationInView(sender.view)
anchorPoint = convertPointFromView(anchorPoint)
if (sender.state == UIGestureRecognizerState.Changed) {
let anchorPointInMySKNode: CGPoint = world!.convertPoint(anchorPoint, fromNode: self)
world!.setScale(world!.xScale * sender.scale)
let mySKNodeAnchorPointInScene: CGPoint = self.convertPoint(anchorPointInMySKNode, fromNode: world!)
let translationOfAnchorInScene = CGPointSubtract(anchorPoint, point2: mySKNodeAnchorPointInScene)
world!.position = CGPointAdd(world!.position, point2: translationOfAnchorInScene)
sender.scale = 1.0
}
}
func pan(sender: UIPanGestureRecognizer){
if(sender.state == UIGestureRecognizerState.Began){
sender.setTranslation(CGPointZero, inView: sender.view)
}
else if (sender.state == UIGestureRecognizerState.Changed){
var translation: CGPoint = sender.translationInView(sender.view!)
translation = CGPointMake(-translation.x, translation.y)
world!.position = CGPointSubtract(world!.position, point2: translation)
sender.setTranslation(CGPointZero, inView: sender.view)
}
}
The code works fine but currently it is written in GameScene.swift class, however it would be better if could create a separate class called Gestures.swift and just call which ever gesture I want. I have tried several approaches but none of them work. The two main issues are how to address the selector in the GestureRecognizer function and how to pass the parent node which is in my case world initialized inside GameScene.swift
You need to use delegation. I have used something of this nature in my games (but in obj-c). Delegation is the way to go.
Here are a few resources I would recommend.
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
http://code.tutsplus.com/tutorials/swift-from-scratch-delegation-and-properties--cms-23445
https://www.youtube.com/watch?v=AHx4nE7EMic
http://swift.exomachina.com/swift-tutorial-8-from-one-scene-to-another-using-delegate-protocol/
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