moving imageView from current location to tap location at a constant speed - ios

I have a an imageView that I want to move prom current location to tapped location at a constant speed, regardless of distance.
tried the following which doesn't really work and I don't really know how to keep animation duration dynamic according to distance that imageView needs to traverse.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self.view)
print(position.x)
print(position.y)
CATransaction.begin()
CATransaction.setCompletionBlock { () -> Void in
self.imageView.layer.position = (self.imageView.layer.presentation()?.position)!
}
var animation = CABasicAnimation(keyPath: "position")
animation.duration = 2
var currentPosition : CGPoint = imageView.layer.presentation()!.position
animation.fromValue = NSValue(currentPosition)
animation.toValue = NSValue(CGPoint: position.x, (position.y))
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
imageView.layer.add(animation, forKey: "transform")
CATransaction.commit()
}
}

Try with this
calculating the distance
func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
let xDist = a.x - b.x
let yDist = a.y - b.y
return CGFloat(sqrt((xDist * xDist) + (yDist * yDist)))
}
and then using a arbitrary velocity, and calculating the time
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self.view)
print(position.x)
print(position.y)
let distance = Double(self.distance(self.imageView.center, position))
let velocity = 100.0 //pixels by seconds
let time = distance / velocity
UIView.animate(withDuration: time, animations: {
self.imageView.center = position
})
}
}

You can add UITapGestureRecognizer of imageView parent view and create tap gesture action
#IBAction func tapGesture(_ sender: UITapGestureRecognizer) {
print("tap working")
if sender.state == UIGestureRecognizerState.recognized
{
let location = sender.location(in: sender.view)
print(location)
UIView.animate(withDuration: 0.5, animations: {
self.imageView.center = location
})
}
}

Related

Touch delegates not called on view when z-index is set to non zero value

I am implementing the chat bubble feature. I want the chat bubble to be on top of all the view controller and shouldn't be affected by screen transition. So I decided to add it as subview to UIWindow so far it looks like
This works great, but if I present view controller instead of push then chat head goes behind the presented view controller. To avoid it I set ZIndex property of chat head layer
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let window = UIApplication.shared.keyWindow {
window.addSubview(chatHead)
chatHead.layer.zPosition = 1
}
}
But now though chat head appears on top of presented view controller, its touch delegates like touchesBegan, touchesMoved, touchesEnded nothing gets called when chat head is above presented ViewController
Adding code of chat head (lot of code might be unnecessary to this question, but just posting it so if someone would like to try it)
class ChatHeadView: UIView {
let headerWidth: CGFloat = 80.0
let headerHeight: CGFloat = 80.0
let sideInset: CGFloat = 10.0
let bottomInset: CGFloat = 10.0
var cancellationWindowShown = false
lazy var cancellationContainerRect: CGRect = {
var rect = self.cancelChatView.convert(self.cancelChatView.frame, to: nil)
rect.origin.x = rect.origin.x + 30
rect.size.height = 100
return rect
}()
lazy var cancellationThreshold: CGFloat = {
return self.keywindow.frame.maxY - (self.keywindow.frame.maxY / 3)
}()
lazy var keywindow: UIWindow = {
let window = UIApplication.shared.keyWindow ?? UIWindow()
return window
}()
lazy var cancelChatView: CancelChatView = {
if let cancelView = Bundle.main.loadNibNamed("CancelChatView", owner: nil, options: [:])?.first as? CancelChatView {
return cancelView
}
fatalError("Couldnt load")
}()
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
self.layer.masksToBounds = true
self.layer.cornerRadius = 40
self.layer.backgroundColor = UIColor.red.cgColor
self.cancelChatView.frame = CGRect(x: 0, y: self.keywindow.frame.maxY - 140, width: self.keywindow.frame.width, height: 140)
self.cancelChatView.layer.opacity = 0.0
self.keywindow.addSubview(self.cancelChatView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
debugPrint("Touch started")
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self.keywindow)
if location.y >= cancellationThreshold {
self.showCancellationWindow()
}
else {
removeCancellationView()
debugPrint("Removing Cancellation window")
}
self.center = location
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self.keywindow)
self.center = location
self.snap(to: location)
self.removeCancellationView()
}
}
private func snap(to point: CGPoint) {
var finalPoint:CGPoint = self.frame.origin
if self.cancellationContainerRect.intersects(self.convert(self.frame, to: nil)) {
self.removeFromSuperview()
}
else {
if finalPoint.x <= self.keywindow.center.x {
finalPoint.x = self.keywindow.frame.minX + sideInset
}
else if finalPoint.x > self.keywindow.center.x {
finalPoint.x = self.keywindow.frame.maxX - (sideInset + self.headerWidth)
}
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.6, options: .curveEaseInOut, animations: {
self.frame.origin = finalPoint
}, completion: nil)
}
}
private func showCancellationWindow() {
if cancellationWindowShown == false {
let animation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
animation.fromValue = 0.0
animation.toValue = 0.6
animation.duration = 0.1
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
self.cancelChatView.layer.add(animation, forKey: "reveal")
self.cancellationWindowShown = true
}
}
private func removeCancellationView() {
if cancellationWindowShown == true {
let animation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
animation.toValue = 0.0
animation.duration = 0.1
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
self.cancelChatView.layer.add(animation, forKey: "hide")
cancellationWindowShown = false
}
}
}
Issue Gif (touch not working):
As matt has asked for details of how I am presenting ViewController, adding screenshot of storyboard segue configuration

How to drag up UIContainer view in parent UIView within certain limits?

How would you go about dragging up a tab at the bottom of the screen like in apple maps and be able to drag it above the parent UIView, but keep it constrained within bounds of the screen.
i got this code which I'm trying to modify for my use case but I'm struggling to set the right bounds so it cant move up past a certain point and move down past a certain point.
var originalX: CGFloat = 0.0
var originalY: CGFloat = 0.0
#objc func handlePanGesture(gesture: UIPanGestureRecognizer) {
guard let panView = gesture.view else {
return
}
self.view.bringSubviewToFront(panView)
var translatedPoint = gesture.translation(in: self.view)
if gesture.state == .began {
originalX = panView.center.x
originalY = panView.center.y
}
translatedPoint = CGPoint(x: originalX, y: originalY + translatedPoint.y)
print(originalY + translatedPoint.y)
print(view.frame.height)
if (originalY + translatedPoint.y) > view.frame.height {
UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseInOut], animations: {
gesture.view?.center = translatedPoint
} , completion: nil)
}
}
new code with offset
var originalX: CGFloat = 0.0
var originalY: CGFloat = 0.0
var currentPoint: CGPoint?
var position:CGPoint?
#objc func handlePanGesture(gesture: UIPanGestureRecognizer) {
guard let panView = gesture.view else {
return
}
self.view.bringSubviewToFront(panView)
var translatedPoint = gesture.translation(in: self.view)
if gesture.state == .began {
originalX = panView.center.x
originalY = panView.center.y
}
translatedPoint = CGPoint(x: originalX, y: originalY + translatedPoint.y)
//if offsetY >= 9 {
UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseInOut], animations: {
gesture.view?.center = translatedPoint
} , completion: nil)
//}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
position = touch.location(in: view)
//print("position\(position)")
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
currentPoint = touch.location(in: view)
//print("currentPoint\(currentPoint)")
let offsetY = currentPoint!.y - position!.y
print(offsetY)
}
}
out put when dragging container view
2.0
0.5
9.0
1.0
8.0
0.5
4.0
7.5
-0.5
-6.5
-1.0
-8.0
output for dragging on uiview
-11.5
-21.5
-36.0
-52.0
-74.5
-86.5
-98.0
-102.0
-106.0
-112.0
-116.5
-120.0
-122.5
-124.0
-124.0
-124.0
-124.0
-124.0
-123.5
-120.5
-116.5
-114.0
-109.0
-104.5
-97.0
-89.0
-80.0
-74.0
-66.0
-58.5
-54.5
-49.5
-47.0
-42.5
-39.5
-35.5
-31.0
-28.5
-24.0
-21.5
-19.5
-18.0
-16.0
-15.5
-15.0
You can try that, and record the TouchPoint at TouchBegin(beginDrag) and get currentPoint at TouchMove(dragging)——I don't remember the specific agency method. By calculating offsetX = currentpoint.x - touchpoint.x. Using gestures to calculate is too complicated。
offsetX = currentpoint.x - touchpoint.x;
offsetY = currentpoint.y - touchpoint.y;

make SKPhysicsBody unidirectional

I have a SKSpriteNode as a ball, it's been given all the SKPhysicsBody properties move around in all direction. What I want now is to make it unidirectional (only move in that direction it hasn't move to before and not go back in to a path it had move upon). Currently I have following thoughts on this the problem,
make a fieldBitMask, to the path that is iterated by it and repel
the ball to not go back
apply some kind of force/ impulses on the ball from touchesBegan/ touchesMoved method to keep it from going back
something that can be handled in update method
a lifesaver from stackflowoverflow, who is coding even on the weekend :)
Supporting Code snippets for better understanding,
//getting current touch position by using UIEvent methods
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let location = touch.location(in: self)
lastTouchPoint = location
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let location = touch.location(in: self)
lastTouchPoint = location
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
lastTouchPoint = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
lastTouchPoint = nil
}
//ball created
func createPlayer(){
player = SKSpriteNode(imageNamed: "player")
player.position = CGPoint(x: 220, y: 420)
player.zPosition = 1
//physics for ball
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 2)
player.physicsBody?.allowsRotation = false
player.physicsBody?.linearDamping = 0.5
player.physicsBody?.categoryBitMask = collisionTypes.player.rawValue
player.physicsBody?.contactTestBitMask = collisionTypes.finish.rawValue
player.physicsBody?.collisionBitMask = collisionTypes.wall.rawValue
addChild(player)
}
//unwarp the optional property, calclulate the postion between player touch and current ball position
override func update(_ currentTime: TimeInterval) {
guard isGameOver == false else { return }
if let lastTouchPosition = lastTouchPoint {
//this usually gives a large value (related to screen size of the device) so /100 to normalize it
let diff = CGPoint(x: lastTouchPosition.x - player.position.x, y: lastTouchPosition.y - player.position.y)
physicsWorld.gravity = CGVector(dx: diff.x/100, dy: diff.y/100)
}
}
Well it was a combination little hacks in touchesBegan/ touchesMoved and update func,
First you need to catch on which touch occurred, get it's name (in my
case I made nodes which had alpha of 0, but become visible upon
moving over them i.e alpha 1). In touchesBegan, touchesMoved as follow
guard let touch = touches.first else {return}
let location = touch.location(in: self)
lastTouchPoint = location
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name
{
if name == "vortex"
{
touching = false
self.view!.isUserInteractionEnabled = false
print("Touched on the interacted node")
}else{
self.view!.isUserInteractionEnabled = true
touching = true
}
}
}
Second use a BOOL touching to track user interactions, on the screen by using getting a tap recogniser setup, as follow
func setupTapDetection() {
let t = UITapGestureRecognizer(target: self, action: #selector(tapped(_:)))
view?.addGestureRecognizer(t)
}
#objc func tapped(_ tap: UITapGestureRecognizer) {
touching = true
}
Finally in update put checks as follow,
guard isGameOver == false else { return }
self.view!.isUserInteractionEnabled = true
if(touching ?? true){
if let lastTouchPosition = lastTouchPoint {
//this usually gives a large value (related to screen size of the device) so /100 to normalize it
let diff = CGPoint(x: lastTouchPosition.x - player.position.x, y: lastTouchPosition.y - player.position.y)
physicsWorld.gravity = CGVector(dx: diff.x/100, dy: diff.y/100)
}
}
}

Calculate rotation velocity and naturally rotate view iOS

I have a circle-shaped view and have it rotate via the following code overriding touchesBegan(touches:, withEvent:) and touchesMoved(touches:, withEvent:).
var deltaAngle = CGFloat(0)
var startTransform: CGAffineTransform?
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touchPoint = touches.first!.locationInView(view)
let dx = touchPoint.x - view.center.x
let dy = touchPoint.y - view.center.y
deltaAngle = atan2(dy, dx)
startTransform = arrowPickerView.transform
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
{
let touchPoint = touches.first!.locationInView(view)
let dx = touchPoint.x - view.center.x
let dy = touchPoint.y - view.center.y
let angle = atan2(dy, dx)
let angleDifference = deltaAngle - angle
let transform = CGAffineTransformRotate(startTransform!, -angleDifference)
arrowPickerView.transform = transform
}
I want to override touchesEnded(touches:, withEvent:) to calculate the velocity and have the view naturally rotate a little (similar to continuous scrolling). I currently save the original transform and calculate the delta angle. How can I implement this? Thanks in advance!
In my example below, the CGAffineTransformRotate depends on:
direction (if the user moves from left to right, up or down etc)
rotation (a factor dependent on the time between touchesBegan and touchesEnded)
PI
This creates an CGAffineTransformRotate and this rotates with duration 1 (s), to create a feel of kinetic scrolling the UIViewAnimationOptions.CurveEaseOut property is used
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touchPointEnd = touches.first!.locationInView(view)
self.dateTouchesEnded = NSDate()
let delay : NSTimeInterval = NSTimeInterval(0)
let timeDelta : Double = self.dateTouchesEnded!.timeIntervalSince1970 - self.dateTouchesStarted!.timeIntervalSince1970
let horizontalDistance : Double = Double(touchPointEnd.x - self.touchPointStart!.x)
let verticalDistance : Double = Double(touchPointEnd.y - self.touchPointStart!.y)
var direction : Double = 1
if (fabs(horizontalDistance) > fabs(verticalDistance)) {
if horizontalDistance > 0 {
direction = -1
}
} else {
if verticalDistance < 0 {
direction = -1
}
}
let rotation : Double = (0.1 / timeDelta < 0.99) ? 0.1 / timeDelta : 0.99
let duration : NSTimeInterval = NSTimeInterval(1)
UIView.animateWithDuration(duration, delay: delay, options: UIViewAnimationOptions.CurveEaseOut, animations: {
let transform = CGAffineTransformRotate(self.imageView.transform, CGFloat(direction) * CGFloat(rotation) * CGFloat(M_PI))
self.imageView.transform = transform
}, completion: nil)
}
I added the variables to keep track of the start and end time of touches and added the CGPoint location of the touchesBegan function
var dateTouchesStarted : NSDate?
var dateTouchesEnded : NSDate?
var touchPointStart : CGPoint?
In touchesBegan i added:
self.touchPointStart = touchPoint
To set the variable as explained above.

SpriteKit friction not seeming to working properly

On a project in Xcode 7 I have a few SKSpriteNodes that move back and forth on the screen and another one, called user, that is meant to jump from sprite to sprite, progressively up the screen. However, when user lands on one of the moving sprites the moving sprite just slides right out from under it and user falls back down. I thought that this meant that I needed to increase the friction property on the nodes so that user would "stick" to the nodes, but this just makes it bounce on the other nodes. My problem is that the nodes moving back and forth seem to "slippery," and user just doesn't stay on them.
Here's my code:
My class for user:
class UserNode: SKSpriteNode
{
class func newNode(position position: CGPoint) -> UserNode
{
let position = position
let sprite = UserNode(imageNamed: "userImage")
sprite.position = position
sprite.size = CGSize(width: sprite.size.width * 2, height: sprite.size.height * 2)
sprite.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "userImage"), size: sprite.size)
sprite.physicsBody?.affectedByGravity = true
sprite.physicsBody?.dynamic = true
sprite.physicsBody?.allowsRotation = false
sprite.physicsBody?.friction = 0.2
return sprite
}
}
and for moving user (the methods in my gamescene)
let scale: CGFloat = 2.0
let damping: CGFloat = 0.98
var point = CGPoint?()
func moveNodeToPoint(sprite: SKSpriteNode, point: CGPoint)
{
let dx = (point.x - sprite.position.x) * scale
let dy = (point.y - sprite.position.y) * scale
sprite.physicsBody?.velocity = CGVectorMake(dx, dy)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
{
/* Called when a touch begins */
for _: AnyObject in touches
{
if !welcomeNode.hidden
{
let fadeAway = SKAction.fadeOutWithDuration(0.3)
welcomeNode.runAction(fadeAway)
directionsNode.runAction(fadeAway)
touchStartNode.runAction(fadeAway)
welcomeNode.hidden = true
directionsNode.hidden = true
touchStartNode.hidden = true
}
//////////
point = CGPointMake(self.frame.midX, user.position.y + 300)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
{
point = nil
}
override func update(currentTime: CFTimeInterval)
{
/* Called before each frame is rendered */
if (point != nil)
{
moveNodeToPoint(user, point: point!)
}
else
{
let dx = user.physicsBody!.velocity.dx * damping
let dy = user.physicsBody!.velocity.dy * damping
user.physicsBody?.velocity = CGVectorMake(dx, dy)
}
}
and for moving the platforms:
let screenSize = UIScreen.mainScreen().bounds
let width = screenSize.size.width * 2
let firstAction = SKAction.moveBy(CGVector(dx: width, dy: 0), duration: 2)
let secondAction = SKAction.moveBy(CGVector(dx: -width, dy: 0), duration: 2)
let actions = [firstAction, secondAction]
let barAction = SKAction.sequence(actions)
let mainBarAction = SKAction.repeatActionForever(barAction)
platform.runAction(mainBarAction)

Resources