Detecting the collision b/w two objects in Swift 3.0? - ios

I've added 2 UIViews on my screen and I want to detect if they collide. If so, then I need to show an alert on the screen.

what about
if (CGRectIntersectsRect(secondView.frame, sender.frame)) {
// Do something
}

You can check wether 2 views are intersecting by checking if their frames are intersecting. Here is an example:
let view1 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let view2 = UIView(frame: CGRect(x: 90, y: 90, width: 50, height: 50))
extension UIView {
func intersects(_ otherView: UIView) -> Bool {
if self === otherView { return false }
return self.frame.intersects(otherView.frame)
}
}
print(view1.intersects(view2)) // Prints true because the 2 views are intersecting
You can call intersects(_:) every time you update any of your views frames (ie. change their size and/or position). If the method returns true, show an alert using UIAlertController.

// The Pan Gesture
func createPanGestureRecognizer(targetView: UIImageView)
{
var panGesture = UIPanGestureRecognizer(target: self, action:("handlePanGesture:"))
targetView.addGestureRecognizer(panGesture)
}
// THE HANDLE
func handlePanGesture(panGesture: UIPanGestureRecognizer) {
// get translation
var translation = panGesture.translationInView(view)
panGesture.setTranslation(CGPointZero, inView: view)
println(translation)
//create a new Label and give it the parameters of the old one
var label = panGesture.view as UIImageView
label.center = CGPoint(x: label.center.x+translation.x, y: label.center.y+translation.y)
label.multipleTouchEnabled = true
label.userInteractionEnabled = true
if panGesture.state == UIGestureRecognizerState.Began {
//add something you want to happen when the Label Panning has started
}
if panGesture.state == UIGestureRecognizerState.Ended {
//add something you want to happen when the Label Panning has ended
}
if panGesture.state == UIGestureRecognizerState.Changed {
//add something you want to happen when the Label Panning has been change ( during the moving/panning )
}
else {
// or something when its not moving
}
}

Related

Increase tap area for UITapGestureRecognizer on UILabel when size of the label increases

I have a UILabel on the UICollectionViewCell. On the UILabel I've got a UITapGestureRecognizer attached. I'm trying to increase the tap area of the UITapGestureRecognizer on the UILabel when the width of the UILabel increases.
Here is the sample of the code:
class BusCell: UICollectionViewCell {
var bus: Bus!
var tapGesture: UITapGestureRecognizer!
#IBOutlet weak var nameLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
addTapGestureToNameLabel()
}
// MARK: - UI
func addTapGestureToNameLabel() {
tapGesture = UITapGestureRecognizer(target: self, action: #selector(nameLabelDoubleTap(gesture:)))
tapGesture.numberOfTapsRequired = 2
nameLabel.addGestureRecognizer(tapGesture)
nameLabel.isUserInteractionEnabled = true
}
func configure(_ bus: Bus, isStereo: Bool = false) {
self.bus = bus
loadCellUI(bus: bus)
bus.updateBlock = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.loadCellUI(bus: bus)
}
}
func loadCellUI(bus: Bus) {
nameLabel.frame = CGRect(x: CGFloat(0), y: yPosition, width: 122, height: self.nameLabel.frame.height)
if bus.isStereo {
if bus.index % 2 == 0 {
let frame = nameLabel.frame
nameLabel.frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: 244, height: frame.height)
nameLabel.isHidden = false
// Make the tap frame same as the nameLabel's frame
} else {
nameLabel.isHidden = true
}
} else {
let frame = nameLabel.frame
nameLabel.frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: 122, height: frame.height)
nameLabel.isHidden = false
// Make the tap frame same as the nameLabel's frame
}
}
}
How do I make this work?
A tap gesture recognizer attaches to a view, and responds to taps inside the frame of the view. It doesn't have a tap area of its own. If you increase the size of the label then the tap area should increase in size as well.
I remember reading a recommendation from Apple that a tappable area be at least 40x40 points. You might want to put an invisible view (call it tapView) on top of your label that is slightly larger than the label (You could get the label's frame, and call CGRect.inset(by:) with negative values for all the edges. Use the resulting rect as the tapView's frame, and add the tap view on top of your label.) If you do that then you should put code in your view controller's viewDidLayoutSubviews() method (and any time you change your nameLabel label) to adjust the tapView's frame.

swift pan a imageView that is behind a view

Is it possible to pan an object that is behind another object?
Currently I have a imageview and a view with clear color and only a border around, that I can see the imageview. I want to achieve that the view with the border is always on top. Trough the view I want to pan the image view with pan gesture.
You can add Pan gesture to the view and apply the transformation to the imageView.
var translation = panGesture.translationInView(imageView)
panGesture.setTranslation(CGPointZero, inView: imageView)
Just set isUserInteractionEnabled false for upper view.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let bottomView = UIView.init(frame: .init(x: 100, y: 100, width: 300, height: 300))
bottomView.backgroundColor = .yellow
let upperView = UIView.init(frame: .init(x: 100, y: 100, width: 300, height: 300))
upperView.backgroundColor = .clear
upperView.layer.borderWidth = 1
upperView.layer.borderColor = UIColor.red.cgColor
upperView.isUserInteractionEnabled = false //<------------
view.addSubview(bottomView)
view.addSubview(upperView)
bottomView.addGestureRecognizer(UIPanGestureRecognizer.init(target: self, action: #selector(handle)))
}
#objc func handle() {
print("handletapgesture")
}
}

Getting details about the pinch gesture in ARKit

In my project I have a pinch to resize option for the object that has been placed in scene view. But when someone pinch the screen to reduce or enlarge the actual size of the object I need to get that scale. I need to display the scale in which the object is being changed in the screen. How do I get the scale when the action is being performed?
Thank you
Within your main ViewController Class for the ARSCNView
declare the label view, and the label itself at the top.
let scaleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, 70))
let labelView = UIView(frame: CGRect(x: 300, y: 300, width: 300, height: 70))
Now within LoadView or ViewDidLoad you can set the attributes for the label such backgroundColor, textColor etc... and also add the view and label to sceneView.
// add your attributes for label,view
labelView.backgroundColor = .clear
scaleLabel.textColor = .white
scaleLabel.adjustsFontSizeToFitWidth
// add you views to sceneView
labelView.addSubview(scaleLabel)
sceneView.addSubview(labelView)
Lastly, with the pinch gesture function for scaling.. which should look something like this.
#objc func pinchGesture(_ gesture: UIPinchGestureRecognizer) {
if nodeYouScale != nil {
let action = SCNAction.scale(by: gesture.scale, duration: 0.1)
nodeYouScale.runAction(action)
gesture.scale = 1
// this part updates the label with the current scale factor
scaleLabel.text = "X: \(nodeYouScale.scale.x) Y: \(nodeYouScale.scale.y) Z:\(nodeYouScale.scale.z)"
} else {
return
}

Popup view close button isn't working correctly

I am creating a popup view that displays on top of a blurred background. It seems to work fine apart from the fact that at the moment, occasionally when it appears, the close button doesn't work at all. This often seems to happen on the second use.
Here is my code:
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent) {
if motion == .MotionShake {
println("Shake")
if randomImageContainer.alpha == 0 {
var randomIndex = Int(arc4random_uniform(UInt32(randomImages.count)))
lightBlur = UIBlurEffect(style: UIBlurEffectStyle.Light)
blurRandomView = UIVisualEffectView(effect: lightBlur)
blurRandomView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
view.addSubview(blurRandomView)
randomImageContainer.frame = CGRect(x: 0, y: 0, width: 264, height: 308)
randomImageContainer.center.x = view.frame.width/2
randomImageContainer.center.y = view.frame.height/2
randomImageContainer.image = randomImages[randomIndex]
randomImageContainer.contentMode = .ScaleAspectFill
view.addSubview(randomImageContainer)
closeRandomButton.frame = randomImageContainer.frame
closeRandomButton.addTarget(self, action: "closeRandomView:", forControlEvents: UIControlEvents.TouchUpInside)
view.addSubview(closeRandomButton)
view.bringSubviewToFront(closeRandomButton)
UIView.animateWithDuration(1, animations: {
self.randomImageContainer.alpha = 1
self.blurRandomView.alpha = 1
})
}
}
}
func closeRandomView(sender: UIButton!) {
println("Activated")
UIView.animateWithDuration(1, animations: {
self.randomImageContainer.alpha = 0
self.blurRandomView.alpha = 0
}, completion: { (finished:Bool) in
self.blurRandomView.removeFromSuperview()
self.randomImageContainer.removeFromSuperview()
self.closeRandomButton.removeFromSuperview()
})
}
The alpha value for the image and blur view are being set in viewDidLoad(), so they definitely pass the if statement first time around.
I can't see the problem, so I was wondering if anyone could notice any issues?
Thanks.
Figured it out. There was two issues. First of all, I was removing the views from the SuperView which meant they couldn't be checked in the if statement. The other problem was that there was a different view being brought to the front elsewhere in my code which meant the button was unaccessible.

can't access object within UIPanGestureRecognizer

I am doing a bezier animation with programmatically generated objects. The number of objects could be bigger than 100. These objects are also responsive to touch.
The animation works fine. Now I would like to make some objects drag-able.
However, I can't access the individual objects in the UIPanGestureRecognizer function. I assume I m doing something wrong with class / subclass calling, but can't think of it..
The tutorials I looked into had IBOutlets or dedicated class variables for every animated object on screen.. I do have a potential high number of objects that are generated within the FOR loop..
What approach do you suggest?
func panning(pan: UIPanGestureRecognizer) {
self.view.bringSubviewToFront(pan.view!)
var translation = pan.translationInView(self.view)
pan.view.center = CGPointMake(pan.view.center.x + translation.x, pan.view.center.y + translation.y)
pan.setTranslation(CGPointZero, inView: self.view)
let panGesture = UIPanGestureRecognizer(target: self, action: "panning:");
}
}
.. within viewDidLoad the function gets called:
// loop from 0 to 50
for i in 0...50 {
// create a square object
let square = UIView()
square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
square.backgroundColor = UIColor.redColor()
self.view.addSubview(square)
let recognizer = UIPanGestureRecognizer(target: self, action: "panning:");
recognizer.delegate = self;
square.addGestureRecognizer(recognizer)
panGesture.delegate = self;
Thanks to the feedback from Rob, here's an updated version:
I don't really know how to do add the AllowUserInteraction to CAKeyframeAnimation (Rob's answer 3a), as it does not have an "option" field (I m a noob).
Stopping the animation for the object being moved (Rob's answer 3b) completely eludes me. But its something definitely necessary in here.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
srand48(Int(NSDate().timeIntervalSince1970))
// loop from 0 to 5
for i in 0...5 {
// create a square
let square = UIView()
square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
square.backgroundColor = UIColor.redColor()
self.view.addSubview(square)
//square.userInteractionEnabled = true;
let recognizer = UIPanGestureRecognizer(target: self, action: "panning:");
square.addGestureRecognizer(recognizer)
// randomly create a value between 0.0 and 150.0
let randomYOffset = CGFloat( drand48() * 150)
// for every y-value on the bezier curve
// add our random y offset so that each individual animation
// will appear at a different y-position
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: -16,y: 239 + randomYOffset))
path.addCurveToPoint(CGPoint(x: 375, y: 239 + randomYOffset), controlPoint1: CGPoint(x: 136, y: 373 + randomYOffset), controlPoint2: CGPoint(x: 178, y: 110 + randomYOffset))
// create the animation
let anim = CAKeyframeAnimation(keyPath: "position")
anim.path = path.CGPath
anim.rotationMode = kCAAnimationRotateAuto
anim.repeatCount = Float.infinity
//anim.duration = 5.0
// each square will take between 4.0 and 8.0 seconds
// to complete one animation loop
anim.duration = 4.0 + 3 * drand48()
// stagger each animation by a random value
// `290` was chosen simply by experimentation
anim.timeOffset = 290 * drand48()
// add the animation
square.layer.addAnimation(anim, forKey: "animate position along path")
}
}
func panning(pan: UIPanGestureRecognizer) {
if pan.state == .Began {
println("pan began")
self.view.bringSubviewToFront(pan.view!)
} else if pan.state == .Changed {
println("pan state changed")
var translation = pan.translationInView(self.view)
pan.view?.center = CGPointMake(pan.view!.center.x + translation.x, pan.view!.center.y + translation.y)
pan.setTranslation(CGPointZero, inView: self.view)
println("translation")
}
}
Two things jump out at me:
Your for loop is setting delegates for recognizer (which is not needed unless you're doing something particular which you haven't shared with us) and panGesture (which should be removed because panGesture is not a variable that you have set in this for loop and seems to be completely unrelated). Thus it would be:
for i in 0...50 {
let square = UIView()
square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
square.backgroundColor = UIColor.redColor()
self.view.addSubview(square)
let recognizer = UIPanGestureRecognizer(target: self, action: "panning:");
square.addGestureRecognizer(recognizer)
}
In panning you are instantiating a new panGesture: You definitely want to get rid of that. A gesture handler has no business creating new gestures. So get rid of the line that starts with let panGesture = ....
In your original question, you hadn't shared the animation you're doing. If you were animating using block-based animation, you have to specify the UIViewAnimationOptions.AllowUserInteraction options. Also, if you'd start dragging it around a view that is being animated, you'd also want to (a) stop the animation for that view; and (b) reset the frame in accordance with the presentationLayer.
But, it's now clear that you're using CAKeyframeAnimation, in which case, there is no AllowUserInteraction mechanism. So, instead, you have to add the gesture recognizer to the superview, and then iterate through the frames of the squares (as represented by their presentation layer, i.e. the location mid-animation) and test to see if you get any hits.
It's up to you, but I find the pan gesture is a little slow to start recognizing the gesture (as it differentiates between a pan and a tap. In this case, you want something a little more responsive, methinks, so I might use a long press gesture recognizer with a
So, pulling that all together, you end up with something like:
var squares = [UIView]() // an array to keep track of all of the squares
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
srand48(Int(NSDate().timeIntervalSince1970))
// loop from 0 to 5
for i in 0...5 {
// create a square
let square = UIView()
square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
square.backgroundColor = UIColor.redColor()
self.view.addSubview(square)
squares.append(square)
// randomly create a value between 0.0 and 150.0
let randomYOffset = CGFloat( drand48() * 150)
// for every y-value on the bezier curve
// add our random y offset so that each individual animation
// will appear at a different y-position
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: -16,y: 239 + randomYOffset))
path.addCurveToPoint(CGPoint(x: 375, y: 239 + randomYOffset), controlPoint1: CGPoint(x: 136, y: 373 + randomYOffset), controlPoint2: CGPoint(x: 178, y: 110 + randomYOffset))
// create the animation
let anim = CAKeyframeAnimation(keyPath: "position")
anim.path = path.CGPath
anim.rotationMode = kCAAnimationRotateAuto
anim.repeatCount = Float.infinity
//anim.duration = 5.0
// each square will take between 4.0 and 8.0 seconds
// to complete one animation loop
anim.duration = 4.0 + 3 * drand48()
// stagger each animation by a random value
// `290` was chosen simply by experimentation
anim.timeOffset = 290 * drand48()
// add the animation
square.layer.addAnimation(anim, forKey: "animate position along path")
}
let recognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPress:");
recognizer.minimumPressDuration = 0
view.addGestureRecognizer(recognizer)
}
var viewToDrag: UIView! // which of the squares are we dragging
var viewToDragCenter: CGPoint! // what was the `center` of `viewToDrag` when we started to drag it
var originalLocation: CGPoint! // what was gesture.locationInView(gesture.view!) when we started dragging
func handleLongPress(gesture: UILongPressGestureRecognizer) {
let location = gesture.locationInView(gesture.view!)
if gesture.state == .Began {
for square in squares {
let presentationLayer = square.layer.presentationLayer() as CALayer
let frame = presentationLayer.frame
if CGRectContainsPoint(frame, location) {
viewToDrag = square
viewToDragCenter = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
viewToDrag.layer.removeAnimationForKey("animate position along path")
viewToDrag.center = viewToDragCenter
originalLocation = location
return
}
}
} else if gesture.state == .Changed {
if viewToDrag != nil {
var translation = CGPointMake(location.x - originalLocation.x, location.y - originalLocation.y)
viewToDrag.center = CGPointMake(viewToDragCenter.x + translation.x, viewToDragCenter.y + translation.y)
}
} else if gesture.state == .Ended {
viewToDrag = nil
}
}

Resources