UIImages or UIViews are not colliding each other in iOS Swift? - ios

I am trying to collide two UIViews each other for using pan gesture. Now i can able to drag one view but when dragging view to next view its collide each other. It goes behind other view.
Here is the screenshot result:
Here is the code I used to drag, pan and collide of physics property.
import UIKit
class ViewController: UIViewController {
var greenSquare: UIView?
var redSquare: UIView?
var animator: UIDynamicAnimator?
var snap: UISnapBehavior!
var panGesture = UIPanGestureRecognizer()
#IBAction func pan(_ sender: UIPanGestureRecognizer) {
let tapPoint: CGPoint = sender.location(in: view)
if (snap != nil) {
animator?.removeBehavior(snap)
}
snap = UISnapBehavior(item: greenSquare!, snapTo: tapPoint)
animator?.addBehavior(snap)
}
override func viewDidLoad() {
super.viewDidLoad()
var dimen = CGRect(x: 25, y: 25, width: 120, height: 120)
greenSquare = UIView(frame: dimen)
greenSquare?.backgroundColor = UIColor.green
dimen = CGRect(x: 130, y: 25, width: 100, height: 100)
redSquare = UIView(frame: dimen)
redSquare?.backgroundColor = UIColor.red
self.view.addSubview(greenSquare!)
self.view.addSubview(redSquare!)
animator = UIDynamicAnimator(referenceView: self.view)
let gravity = UIGravityBehavior(items: [greenSquare!, redSquare!])
let direction = CGVector(dx: 0.0, dy: 1.0)
gravity.gravityDirection = direction
let boundries = UICollisionBehavior(items: [greenSquare!, redSquare!])
boundries.translatesReferenceBoundsIntoBoundary = true
let bounce = UIDynamicItemBehavior(items: [greenSquare!, redSquare!])
bounce.elasticity = 0.5
animator?.addBehavior(bounce)
animator?.addBehavior(boundries)
animator?.addBehavior(gravity)
panGesture = UIPanGestureRecognizer(target: self, action:#selector(draggedView(sender:)))
greenSquare?.isUserInteractionEnabled = true
greenSquare?.addGestureRecognizer(panGesture)
}
#objc func draggedView(sender:UIPanGestureRecognizer){
let translation = sender.translation(in: self.view)
greenSquare?.center = CGPoint(x: (greenSquare?.center.x)! + translation.x, y: (greenSquare?.center.y)! + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
}//class`
Anyone can help me out this.

The issue is that the animator only recognizes the collision at the origin of the greenSquare. In this case it is at the top of the screen. You can update the collision location on the animator after you move the greenSquare. Add animator?.updateItem(usingCurrentState: greenSquare!)
to the end of the draggedView method. You do not need to use UISnapBehavior for this so you can remove the IBAction func pan method at the top of your code. The updateItem(usingCurrentState:) call will reset where the collision barrier is when you are moving the view manually and it is not being done by the physics engine.

Related

Swift PanGesture : Problems Managing Constraints: Flash on New Pan

This is the code used to control a view with a pan-capable View.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var redRect: PaletteView!
#IBOutlet var paletteTop : NSLayoutConstraint?
#IBOutlet var paletteLeading : NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
addRedViewUI(tgt: redRect!)
redRect.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(palettePan(_:))))
redRect?.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(paletteConstraints())
}
func addRedViewUI(tgt : PaletteView) {
var pos = CGPoint(x: 10, y: 10)
var size = CGSize(width: 200, height: 240)
var v = UIView(frame: CGRect(origin: pos, size: size))
v.backgroundColor = .black
tgt.addSubview(v)
}
func paletteConstraints() -> Array<NSLayoutConstraint> {
var constraints : Array<NSLayoutConstraint> = []
constraints.append((redRect?.leadingAnchor.constraint( equalTo: view.leadingAnchor, constant: 100))!)
paletteLeading = constraints.last
constraints.append((redRect?.topAnchor.constraint( equalTo: view.topAnchor, constant: 100))!)
paletteTop = constraints.last
constraints.append((redRect?.widthAnchor.constraint(equalToConstant: 250))! )
constraints.append((redRect?.heightAnchor.constraint(equalToConstant: 350))! )
return constraints
}
func paletteConstraints(_ tgtView : PaletteView ) -> Array<NSLayoutConstraint> {
var constraints : Array<NSLayoutConstraint> = []
constraints.append((tgtView.leadingAnchor.constraint( equalTo: redRect!.leadingAnchor)))
constraints.append((tgtView.topAnchor.constraint( equalTo: redRect!.topAnchor)))
return constraints
}
#objc func palettePan(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: view)
switch sender.state {
case .began:
print("start \(redRect!.frame)")
paletteLeading?.constant = redRect!.frame.origin.x
paletteTop?.constant = redRect!.frame.origin.y
case .changed:
redRect!.transform = CGAffineTransform(translationX: translation.x, y: translation.y)
case .ended:
print("done \(redRect!.frame)")
default:
_ = true
}
}
}
class PaletteView : UIView {
var lastPos: CGPoint = CGPoint(x: 20, y: 20)
convenience init() {
self.init(frame: CGRect(x: 10, y: 10, width: 200, height: 200))
translatesAutoresizingMaskIntoConstraints = false
}
}
It works fine, except that when beginning a new pan, it jumps from its position from the end of the last pan, before instantaneously jumping back and the pan can be done. This jump is in the same direction as the last pan, so the second pan the view won't jump, and subsequent pans it jumps in the direction that the last pan went, in roughly the same amount.
This makes me think that it's something associated with the sender state, but I can't figure out what. If the amount of the last pan is small, or the pan is begun slowly, there's no flash, but I haven't been able to understand where that comes from.
The view being panned has no constraints in the storyBoard, but it does get defined and connected to its outlet in the storyboard.
The solution is to change the function so both start and changed perform the CGAffineTransform, after the constraints are updated in the case of changed:
#objc func palettePan(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: view)
switch sender.state {
case .began:
print("start \(redRect!.frame)")
paletteLeading?.constant = redRect!.frame.origin.x
paletteTop?.constant = redRect!.frame.origin.y
redRect!.transform = CGAffineTransform(translationX: translation.x, y: translation.y)
case .changed:
redRect!.transform = CGAffineTransform(translationX: translation.x, y: translation.y)
case .ended:
print("done \(redRect!.frame)")
default:
_ = true
}
}

Swift: restoring UIView center after moving it

I'm using gestures to zoom and move a UIImageView (like Instagram zoom, for example). When the gesture ends, I want to restore UIImageView initial position, but I cannot get a copy of the initial center because it's a reference type. Using:
let prevCenter = myImage.center
is of course useless. How can I copy it?
On zoom and move apply transform to view. On the end just set .identity value.
Edit
Example:
#IBOutlet weak var butt: UIButton!
var offsetTransform: CGAffineTransform = .identity
var zoomTransform: CGAffineTransform = .identity
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let pan = UIPanGestureRecognizer(target: self, action: #selector(onPan(pan:)))
pan.minimumNumberOfTouches = 2
pan.delegate = self
view.addGestureRecognizer(pan)
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(onPinch(pinch:)))
pinch.delegate = self
view.addGestureRecognizer(pinch)
}
#objc func onPinch(pinch: UIPinchGestureRecognizer) {
let s = pinch.scale
zoomTransform = CGAffineTransform(scaleX: s, y: s)
butt.transform = offsetTransform.concatenating(zoomTransform)
if (pinch.state == .ended) {
finish()
}
}
#objc func onPan(pan: UIPanGestureRecognizer) {
let t = pan.translation(in: view)
offsetTransform = CGAffineTransform(translationX: t.x, y: t.y)
}
func updatePos() {
butt.transform = offsetTransform.concatenating(zoomTransform)
}
func finish() {
butt.transform = .identity
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Are you sure that you are not updating the prevCenter somewhere else?
center is struct so you shouldn't have problems copying it.
Just make sure to take it (the center) when the view is in original state, before starting to zoom it and after it is drawn with correct frame
let imageView = UIImageView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
let prevCenter = view.center // 20, 20
imageView.center = CGPoint(x: 50, y: 50)
// imageView.center - 50, 50
// prevCenter - 20, 20
imageView.frame = CGRect(x: 40, y: 40, width: 10, height: 10)
// imageView.center - 45, 45
// prevCenter - 20, 20

How to add a UIPanGestureRecognizer to an shape drawn on CAShapLayer - swift?

I have an imageView that I have drawn a blue circle in its layer. I would like a user to be able to tap, hold and drag this blue circle anywhere within the UIImageView. I am unsure how to attach this shape to a UIPanGestureRecognizer. My effort so far is below:
class DrawCircleViewController: UIViewController {
#IBOutlet weak var imgView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// DRAW A FILLED IN BLUE CIRCLE
drawBlueCircle()
// ADD GESTURE RECOGNIZER
let panRecgonizer = UIPanGestureRecognizer.init(target: ???, action: <#T##Selector?#>)
}
func drawBlueCircle(){
let fourDotLayer = CAShapeLayer()
fourDotLayer.path = UIBezierPath.init(roundedRect: CGRect.init(x: 60, y: 60, width: 30, height: 30), cornerRadius: 50).cgPath
fourDotLayer.fillColor = UIColor.blue.cgColor
self.imgView.layer.addSublayer(fourDotLayer)
}
}
use this code to move the view
#objc func handlePanRecgonizer(_ gestureRecognizer: UIPanGestureRecognizer){
if panRecgonizer.state == .began || panRecgonizer.state == .changed {
let translation = panRecgonizer.translation(in: self.view)
panRecgonizer.view!.center = CGPoint(x: panRecgonizer.view!.center.x + translation.x, y: panRecgonizer.view!.center.y + translation.y)
panRecgonizer.setTranslation(CGPoint.zero, in: self.view)
}
}
if you want to add UIPanGestureRecognizer programmatically:
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanRecgonizer))
self.someDraggableView.addGestureRecognizer(gestureRecognizer)
As you might have noticed you can't add GestureRecognizers to CALayers. They can only be added to UIView types.
The solution is to add a subview to your imageview and draw the blue circle in it.
var drawingView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
drawingView.frame = imgView.bounds
imgView.addSubview(drawingView)
// DRAW A FILLED IN BLUE CIRCLE
drawBlueCircle()
// ADD GESTURE RECOGNIZER
let panRecgonizer = UIPanGestureRecognizer(target: drawingView, action: #selector())
drawingView.addGestureRecognizer(panRecgonizer)
}
func drawBlueCircle(){
let fourDotLayer = CAShapeLayer()
fourDotLayer.path = UIBezierPath.init(roundedRect: CGRect.init(x: 60, y: 60, width: 30, height: 30), cornerRadius: 50).cgPath
fourDotLayer.fillColor = UIColor.blue.cgColor
drawingView?.layer.addSublayer(fourDotLayer)
}

Unable to get UIPanGesturerRecognizer to work for child UIView of parent UIImageView - swift

I have an application that has a UIImageView that has a subview(UIView) that contains CAShapeLayer which i used to draw a blue circle. I would like the user to be able to drag the blue circle anywhere over the UIImageView. My problem is the following. I have made the CAShapeLayer to be the same CGRect as the parent UIView so that the user has to actually touch the blue circle before be able to drag it around. When I do this, my translations no longer work. However, if i make the the UIView frame the same as the parent UIImagView, the dragging of the blue circle is enabled, but then the user does not necessarily have to be touch the blue circle to be able to drag it around.
class DrawCircleViewController: UIViewController {
var circleSize = CGRect.init(x: 60, y: 80, width: 15, height: 15)
var firstDotView: UIView?
#IBOutlet weak var imgView: UIImageView!
// LAYERS
let layer1 = CAShapeLayer()
let layer2 = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
firstDotView = UIView.init()
firstDotView?.frame = circleSize
imgView.addSubview(firstDotView!)
// DRAW A FILLED IN BLUE CIRCLE
drawBlueCircle()
imgView.isUserInteractionEnabled = true
firstDotView!.isUserInteractionEnabled = true
// ADD GESTURE RECOGNIZER
let firstDotPanRecgnzr = UIPanGestureRecognizer.init(target: self, action: #selector(firstDotHandler(sender:)))
firstDotView?.addGestureRecognizer(firstDotPanRecgnzr)
}
#objc func firstDotHandler(sender: UIPanGestureRecognizer) -> Void {
let firstDot = sender.view
let translation = sender.translation(in: view)
if(sender.state == .began || sender.state == .changed){
firstDot?.center = CGPoint.init(x: (firstDot?.center.x)! + translation.x, y: (firstDot?.center.y)! + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
}
func drawBlueCircle(){
layer1.path = UIBezierPath.init(roundedRect: circleSize, cornerRadius: 50).cgPath
layer1.fillColor = UIColor.blue.cgColor
firstDotView?.layer.addSublayer(layer1)
}
}
sceen shot of the UI

Swift Collision Boundary not working

Swift 2.0, xcode 7, Ios 9
Intention: I want the two squares to drop to the bottom and stay at the bottom of the screen.
What is happening now: There is no error preventing the code from running, the gravity works fine but the collision just seems to be ignored. Thus resulting in the two objects falling through the bottom of the screen.
Note: the subclass is UIView not UIViewController and this view is accessed from a segue.
Many Thanks!
Code :
import UIKit
class graphs: UIView {
//create two shapes
var greenSquare: UIView?
var redSquare: UIView?
var animator: UIDynamicAnimator?
override func drawRect(rect: CGRect) {
var dimen = CGRectMake(25, 25, 60, 60)
greenSquare = UIView(frame: dimen)
greenSquare?.backgroundColor = UIColor.greenColor()
dimen = CGRectMake(130, 25, 90, 90)
redSquare = UIView(frame: dimen)
redSquare?.backgroundColor = UIColor.redColor()
//add them to the screen
self.addSubview(greenSquare!)
self.addSubview(redSquare!)
}
#IBAction func startbutton(sender: AnyObject) {
//initialise the animator
animator = UIDynamicAnimator()
//colision
let boundries = UICollisionBehavior(items:[greenSquare!,redSquare!])
boundries.translatesReferenceBoundsIntoBoundary = true
//add gravity
let gravity = UIGravityBehavior(items: [greenSquare!, redSquare!])
let direction = CGVectorMake(0.0, 1.0)
gravity.gravityDirection = direction
animator?.addBehavior(boundries)
animator?.addBehavior(gravity)
}
}
I fixed it by changing the subclass to a UIViewController
why did the previous code not work?
this is the new code:
import UIKit
class DrawView: UIViewController {
//Create two shapes
var greenSquare: UIView?
var redSquare: UIView?
var animator: UIDynamicAnimator?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func startbutton(sender: UIButton) {
//Create the shapes
var dimen = CGRectMake(25, 25, 60, 60)
greenSquare = UIView(frame: dimen)
greenSquare?.backgroundColor = UIColor.greenColor()
dimen = CGRectMake(130, 25, 90, 90)
redSquare = UIView(frame: dimen)
redSquare?.backgroundColor = UIColor.redColor()
//Add them to the screen
self.view.addSubview(greenSquare!)
self.view.addSubview(redSquare!)
//Initialize the animator
animator = UIDynamicAnimator(referenceView: self.view)
//Gravity
let gravity = UIGravityBehavior(items: [greenSquare!, redSquare!] )
let direction = CGVectorMake(0.0, 1.0)
gravity.gravityDirection = direction
//Collision
let boundries = UICollisionBehavior(items: [greenSquare!, redSquare!] )
boundries.translatesReferenceBoundsIntoBoundary = true
//Elasticity
let bounce = UIDynamicItemBehavior(items: [greenSquare!, redSquare!] )
bounce.elasticity = 0.5
animator?.addBehavior(bounce)
animator?.addBehavior(boundries)
animator?.addBehavior(gravity)
}
}

Resources