How do I limit the dragging area for image views - ios

My first post and I am currently making an app in Xcode 8.1 using Swift 3
I have 9 images that I have made draggable with touchesBegan and touchesMoved functions.
However they are able to be dragged ANYWHERE on the screen and this can cause them to cover up other images I have. I would like to limit their movement by setting a boundary for them so that even when the user tries to drag the images out of that boundary they wont be able to.
I have created this code in draggedimageview.swift this allows the Image views to be dragged.
I have been spending a long time trying to figure out how to do this and if anyone can help I would appreciate it.
Thanks...
import UIKit
class DraggedImageView: UIImageView {
var startLocation: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startLocation = touches.first?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let currentLocation = touches.first?.location(in: self)
let dx = currentLocation!.x - startLocation!.x
let dy = currentLocation!.y - startLocation!.y
self.center = CGPoint(x: self.center.x+dx, y: self.center.y+dy)
}
}

You can do this:
let cx = self.center.x+dx
if (cx > 100) {
cx = 100
}
self.center = CGPoint(x: cx, y: self.center.y+dy)
But alter the if based on what you are trying to do. This clamps it so that it cannot be moved to a position where center.x > 100

Try to define your "allowed area" in a rect, such as:
import UIKit
class DraggedImageView: UIImageView {
var startLocation: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startLocation = touches.first?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let currentLocation = touches.first?.location(in: self)
let dx = currentLocation!.x - startLocation!.x
let dy = currentLocation!.y - startLocation!.y
// This is the area in which the dragging is allowed
let coolArea = CGRect(x: 0, y: 0, width: 100, height: 100)
let newCenter = CGPoint(x: self.center.x+dx, y: self.center.y+dy)
// If the allowed area contains the new point, we can assign it
if coolArea.contains(newCenter) {
self.center = newCenter
}
// else {
// print("Out of boundaries!")
// }
self.center = CGPoint(x: self.center.x+dx, y: self.center.y+dy)
}
}
You may want to change the code if you want something different to happen when user is dragging out of the bounds.

Taking the "allowed area" from the view that contains the UIImageView
import UIKit
class DraggedImageView: UIImageView {
var startLocation: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startLocation = touches.first?.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let currentLocation = touches.first?.location(in: self)
let dx = currentLocation!.x - startLocation!.x
let dy = currentLocation!.y - startLocation!.y
let coolArea = (self.superview?.bounds)!
let newCenter = CGPoint(x: self.center.x+dx, y: self.center.y+dy)
// If the allowed area contains the new point, we can assign it
if coolArea.contains(newCenter) {
self.center = newCenter
print("touchesMoved")
}
else {
print("Out of boundaries!")
}
}
}

Related

How can I select a specific circle?

I have created Circle using "draw" and can place it anywhere on view. but when I want to move a specific circle by clicking it. it selects "LAST" created circle.
how can I select a specific circle? please guide me for the same.
if anything else you need let me know.
**CircleView**
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
}
required init(coder aDecoder : NSCoder) {
fatalError("init(coder : ) has not been implemented")
}
override func draw(_ rect: CGRect) {
if let context = UIGraphicsGetCurrentContext(){
context.setLineWidth(2)
UIColor.yellow.set()
let circleCenter = CGPoint(x: frame.size.width / 2, y: frame.self.height / 2)
let circleRadius = (frame.size.width - 10) / 2
context.addArc(center: circleCenter, radius: circleRadius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
context.strokePath()
}
}
}
**ViewController**
import UIKit
class ViewController: UIViewController {
var circleView = CircleView(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
let circleWidth = CGFloat(100)
var lastCircleCenter = CGPoint()
var currentCenter = CGPoint()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.lightGray
circleView.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
lastCircleCenter = touch.location(in: view)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let circleCenter = touch.location(in: view)
if circleCenter == lastCircleCenter{
let circleHeight = circleWidth
circleView = CircleView(frame: CGRect(x: circleCenter.x - circleWidth / 2, y: circleCenter.y - circleWidth / 2, width: 100, height: 100))
view.addSubview(circleView)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let location = touch.location(in: view)
circleView.center = location
}
}
how can I select a specific circle? please guide me for the same.
if anything else you need let me know.
how can I select a specific circle? please guide me for the same.
if anything else you need let me know.
If you are creating multiple circles and adding them to your view, I would suggest to keep track of the created circles in a collection. That way on each touch you can check if the coordinate matches any of the created circles. Based on that you can determine if to create a new circle or to move an existing one.
Example:
class ViewController: UIViewController {
var circleViews: [CircleView] = []
let circleWidth = CGFloat(100)
var draggedCircle: CircleView?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Do nothing if a circle is being dragged
// or if we do not have a coordinate
guard draggedCircle == nil, let point = touches.first?.location(in: view) else {
return
}
// Do not create new circle if touch is in an existing circle
// Keep the reference of the (potentially) dragged circle
if let draggedCircle = circleViews.filter({ UIBezierPath(ovalIn: $0.frame).contains(point) }).first {
self.draggedCircle = draggedCircle
return
}
// Create new circle and store in an array
let offset = circleWidth / 2
let rect = CGRect(x: point.x - offset, y: point.y - offset, width: circleWidth, height: circleWidth)
let circleView = CircleView(frame: rect)
circleViews.append(circleView)
view.addSubview(circleView)
// The newly created view can be immediately dragged
draggedCircle = circleView
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// If touches end then a circle is never dragged
draggedCircle = nil
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let draggedCircle = draggedCircle, let point = touches.first?.location(in: view) else {
return
}
draggedCircle.center = point
}
}

Stretch SKSpriteNode between two points/touches

I have an SKSpriteNode in which I've set the centerRect property so that the node can be stretched to appear like a styled line. My intention is for the user to touch the screen, and draw/drag a straight line with the node. The line would pivot around an anchor point to remain straight.
In touchesBegan:, the node is added:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
if let _ = fgNode.childNode(withName: "laser") {
print("already there")
} else {
laser.centerRect = CGRect(x: 0.42857143, y: 0.57142857, width: 0.14285714, height: 0.14285714)
laser.anchorPoint = CGPoint(x: 0, y: 0.5)
laser.position = positionInScene
fgNode.addChild(laser)
}
}
And adjusted in touchesMoved::
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
stretchLaserTo(positionInScene)
}
The node is stretched and rotated with two functions:
func stretchLaserTo(_ point: CGPoint) {
let offset = point - laser.anchorPoint
let length = offset.length()
let direction = offset / CGFloat(length)
laser.xScale = length
rotate(sprite: laser, direction: direction)
}
func rotate(sprite: SKSpriteNode, direction: CGPoint) {
sprite.zRotation = atan2(direction.y, direction.x)
}
I think I'm somewhat on the right track. The line rotates with my touch and expands, however, it's extremely sensitive and doesn't stay with my touch. Maybe I'm going about it wrong. Is there a standard technique for doing something like this?
An example of this working can be seen here: https://imgur.com/A83L45i
I suggest you set anchor point of the sprite to (0, 0), set the sprite's scale to the distance between the sprite's position and the current touch location, and then rotate the sprite.
First, create a sprite and set its anchor point.
let laser = SKSpriteNode(color: .white, size: CGSize(width: 1, height: 1))
override func didMove(to view: SKView) {
laser.anchorPoint = CGPoint(x: 0, y: 0)
addChild(laser)
}
In touchesBegan, set the position of the sprite to the location of the touch. In this case, it's also the start of the line.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
laser.position = positionInScene
laser.setScale(1)
}
Update the sprite so that it forms a line that starts at the position of the sprite and ends at the current touch location.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let positionInScene = touch.location(in: self)
stretchLaserTo(positionInScene)
}
Stretch the sprite by setting its xScale to the distance from the start of the line to the location of the current touch and then rotate the sprite.
func stretchLaserTo(_ point: CGPoint) {
let dx = point.x - laser.position.x
let dy = point.y - laser.position.y
let length = sqrt(dx*dx + dy*dy)
let angle = atan2(dy, dx)
laser.xScale = length
laser.zRotation = angle
}

SpriteKit interplay between line and node: line should give a direction and speed to the node

I would like to have the following interplay between line and ball in my game: a line gives direction and speed to the ball. The longer the line, the faster the ball.
What I have now: a ball is following the line and stops at the end of it. But it shouldn't stop here. Of course, I understand that the ball is only following the path I made. But how could I change it?
Here is my code:
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
// Basic for dynamic sizes step01
var width = CGFloat()
var height = CGFloat()
var ball:SKSpriteNode!
var line:SKShapeNode!
var startPoint: CGPoint!
var location = CGPoint()
override func didMove(to view: SKView) {
self.backgroundColor = .purple
//declare dynamic size of the screen
width = self.frame.size.width
height = self.frame.size.height
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
self.physicsWorld.gravity = CGVector.zero
createBall()
}
func createBall(){
ball = SKSpriteNode(imageNamed: "yellowBtn")
ball.position = CGPoint(x:0, y: -(height/2.5))
ball.size = CGSize(width: width/6, height: width/6)
self.addChild(ball)
}
func drawLine(endPoint:CGPoint){
if(line != nil ){ line.removeFromParent() }
startPoint = ball.position
let path = CGMutablePath()
path.move(to: startPoint)
path.addLine(to: endPoint)
line = SKShapeNode()
line.path = path
line.lineWidth = 5
line.strokeColor = UIColor.white
self.addChild(line)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(line != nil ){ line.removeFromParent()
for touch in (touches ) {
let location = touch.location(in: self)
drawLine(endPoint: location)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
let location = touch.location(in: self)
drawLine(endPoint: location)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
location = touch.location(in: self)
drawLine(endPoint: location)
}
let moveAction = SKAction.move(to: location, duration: 10)
ball.run(moveAction)
}
override func update(_ currentTime: TimeInterval) {
}
}
I have found at least a part of answer to my question: I need to calculate CGVector manually (lastPoint - firstPoint) and then apply impulse to the ball:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
location = touch.location(in: self)
drawLine(endPoint: location)
}
let dx = location.x - startPoint.x
let dy = location.y - startPoint.y
let movement = CGVector(dx: dx, dy: dy)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 50)
ball.physicsBody?.applyImpulse(movement)
}
Now I should find the second part: how to set the speed of the ball accordingly to the length of the line.

Joystick, clamping the control stick inside the joystick base

I have a circular joystick i created in SpriteKit, how do i clamp the smaller control stick inside of the larger joystick base and prevent it from going outside the joystick base programmatically.
The code setting up my joystick inside of my didMove(toView:) method.
var DPad = SKSpriteNode(imageNamed: "dpad")
DPad.size = CGSize(width: 150, height: 150)
DPad.position = CGPoint(x: -270, y: -100)
var thumbNode = SKSpriteNode(imageNamed: "joystick")
thumbNode.size = CGSize(width: 50, height: 50)
thumbNode.position = CGPoint(x: DPad.position.x, y: DPad.position.y)
touchesBegan methods
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if thumbNode.contains(location) && isTracking == false {
isTracking = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if isTracking == true {
thumbNode.run(SKAction.move(to: location, duration: 0.01))
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
thumbNode.run(SKAction.move(to: DPad.position, duration: 0.01))
if thumbNode.position.x > DPad.size.width || thumbNode.position.y > DPad.size.height {
thumbNode.position = DPad.position
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if thumbNode.physicsBody?.velocity.dx > Float(0) || thumbNode.physicsBody?.velocity.dx < Float(0) {
print("greater")
}
}
For this, you can use SKConstraint.distance(SKRange(upperLimit: radius), to: baseNode)
To add it, just do joystickNode.constraints = [SKConstraint.distance(SKRange(upperLimit: radius), to: baseNode)]

Moving a Sprite along a UIBezierPath

I have been having some trouble moving a Sprite along a UIBezierPath. I am getting this path from my view controller and it is passing along the correct path. Any ideas on why this isn't animating? Eventually I want to be able to move multiple sprites along different bezier paths.
func play(){
let img = SKSpriteNode(imageNamed:"ball.png")
img.position = CGPoint(x: self.frame.size.width/4, y: self.frame.size.height/4)
self.addChild(img)
new_path = viewController.path //UIBezierPath is being returned
let followPath = SKAction.follow(new_path.cgPath, asOffset: true, orientToPath: false, duration: 5.0)
img.run(followPath)
}
I am trying to draw a bezier path here(Kind of like a whiteboard app)in a custom view and then access the bezier path when I want to animate it. The path seems to be passed through correctly.
private var path: UIBezierPath!
let newpath = UIBezierPath()
func initBezierPath() {
path = UIBezierPath()
path.lineCapStyle = CGLineCap.round
path.lineJoinStyle = CGLineJoin.round
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: AnyObject? = touches.first
lastPoint = touch!.location(in: self)
pointCounter = 0
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: AnyObject? = touches.first
let newPoint = touch!.location(in: self)
path.move(to: lastPoint)
path.addLine(to: newPoint)
lastPoint = newPoint
pointCounter += 1
if pointCounter == pointLimit {
pointCounter = 0
renderToImage()
setNeedsDisplay()
newpath.append(path)
path.removeAllPoints()
}
else {
setNeedsDisplay()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
pointCounter = 0
renderToImage()
setNeedsDisplay()
newpath.append(path)
path.removeAllPoints()
}

Resources