Prevent dragged object from leaving view its assigned to - ios

I'm trying to write a piece of code in iOS using swift that creates a square where the user touches and lets them drag it around. The catch is I want the area it can move around in to be confined to the UIView it was created it.
The code below almost works. You can only create the square by pressing within the box, but then you can just drag it where you want. I'm not picky about if the box stays in the "fence" and tracks with your finger or just disappears until you move your finger back in, but I can't have it all over the screen.
I'm pretty new to this, so if there's a better way to go about it, I'm happy to be corrected.
class ViewController: UIViewController {
var dragableSquare = UIView() // a square that will appear on press and be dragged around
var fence = UIView() // a view that the square should be confined within
override func viewDidLoad() {
super.viewDidLoad()
// define the fence UIView and it to view
fence.frame = CGRectMake(view.frame.width/2 - 100, view.frame.height/2 - 100, 200, 200)
fence.backgroundColor = UIColor.grayColor()
view.addSubview(fence)
// give the fence a gesture recognizer
var pressRecog = UILongPressGestureRecognizer(target: self, action: "longPress:")
pressRecog.minimumPressDuration = 0.001
fence.addGestureRecognizer(pressRecog)
}
func longPress(gesture: UILongPressGestureRecognizer) {
print("press!")
// get location of the press
var touchPoint = gesture.locationInView(fence)
// When the touch begins place the square at that point
if gesture.state == UIGestureRecognizerState.Began {
print("began")
// create and add square to fence view
dragableSquare.frame = CGRectMake(touchPoint.x-5, touchPoint.y-5, 10, 10)
dragableSquare.backgroundColor = UIColor.blueColor()
self.fence.addSubview(dragableSquare)
// While the press continues, update the square's location to the current touch point
} else {
print("moving")
dragableSquare.center = touchPoint
}
}
I just joined stack overflow and I've been really impressed with how generous and helpful the community is. I hope I'll get enough experience to start helping others out soon too.

You can use CGRectIntersection to get the size of the intersection rectangle between to views. In your case, you want to keep moving the square as long as the intersection rectangle between the square and the fence is the same size as the square (meaning the square is still wholly within the fence). So your else clause should look like this,
} else {
print("moving")
if CGRectIntersection(dragableSquare.frame, fence.bounds).size == dragableSquare.frame.size {
dragableSquare.center = touchPoint
}
}

Related

Xcode 9 Swift 4 - Reposition views by dragging

Im quite new to iOS development. But have years of programming experience.
Anyway, Im having a hard time finding a solution for my problem.
In my app i render rows of colored circles based on data from the server.
Each of these circles has different properties set to them on the server.
One of these is the "offset" property.
This should be used to render the circle with a distance from its left sibling, or the start of the parent view if its the first.
Each circle should then also be able to be moved by dragging it to the right or left. But never less then 0 from its left sibling.
In android this was very easy, just set the left-margin on drag, and all was good.
But in xcode im having a very hard time figuring out how to get this done.
Im sure its me thats way to inexperienced. So I hope someone that has a bit more knowledge about swift can help me with this.
Heres some images to make clear what Im looking to achive.
First render where one circle has an offset
The gesture where the 3. last circle is drages to the right
The result of the gesture
I need this to move seamless, so not reposiotioning after the gesture ends, but move along with the finger.
As you can see, the circles right of the one that is drages, keep their relative position to the one that is moved.
Thank you.
There are couples of ways to do this.The First possible solution can be using the Swipe gestures to move the objects.
override func viewDidLoad() {
super.viewDidLoad()
let swipeGesture = UISwipeGestureRecognizer(target: self, action: "handleSwipe:")
swipeGesture.direction = [.Down, .Up]
self.view.addGestureRecognizer(swipeGesture)
}
func handleSwipe(sender: UISwipeGestureRecognizer) {
print(sender.direction)
}
Use these Gestures to move along the objects with your fingers either you can use .left and .right gestures depending upon your need.
The Second solution for drag components can be a Pan Gesture
func detectPan(recognizer:UIPanGestureRecognizer) {
var translation = recognizer.translationInView(self.superview!)
self.center = CGPointMake(lastLocation.x + translation.x, lastLocation.y + translation.y)
}
The translation variable detects the new coordinates of the view when panning. The center of the view will be adjusted according to the changed coordinates.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
// Promote the touched view
self.superview?.bringSubviewToFront(self)
// Remember original location
lastLocation = self.center
}
When the view is clicked, the current view will be displayed in front of the other views and the center of the view will be assigned to the lastlocation variable
Hope this helps.

Move objects around, with gesture recognizer for multiple Objects

I am trying to make an app where you can use Stickers like on Snapchat and Instagram. It fully worked to find a technique, that adds the images, but now I want that if you swipe the object around the object changes its position (I also want to make the scale / rotate function).
My code looks like this:
#objc func StickerLaden() {
for i in 0 ..< alleSticker.count {
let imageView = UIImageView(image: alleSticker[i])
imageView.frame = CGRect(x: StickerXScale[i], y:StickerYScale[i], width: StickerScale[i], height: StickerScale[i])
ImageViewsSticker.append(imageView)
ImageView.addSubview(imageView)
imageView.isUserInteractionEnabled = true
let aSelector : Selector = "SlideFunc"
let slideGesture = UISwipeGestureRecognizer(target: self, action: aSelector)
imageView.addGestureRecognizer(slideGesture)
}
}
func SlideFunc(fromPoint:CGPoint, toPoint: CGPoint) {
}
Here are the high-level steps you need to take:
Add one UIPanGestureRecognizer to the parent view that has the images on it.
Implement UIGestureRecognizerDelegate methods to keep track of the user touching and releasing the screen.
On first touch, loop through all your images and call image.frame.contains(touchPoint). Add all images that are under the touch point to an array.
Loop through the list of touched images and calculate the distance of the touch point to the center of the image. Chose the image whose center is closest to the touched point.
Move the chosen image to the top of the view stack. [You now have selected an image and made it visible.]
Next, when you receive pan events, change the frame of the chosen image accordingly.
Once the user releases the screen, reset any state variables you may have, so that you can start again when the next touch is done.
The above will give you a nicely working pan solution. It's a good amount of things you need to sort out, but it's not very difficult.
As I said in my comment, scale and rotate are very tricky. I advise you to forget that for a bit and first implement other parts of your app.

How to define a zone in picture in Swift 3

I would like to define a clickable zone in a picture for an iOS Project.
It seems like "Where's Wally". When I clicked in a specific zone, Swift do this action.
There are the swipe to manage too.
I thought to make a transparent button but I don't know if it's possible..
Have you any ideas ?
Thank's ! :-)
For iOS, you can do it this way:
(1) Remember to set your UIImageView to receive user interactions, and add a UITapGestureRecognizer to the UIImageView.
myImageView.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer()
tapGesture.numberOfTapsRequired = 1
tapGesture.addTarget(self, action: #selector(tapDetected())
(2) Create a new CALayer, size it, position it, and add it to the UIImageView's layer.
let tappableLayer = CALayer() // place this as a global variable
tappableLayer.frame = // give a CGRect size here, or you can make it a shape by declaring a UIBezierPath and making it a CAShapeLayer
myImageView.layer.addSublayer(tappableLayer)
(3) Code the tapDetected function.
func tapDetected(_ recognizer:UITapGestureRecognizer) {
let p = recognizer.location(in: self)
if tappableLayer.hitTest(p) != nil {
// user tapped in your defined area of the image
}
}

Create clickable body diagram with Swift (iOS)

I'm trying to recreate something for a iOS app (Swift) which I already made in HTML5 (using map area-coords).
I want to show a human body diagram that interacts with user clicks/touches. The body exists of let's say 20 different parts, and the user can select one or more body-parts. For every body-part there is a selected-state image that should appear when a part is selected. Clicking on a selected part will deselect it. After selecting one or more parts, the user can continue and will get some information about these parts in a next viewcontroller. I am attaching a simplified image to explain my goal.
Can somebody explain what the best way is to achieve this goal? Is there a comparable technique that can be used in Swift to create such an interactive image?
Thanks!
There is no built-in mechanism for detecting taps in irregular shapes. The standard UIView tap detection uses frame rectangles. (Likewise with CALayers, as suggested by sketchyTech in his comment above.)
I would suggest drawing your body regions as bezier paths. You could create a custom subclass of UIView (BodyView) that would manage an array of BodyRegion objects each of which include a bezier path.
The UIBezierPath class includes a method containsPoint that lets you tell if a point is inside the path. Your BodyView could us that to decide which path object was tapped.
Bezier paths would handle both drawing your body regions and figuring out which part was tapped.
A very simple way to achieve this, would be to place invisible buttons over the areas, then hook every one up to an IBAction and put anything you want to happen inside.
#IBAction func shinTapped(sender: UIButton) {
tappedCounter = 0
if tappedCounter == 0 {
// set property to keep track
shinSelected = true
// TODO: set button image to selected state
// increment counter
tappedCounter++
}else{
// set property to keep track
shinSelected = false
// TODO: set button image to nil
// reset counter
tappedCounter = 0
}
This might get a little tricky to layout, so that every button sits in the right spot, but if you work with size classes it is totally doable.
I am sure there is a more elegant way to do it, but this is one way.
Fixed!
First I added sublayers to the UIImageView like this
var path = UIBezierPath()
path.moveToPoint(CGPointMake(20, 30))
path.addLineToPoint(CGPointMake(40, 30))
// add as many coordinates you need...
path.closePath()
var layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0.5).CGColor
layer.hidden = true
bodyImage.layer.addSublayer(layer)
Than I overrided the touchesbegan function in order to show and hide when the shapes are tapped.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first! as? UITouch {
// get tapped position
let position = touch.locationInView(self.bodyImage)
// loop all sublayers
for layer in self.bodyImage.layer.sublayers! as! [CAShapeLayer] {
// check if tapped position is inside shape-path
if CGPathContainsPoint(layer.path, nil, position, false) {
if (layer.hidden) {
layer.hidden = false
}
else {
layer.hidden = true
}
}
}
}
}

Swift - Jigsaw Snap to Place

So I am developing an Ipad app that allows the user to solve a jigsaw puzzle. I've worked out getting the panning motion for each piece, but getting them where I want to has not worked properly. I am trying to make a piece snap into it's final destination when it's within a small range, which is followed by a clicking sound.
Here is a bit of code for a single puzzle piece. When my new game button is pressed, an Image View gets set to the corresponding picture, and randomly placed on the canvas.
#IBAction func NewGameTapped(sender: UIButton){
let bounds = UIScreen.mainScreen().bounds
let height = bounds.size.height
let width = bounds.size.width
image1.image = UIImage(named:"puzzleImage1.png")
image1.center.x = CGFloat(100 + arc4random_uniform(UInt32(width)-300))
image1.center.y = CGFloat(100 + arc4random_uniform(UInt32(height)-300))
//Create Panning (Dragging) Gesture Recognizer for Image View 1
let panRecognizer1 = UIPanGestureRecognizer(target: self, action: "handlePanning1:")
// Add Panning (Dragging) Gesture Recognizer to Image View 1
image1.addGestureRecognizer(panRecognizer1)
}
This is where I am having some issues.
func handlePanning1(recognizer: UIPanGestureRecognizer) {
let center = dict1_image_coordinates["puzzleImage1"] as![Int]
let newTranslation: CGPoint = recognizer.translationInView(image1)
recognizer.view?.transform = CGAffineTransformMakeTranslation(lastTranslation1.x + newTranslation.x, lastTranslation1.y + newTranslation.y)
if recognizer.state == UIGestureRecognizerState.Ended {
lastTranslation1.x += newTranslation.x
lastTranslation1.y += newTranslation.y
}
checkPosition(image1, center: center)
}
func checkPosition(image: UIImageView, center: [Int]){
let distance: Double = sqrt(pow((Double(image.center.x) - Double(center[0])),2) + pow((Double(image.center.y) - Double(center[1])),2))
//if the distance is within range, set image to new location.
if distance <= 20{
image.center.x = CGFloat(center[0])
image.center.y = CGFloat(center[1])
AudioServicesPlaySystemSound(clickSoundID)
}
For whatever reason, the puzzle piece only wants to snap to it's spot when the piece begins the game within the acceptable snap distance. I have tried checking for the object position in various different parts of my program, but nothing has worked so far. Any help or other tips are greatly appreciated.
The issue is likely caused by this line
image1.addGestureRecognizer(panRecognizer1)
Usually people add gestureRecognizer on the parentView, or the rootView of the view controller instead of the image1 itself. The benefit is that the parentView never moves, where as the image1 is constantly being transformed, which may or may not affect the recognizer.translationInView(x) method return value.
do this instead:
self.view.addGestureRecognizer(panRecognizer1)
and change to this line in handlePanning1 function:
image1.transform = CGAffineTransformMakeTranslation(lastTranslation1.x + newTranslation.x, lastTranslation1.y + newTranslation.y)

Resources