Button stops moving when creating new button who moves - ios

I'm creating a game where a button who is being created moves from one side of the screen to the other when I click a button called start. The problem is that when I click start before the button who was moving reaches its end point, it stops instead of continuing (and the another created button start moving like expected).
Should I create a new CADisplayLink every time I click the start button? If so, how would I do that? Here's the code:
var button1 = UIButton()
var displayLink: CADisplayLink?
var startTime: CFAbsoluteTime?
let duration = 2.0
var leadingConstraint: NSLayoutConstraint!
var topConstraint: NSLayoutConstraint!
var l1 = false
#IBAction func start(sender: UIButton) {
n1()
}
func n1() {
l1 = false
startTime = CFAbsoluteTimeGetCurrent()
displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
}
func handleDisplayLink(displayLink: CADisplayLink) {
if l1 == false { // so it doesn't randomize leading constraint twice
button1 = createButton()
let randomNumber = Int(arc4random_uniform(180) + 30)
let elapsed = CFAbsoluteTimeGetCurrent() - startTime!
var percentComplete = CGFloat(elapsed / duration)
if percentComplete >= 1.0 {
percentComplete = 1.0
// self.button1.removeFromSuperview()
displayLink.invalidate()
button1.hidden = true
}
leadingConstraint.constant = CGFloat(randomNumber)
topConstraint.constant = 390 - 350 * percentComplete
NSLayoutConstraint.activateConstraints([
leadingConstraint,
topConstraint,
button1.widthAnchor.constraintEqualToConstant(75),
button1.heightAnchor.constraintEqualToConstant(75)
])
l1 = true
}
else{
let elapsed = CFAbsoluteTimeGetCurrent() - startTime!
var percentComplete = CGFloat(elapsed / duration)
if percentComplete >= 1.0 {
percentComplete = 1.0
displayLink.invalidate()
button1.hidden = true
}
topConstraint.constant = 390 - 350 * percentComplete
NSLayoutConstraint.activateConstraints([
leadingConstraint,
topConstraint,
button1.widthAnchor.constraintEqualToConstant(75),
button1.heightAnchor.constraintEqualToConstant(75)
])
}
}
func buttonPressed(sender: UIButton!) {
button1.hidden = true
displayLink?.invalidate()
}
func createButton() ->UIButton {
let button = UIButton()
button.setImage(UIImage(named: "BlueBall.png")!, forState: UIControlState.Normal)
button.addTarget(self, action: "buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
leadingConstraint = button.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 0)
topConstraint = button.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 0)
NSLayoutConstraint.activateConstraints([
leadingConstraint,
topConstraint,
button.widthAnchor.constraintEqualToConstant(75),
button.heightAnchor.constraintEqualToConstant(75)
])
return button
}
Please help. It would be really appreciated. Thanks in advance. Anton

Okay Anton O; as discussed I post an answer how to detect a touch on a moving UIView. This works for both, CAAnimation and UIView.animationWith..
First I created an extension of CGRect, just for convenience:
extension CGRect {
init(position: CGPoint, size: CGSize) {
self.origin = CGPoint(x: position.x - (size.width / 2.0), y: position.y - (size.height / 2.0))
self.size = size
}
}
Then I created two methods which create and move the view. You can adapt the code then to your needs. (I hold a global variable called nextView to keep reference to the view, can also be extended to an array of course)
Create View:
private func createView(index: Int) {
nextView?.removeFromSuperview()
nextView = UIView()
nextView?.bounds = CGRect(x: 0, y: 0, width: 60, height: 60)
nextView?.backgroundColor = UIColor.redColor()
nextView?.center = CGPoint(x: 30, y: CGRectGetMidY(self.view.bounds))
if let nextView = nextView {
view.addSubview(nextView)
}
}
Move View:
private func moveView() {
guard let nextView = nextView else {
return
}
UIView.animateWithDuration(5.0) { () -> Void in
nextView.center = CGPoint(x: CGRectGetMaxX(self.view.bounds) + 30, y: CGRectGetMidY(self.view.bounds))
}
}
Detect Touch:
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
if let touch = touches.first, nextView = nextView {
let touchRect = CGRect(position: touch.locationInView(self.view), size: CGSize(width: 20, height: 20))
guard let viewPosition = nextView.layer.presentationLayer()?.position else {
return
}
let viewRect = CGRect(position: viewPosition, size: nextView.bounds.size)
if CGRectIntersectsRect(touchRect, viewRect) {
print("👍 💯")
} else {
print("👎")
}
}
}
You can extend the methods for your needs and also add some "performance" enhancing checks (like if a view is visible and move on or return right there in the touchesEnded method, etc.)

Related

Swift: Longpress Button Animation

I have a Button that acts as an SOS Button. I would like to only accept longpresses on that button (something like two seconds long press) and animate the button while pressing to let the user know he has to longpress.
The Button is just a round Button:
let SOSButton = UIButton()
SOSButton.backgroundColor = Colors.errorRed
SOSButton.setImage(UIImage(systemName: "exclamationmark.triangle.fill"), for: .normal)
SOSButton.translatesAutoresizingMaskIntoConstraints = false
SOSButton.tintColor = Colors.justWhite
SOSButton.clipsToBounds = true
SOSButton.layer.cornerRadius = 25
SOSButton.addTarget(self, action: #selector(tappedSOSButton(sender:)), for: .touchUpInside)
SOSButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(SOSButton)
which looks something like this:
Now, when the button is getting long-pressed, I'd like to animate a stroke like a circular progress view. It would start from 0* and fill the whole circle to finally look like this:
I know it looks the same because the background is white, but there is a white stroke around it.
If the user lets go of the button before the circle fills up, it should animate back to zero in the same speed. If the user holds on long enough, only then should the action get executed.
How would I go about designing such a button? I have not found anything I can work off right now. I know I can animate stuff but animating while long-pressing seems like I'd need to implement something very custom.
Interested in hearing ideas.
You can create a custom class of UIView and add layer to it.
class CircularProgressBar: UIView {
private var circularPath: UIBezierPath = UIBezierPath()
var progressLayer: CAShapeLayer!
var progress: Double = 0 {
willSet(newValue) {
progressLayer?.strokeEnd = CGFloat(newValue)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func layoutSubviews() {
super.layoutSubviews()
removeAllSubviewAndSublayers()
setupCircle()
}
private func removeAllSubviewAndSublayers() {
layer.sublayers?.forEach { $0.removeFromSuperlayer() }
subviews.forEach { $0.removeFromSuperview() }
}
func setupCircle() {
let x = self.frame.width / 2
let y = self.frame.height / 2
let center = CGPoint(x: x, y: y)
circularPath = UIBezierPath(arcCenter: center, radius: x, startAngle: -0.5 * CGFloat.pi, endAngle: 1.5 * CGFloat.pi, clockwise: true)
progressLayer = CAShapeLayer()
progressLayer.path = circularPath.cgPath
progressLayer.strokeColor = UIColor.white.cgColor
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineWidth = x/10
progressLayer.lineCap = .round
progressLayer.strokeEnd = 0
layer.addSublayer(progressLayer)
}
func addStroke(duration: Double = 2.0) {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = duration
animation.beginTime = CACurrentMediaTime()
progressLayer.add(animation, forKey: "strokeEnd")
}
func removeStroke(duration: Double = 0.0) {
let revAnimation = CABasicAnimation(keyPath: "strokeEnd")
revAnimation.duration = duration
revAnimation.fromValue = progressLayer.presentation()?.strokeEnd
revAnimation.toValue = 0.0
progressLayer.removeAllAnimations()
progressLayer.add(revAnimation, forKey: "strokeEnd")
}
}
In UIViewController create a UIImageView and CircularProgressBar. Set isUserInteractionEnabled to true of imageView and add progressView to it.
In viewDidLayoutSubviews() method set the frame of progressView equal to bounds of imageView. You also need to set Timer to execute action. Here is the full code.
class ViewController: UIViewController {
let imageView = UIImageView(image: UIImage(named: "icon_sos")!)
let progressView = CircularProgressBar()
var startTime: Date?
var endTime: Date?
var longPress: UILongPressGestureRecognizer?
var timer: Timer?
let longPressDuration: Double = 2.0
override func viewDidLoad() {
super.viewDidLoad()
longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
longPress?.minimumPressDuration = 0.01
imageView.frame = CGRect(x: 100, y: 100, width: 30, height: 30)
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(longPress!)
imageView.addSubview(progressView)
self.view.addSubview(imageView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
progressView.frame = imageView.bounds
}
private func setupTimer() {
timer = Timer.scheduledTimer(timeInterval: self.longPressDuration, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: false)
}
#objc private func fireTimer() {
longPress?.isEnabled = false
longPress?.isEnabled = true
progressView.removeStroke()
if let timer = timer {
timer.invalidate()
self.timer = nil
}
// execute button action here
print("Do something")
}
#objc private func longPressAction(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
print("Long Press Began: ", Date())
startTime = Date()
self.progressView.addStroke(duration: self.longPressDuration)
setupTimer()
}
if sender.state == .changed {
print("Long Press Changed: ", Date())
}
if sender.state == .cancelled {
print("Long Press Cancelled: ", Date())
endTime = Date()
if let startTime = startTime, let endTime = endTime {
let interval = DateInterval(start: startTime, end: endTime)
progressView.removeStroke(duration: interval.duration.magnitude)
if interval.duration.magnitude < self.longPressDuration {
timer?.invalidate()
timer = nil
}
}
}
if sender.state == .ended {
print("Long Press Ended: ", Date())
endTime = Date()
if let startTime = startTime, let endTime = endTime {
let interval = DateInterval(start: startTime, end: endTime)
progressView.removeStroke(duration: interval.duration.magnitude)
if interval.duration.magnitude < self.longPressDuration {
timer?.invalidate()
timer = nil
}
}
}
}
}

How to attach multiple UIDynamicItems to each other

I am trying to implement circles attached to each other like in Apple's Music App via UIDynamicAnimator. I need to attach circles to each other and to view center. I was trying to implement this via UIAttachmentBehavior, but seems to it's not supporting multiple attachments. In result, circles overlaps on each other :)
let attachment = UIAttachmentBehavior(item: circle, attachedToAnchor: CGPoint(x: view.center.x, y: view.center.y))
attachment.length = 10
animator?.addBehavior(attachment)
let push = UIPushBehavior(items: [circle], mode: .continuous)
collision.addItem(circle)
animator?.addBehavior(push)
What I am doing wrong?
I don't think the apple music genre picker thing uses UIAttachmentBehavior which is closer to attaching two views with a pole or a rope. But, it seems like the problem you're experiencing might be that all of the views are added at the same location which has the effect of placing them on top of each other and with the collision behavior causes them to be essentially be stuck together. One thing to do is to turn on UIDynamicAnimator debugging by calling animator.setValue(true, forKey: "debugEnabled").
For recreating the above circle picker design, I would look into using UIFieldBehavior.springField().
For example:
class ViewController: UIViewController {
lazy var animator: UIDynamicAnimator = {
let animator = UIDynamicAnimator(referenceView: view)
return animator
}()
lazy var collision: UICollisionBehavior = {
let collision = UICollisionBehavior()
collision.collisionMode = .items
return collision
}()
lazy var behavior: UIDynamicItemBehavior = {
let behavior = UIDynamicItemBehavior()
behavior.allowsRotation = false
behavior.elasticity = 0.5
behavior.resistance = 5.0
behavior.density = 0.01
return behavior
}()
lazy var gravity: UIFieldBehavior = {
let gravity = UIFieldBehavior.springField()
gravity.strength = 0.008
return gravity
}()
lazy var panGesture: UIPanGestureRecognizer = {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.didPan(_:)))
return panGesture
}()
var snaps = [UISnapBehavior]()
var circles = [CircleView]()
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(panGesture)
animator.setValue(true, forKey: "debugEnabled")
addCircles()
addBehaviors()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gravity.position = view.center
snaps.forEach {
$0.snapPoint = view.center
}
}
func addCircles() {
(1...30).forEach { index in
let xIndex = index % 2
let yIndex: Int = index / 3
let circle = CircleView(frame: CGRect(origin: CGPoint(x: xIndex == 0 ? CGFloat.random(in: (-300.0 ... -100)) : CGFloat.random(in: (500 ... 800)), y: CGFloat(yIndex) * 200.0), size: CGSize(width: 100, height: 100)))
circle.backgroundColor = .red
circle.text = "\(index)"
circle.textAlignment = .center
view.addSubview(circle)
gravity.addItem(circle)
collision.addItem(circle)
behavior.addItem(circle)
circles.append(circle)
}
}
func addBehaviors() {
animator.addBehavior(collision)
animator.addBehavior(behavior)
animator.addBehavior(gravity)
}
#objc
private func didPan(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: sender.view)
switch sender.state {
case .began:
animator.removeAllBehaviors()
fallthrough
case .changed:
circles.forEach { $0.center = CGPoint(x: $0.center.x + translation.x, y: $0.center.y + translation.y)}
case .possible, .cancelled, .failed:
break
case .ended:
circles.forEach { $0.center = CGPoint(x: $0.center.x + translation.x, y: $0.center.y + translation.y)}
addBehaviors()
#unknown default:
break
}
sender.setTranslation(.zero, in: sender.view)
}
}
final class CircleView: UILabel {
override var collisionBoundsType: UIDynamicItemCollisionBoundsType {
return .ellipse
}
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = bounds.height * 0.5
layer.masksToBounds = true
}
}
For more information I would watch What's New in UIKit Dynamics and Visual Effects from WWDC 2015

IOS Step menu using swift

I would like to create the stepper menu in IOS using swift, But I'm facing some issues. Here are the issues.
1) Portrait and landscape stepper menu is not propper.
2) How to set default step position with the method below method, It's working when button clicked. But I want to set when menu loads the first time.
self.stepView.setSelectedPosition(index: 2)
3) If it reached the position last, I would like to change the color for complete path parentPathRect.
4) Progress animation CABasicAnimation is not like the progress bar, I want to show the animation.
5) It should not remove the selected position color when changing the orientation.
As per my organization rules should not use third-party frameworks.
Can anyone help me with the solution? Or is there any alternative solution for this?
Here is my code:
import UIKit
class ViewController: UIViewController, StepMenuDelegate {
#IBOutlet weak var stepView: StepView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.stepView.delegate = self;
self.stepView.titles = ["1", "2", "3"]
self.stepView.lineWidth = 8
self.stepView.offSet = 8
self.stepView.setSelectedPosition(index: 2)
}
func didSelectItem(atIndex index: NSInteger) {
print(index)
}
}
protocol StepMenuDelegate {
func didSelectItem(atIndex index: NSInteger)
}
class StepView: UIView {
var delegate : StepMenuDelegate!
var titles: [String] = [] {
didSet(values) {
setup()
setupItems()
}
}
var lineWidth: CGFloat = 8 {
didSet(values) {
setup()
}
}
var offSet: CGFloat = 8 {
didSet(values) {
self.itemOffset = offSet * 4
setup()
}
}
private var selectedIndex : NSInteger!
private var itemOffset : CGFloat = 8 {
didSet (value) {
setup()
setupItems()
}
}
private var path : UIBezierPath!
var selectedLayer : CAShapeLayer!
private var parentPathRect : CGRect!
override func awakeFromNib() {
super.awakeFromNib()
}
override func layoutSubviews() {
self.setup()
setupItems()
}
func setup() {
self.removeAllButtonsAndLayes()
let layer = CAShapeLayer()
self.parentPathRect = CGRect(origin: CGPoint(x: offSet, y: self.bounds.midY - (self.lineWidth/2) ), size: CGSize(width: self.bounds.width - (offSet * 2), height: lineWidth))
path = UIBezierPath(roundedRect: self.parentPathRect, cornerRadius: 2)
layer.path = path.cgPath
layer.fillColor = UIColor.orange.cgColor
layer.lineCap = .butt
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowOffset = CGSize(width: 1, height: 2)
layer.shadowOpacity = 0.1
layer.shadowRadius = 2
self.layer.addSublayer(layer)
}
func setupItems() {
removeAllButtonsAndLayes()
let itemRect = CGRect(x: self.itemOffset, y: 0, width: 34, height: 34)
let totalWidth = self.bounds.width
let itemWidth = totalWidth / CGFloat(self.titles.count);
for i in 0..<self.titles.count {
let button = UIButton()
var xPos: CGFloat = itemOffset
self.addSubview(button)
xPos += (CGFloat(i) * itemWidth);
xPos += itemOffset/3
button.translatesAutoresizingMaskIntoConstraints = false
button.leftAnchor.constraint(equalTo: self.leftAnchor, constant: xPos).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0).isActive = true
button.heightAnchor.constraint(equalToConstant: itemRect.height).isActive = true
button.widthAnchor.constraint(equalToConstant: itemRect.width).isActive = true
button.backgroundColor = UIColor.red
button.layer.zPosition = 1
button.layer.cornerRadius = itemRect.height/2
let name : String = self.titles[i]
button.tag = i
button.setTitle(name, for: .normal)
button.addTarget(self, action: #selector(selectedItemEvent(sender:)), for: .touchUpInside)
if self.selectedIndex != nil {
if button.tag == self.selectedIndex {
selectedItemEvent(sender: button)
}
}
}
}
#objc func selectedItemEvent(sender:UIButton) {
if self.selectedLayer != nil {
selectedLayer.removeFromSuperlayer()
}
self.delegate.didSelectItem(atIndex: sender.tag)
let fromRect = self.parentPathRect.origin
self.selectedLayer = CAShapeLayer()
let rect = CGRect(origin: fromRect, size: CGSize(width:sender.frame.origin.x - 4, height: 8))
let path = UIBezierPath(roundedRect: rect, cornerRadius: 4)
self.selectedLayer.path = path.cgPath
self.selectedLayer.lineCap = .round
self.selectedLayer.fillColor = UIColor.orange.cgColor
let animation = CABasicAnimation(keyPath: "fillColor")
animation.toValue = UIColor.blue.cgColor
animation.duration = 0.2
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
self.selectedLayer.add(animation, forKey: "fillColor")
self.layer.addSublayer(self.selectedLayer)
}
func removeAllButtonsAndLayes() {
for button in self.subviews {
if button is UIButton {
button.removeFromSuperview()
}
}
}
func setSelectedPosition(index:NSInteger) {
self.selectedIndex = index
}
}
Here I found One way to achieve the solution.!!
https://gist.github.com/DamodarGit/7f0f484708f60c996772ae28e5e1c615
Welcome to suggestions or code changes.!!

How to create swipe to start button with moving arrows

I want to create the exactly the same swipe button like this https://github.com/shadowfaxtech/proSwipeButton .
I was wondering how to change the arrow of the button on user touches
I was doing this for getting swipe action.
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
rightSwipe.direction = .right
view.addGestureRecognizer(rightSwipe)
but the thing is how to add arrows to button which change there position on user touches.
Here is the code I have written for swiping over the button. You assign image to the image view.
func createSwipeButton() {
let button = UIButton.init(type: .custom)
button.backgroundColor = UIColor.brown
button.setTitle("PLACE ORDER", for: .normal)
button.frame = CGRect.init(x: 10, y: 200, width: self.view.frame.size.width-20, height: 100)
button.addTarget(self, action: #selector(swiped(_:event:)), for: .touchDragInside)
button.addTarget(self, action: #selector(swipeEnded(_:event:)), for: .touchUpInside)
self.view.addSubview(button)
let swipableView = UIImageView.init()
swipableView.frame = CGRect.init(x: 0, y: 0, width: 20, height: button.frame.size.height)
swipableView.tag = 20
swipableView.backgroundColor = UIColor.white
button.addSubview(swipableView)
}
#objc func swiped(_ sender : UIButton, event: UIEvent) {
let swipableView = sender.viewWithTag(20)!
let centerPosition = location(event: event, subView: swipableView, superView: sender,isSwiping: true)
UIView.animate(withDuration: 0.2) {
swipableView.center = centerPosition
}
}
#objc func swipeEnded(_ sender : UIButton, event: UIEvent) {
let swipableView = sender.viewWithTag(20)!
let centerPosition = location(event: event, subView: swipableView, superView: sender, isSwiping: false)
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 5, options: .curveEaseInOut, animations: {
swipableView.center = centerPosition
}) { _ in}
}
func location(event: UIEvent, subView: UIView, superView: UIButton, isSwiping: Bool) -> CGPoint {
if let touch = event.touches(for: superView)?.first{
let previousLocation = touch.previousLocation(in: superView)
let location = touch.location(in: superView)
let delta_x = location.x - previousLocation.x;
print(subView.center.x + delta_x)
var centerPosition = CGPoint.init(x: subView.center.x + delta_x, y: subView.center.y)
let minX = subView.frame.size.width/2
let maxX = superView.frame.size.width - subView.frame.size.width/2
centerPosition.x = centerPosition.x < minX ? minX : centerPosition.x
centerPosition.x = centerPosition.x > maxX ? maxX : centerPosition.x
if !isSwiping{
let normalPosition = superView.frame.size.width * 0.5
centerPosition.x = centerPosition.x > normalPosition ? maxX : minX
centerPosition.x = centerPosition.x <= normalPosition ? minX : centerPosition.x
}
return centerPosition
}
return CGPoint.zero
}
Complete project is on github: https://github.com/IamSaurav/SwipeButton
Mmm what about something like this?
You can add an UIImage in the storyboard in the swipeImage var.
The best effect is done if the image has the same color of the text.
import UIKit
#IBDesignable
class UISwipeableLabel: UILabel {
#IBInspectable var swipeImage: UIImage? {
didSet {
configureSwipeImage()
}
}
private var swipeImageView: UIImageView?
private var rightSwipe: UIPanGestureRecognizer?
private var shouldActivateButton = true
override func awakeFromNib() {
super.awakeFromNib()
configureSwipeImage()
clipsToBounds = true
}
}
private extension UISwipeableLabel {
#objc func handleSwipes(_ sender:UIPanGestureRecognizer) {
if let centerX = swipeImageView?.center.x {
let translation = sender.translation(in: self)
let percent = centerX/frame.width
if sender.state == .changed {
if centerX < frame.width - frame.height/2 {
swipeImageView?.center.x = centerX + translation.x
sender.setTranslation(CGPoint.zero, in: swipeImageView)
} else {
swipeImageView?.center.x = frame.width - frame.height/2
if shouldActivateButton {
activateButton()
}
}
}
if sender.state == .ended || sender.state == .cancelled || sender.state == .failed {
if shouldActivateButton {
UIView.animate(withDuration: 0.25 * TimeInterval(percent)) {
self.swipeImageView?.center.x = self.frame.height/2
}
}
}
}
}
func configureSwipeImage() {
if swipeImageView != nil {
swipeImageView?.removeFromSuperview()
}
swipeImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.height, height: frame.height))
if let swipeImageView = swipeImageView {
swipeImageView.image = swipeImage
swipeImageView.isUserInteractionEnabled = true
swipeImageView.alpha = 0.5
addSubview(swipeImageView)
rightSwipe = UIPanGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
if let rightSwipe = rightSwipe {
swipeImageView.addGestureRecognizer(rightSwipe)
}
}
}
func activateButton() {
print("*** DO YOUR STUFF HERE ***")
}
}
You start with a UILabel and if you want, change it to use autolayout.

How can I set UIGestureRecognizer's Speed as the velocity for my Physicsbody in SpriteKit?

I calculated the speed of UIGestureRecognizer and I want to use it as velocity of Physicsbody in SpriteKit.
How can I do that since Speed is calculated in touches began and touches ended method, while my Physicsbody is wrapped in swipeUp & swipeDown etc functions, which does not have access to the Speed variable?
Here is my code:
class GameScene: SKScene, SKPhysicsContactDelegate, SKViewDelegate, UIGestureRecognizerDelegate, UIViewControllerTransitioningDelegate {
var Kite = SKSpriteNode(imageNamed: "ASC_8025_large.jpg")
#objc let minusButton = SKSpriteNode(imageNamed: "minus.jpg")
#objc let plusButton = SKSpriteNode(imageNamed: "plus.png")
//override init(Kite: SKSpriteNode) {
//For long Long press gesture to work
let longPressGestureRecPlus = UILongPressGestureRecognizer()
let longPressGestureRecMinus = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(press:)))
//First we declare all of our Gestures...
//swipes
let swipeRightRec = UISwipeGestureRecognizer()
let swipeLeftRec = UISwipeGestureRecognizer()
let swipeUpRec = UISwipeGestureRecognizer()
let swipeDownRec = UISwipeGestureRecognizer()
//rotate
let rotateRec = UIRotationGestureRecognizer()
//taps
let tapRec = UITapGestureRecognizer()
let tapRec2 = UITapGestureRecognizer()
override func didMove(to view: SKView) {
// Get label node from scene and store it for use later
Kite.size = CGSize(width: 40.0, height: 40.0)
Kite.position = CGPoint(x: frame.midX, y: frame.midY)
plusButton.size = CGSize(width: 30.0, height: 30.0)
plusButton.position = CGPoint(x: frame.midX, y: frame.minY + 20.0)
plusButton.name = "plusButton"
//plusButton.isUserInteractionEnabled = true
addChild(Kite)
addChild(plusButton)
swipeRightRec.addTarget(self, action: #selector(GameScene.swipedRight) )
swipeRightRec.direction = .right
self.view!.addGestureRecognizer(swipeRightRec)
swipeLeftRec.addTarget(self, action: #selector(GameScene.swipedLeft) )
swipeLeftRec.direction = .left
self.view!.addGestureRecognizer(swipeLeftRec)
swipeUpRec.addTarget(self, action: #selector(GameScene.swipedUp) )
swipeUpRec.direction = .up
self.view!.addGestureRecognizer(swipeUpRec)
swipeDownRec.addTarget(self, action: #selector(GameScene.swipedDown) )
swipeDownRec.direction = .down
self.view!.addGestureRecognizer(swipeDownRec)
//notice the function this calls has (_:) after it because we are passing in info about the gesture itself (the sender)
rotateRec.addTarget(self, action: #selector (GameScene.rotatedView (_:) ))
self.view!.addGestureRecognizer(rotateRec)
// again notice (_:), we'll need this to find out where the tap occurred.
tapRec.addTarget(self, action:#selector(GameScene.tappedView(_:) ))
tapRec.numberOfTouchesRequired = 1
tapRec.numberOfTapsRequired = 1
self.view!.addGestureRecognizer(tapRec)
tapRec2.addTarget(self, action:#selector(GameScene.tappedView2(_:) ))
tapRec2.numberOfTouchesRequired = 1
tapRec2.numberOfTapsRequired = 2 //2 taps instead of 1 this time
self.view!.addGestureRecognizer(tapRec2)
longPressGestureRecPlus.addTarget(self, action: #selector(longPressed(press:)))
longPressGestureRecPlus.minimumPressDuration = 2.0
self.view?.addGestureRecognizer(longPressGestureRecPlus)
}
//the functions that get called when swiping...
#objc func swipedRight() {
print("Right")
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Tilts the Kite towards Right
let tiltRight = SKAction.rotate(toAngle: -1.00, duration: 0.1)
Kite.run(tiltRight)
}
#objc func swipedLeft() {
Kite.physicsBody?.velocity = CGVector(dx: -60, dy: 60)
//Tilts the Kite towards Right
let tiltLeft = SKAction.rotate(toAngle: 1.00, duration: 0.1)
Kite.run(tiltLeft)
print("Left")
}
#objc func swipedUp() {
Kite.physicsBody?.velocity = CGVector(dx: 60, dy: 60)
//Straightens the Kite
let straightens = SKAction.rotate(toAngle: 0.00, duration: 0.1)
Kite.run(straightens)
print("Up")
}
#objc func swipedDown() {
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: -60)
print("Down")
}
// what gets called when there's a single tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Single tap")
print(point)
}
// what gets called when there's a double tap...
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func tappedView2(_ sender:UITapGestureRecognizer) {
let point:CGPoint = sender.location(in: self.view)
print("Double tap")
print(point)
}
//what gets called when there's a rotation gesture
//notice the sender is a parameter. This is why we added (_:) that part to the selector earlier
#objc func rotatedView(_ sender:UIRotationGestureRecognizer) {
if (sender.state == .began) {
print("rotation began")
}
if (sender.state == .changed) {
print("rotation changed")
//you could easily make any sprite's rotation equal this amount like so...
//thePlayer.zRotation = -sender.rotation
//convert rotation to degrees...
let rotateAmount = Measurement(value: Double(sender.rotation), unit: UnitAngle.radians).converted(to: .degrees).value
print("\(rotateAmount) degreess" )
}
if (sender.state == .ended) {
print("rotation ended")
}
}
func removeAllGestures(){
//if you need to remove all gesture recognizers with Swift you can do this....
for gesture in (self.view?.gestureRecognizers)! {
self.view?.removeGestureRecognizer(gesture)
}
//this is good to do before a SKScene transitions to another SKScene.
}
func removeAGesture()
{
//To remove a single gesture you can use...
self.view?.removeGestureRecognizer(swipeUpRec)
}
//Fix the gesture recognizing the plusButton
#objc func longPressed(press: UILongPressGestureRecognizer) {
if press.state == .began {
isUserInteractionEnabled = true
let positionInScene = press.location(in: self.view)
let touchedNode = self.atPoint(positionInScene)
plusButton.name = "plusButton"
Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 60.0*3)
print("Pressed on the screen")
if let name = touchedNode.name {
if name == "plusButton" {
print("LONG TAPPED")
}
}
}
}
var start: CGPoint?
var startTime: TimeInterval?
var taps = 0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Began")
Kite.physicsBody = SKPhysicsBody(rectangleOf: Kite.size)
Kite.physicsBody?.affectedByGravity = false
//Kite.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
Kite.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 5))
plusButton.name = "plusButton"
plusButton.isUserInteractionEnabled = false
//We figure how to interact with BUTTON in Spritekit
let touch = touches.first
let positionInScene = touch!.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name {
if name == "plusButton" {
taps += 1
Kite.size = CGSize(width: 200, height: 200)
print("tapped")
print("Taps", taps)
}
}
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
start = location
startTime = touch.timestamp
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Ended")
//Calculating Speed of the Gestures
for touch in touches {
let location:CGPoint = touch.location(in: self.view!)
var dx:CGFloat = location.x - start!.x;
var dy:CGFloat = location.y - start!.y;
var magnitude:CGFloat = sqrt(dx*dx+dy*dy)
//Calculate Time
var dt:CGFloat = CGFloat(touch.timestamp - startTime!)
//Speed = Distance / Time
var speed:CGFloat = magnitude / dt
var speedX:CGFloat = dx/dt
var speedY:CGFloat = dy/dt
print("SpeedY", speedX)
print("SpeedY", speedY)
}
}
so calculating the speed from a vector like dx and dy is
let speed = sqrtf(Float(pow(dx!, 2) + pow(dy!, 2)))
So if you want apply the speed as velocity to an object you need at least one of the directions.
let newDx = 0 // means in no x direction
// next you can change the formula to your new dy to match the given speed
let newDy = sqrtf(pow(speed, 2) - Float(pow(newDx, 2)))
yourNode.physicsBody?.velocity = CGVector(dx: newDx, dy: CGFloat(newDy))

Resources