How to define a zone in picture in Swift 3 - ios

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
}
}

Related

Why SpriteKit's 'addChild' method result in chaos

I'm in a trouble of SpriteKit's 'addChild' method.
I tried to add a simple button to my scene , and the button is formed of one background image and one label.So I wrote code like this:
override func didMove(to view: SKView) {
let buttonBg = SKSpriteNode(imageNamed: "mainButton_green")
let buttonLabel = SKLabelNode(text: "Label")
self.addChild(buttonBg)
self.addChild(buttonLabel)
}
Then I ran the program on simulator and I found that result is strange.Sometimes it appeared right,like this:
correct appearence
But sometimes, 'buttonLabel' will be behind of 'buttonBg', just like this:
wrong appearence
Why?
ps. I had print self.children , and the result was '[buttonBg,buttonLabel]' in each situation , my skview.ignoresSiblingOrder was always false.
A couple of things you can do
let buttonBg = SKSpriteNode(imageNamed: "mainButton_green")
buttonBg.zPosition = 1
let buttonLabel = SKLabelNode(text: "Label")
buttonLabel.zPosition = 2
which manually sets the z layering of your objects
I also think that if you are creating a button object you should add the label as a child of the background
let buttonBg = SKSpriteNode(imageNamed: "mainButton_green")
buttonBg.zPosition = 1
let buttonLabel = SKLabelNode(text: "Label")
buttonLabel.zPosition = 1
self.addChild(buttonBg)
buttonBg.addChild(buttonLabel)
I always add a zPosition even if its the only child.
Your issue is not a real problem: when you create your elements, following your case they'll be rendered in the main thread following some rules like:
statements order
zPosition property
alpha property
etc..
So, something like a litlle label could be drawed before your button background causing this annoying aspect.
A good thing to do when you want to create a button should be:
let buttonBg = SKSpriteNode(imageNamed: "mainButton_green")
self.addChild(buttonBg)
let buttonLabel = SKLabelNode(text: "Label")
buttonBg.addChild(buttonLabel)
In other words, put your label as a child of your background, this should will guarantee to you a correct zPosition levels (without change zPosition properties), a correct label position (by default CGPoint.zero) always if you haven't changed (leave untouched) the anchorPoint of your elements.

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.

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)

Prevent dragged object from leaving view its assigned to

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
}
}

Resources