How to disable standard UIButton touch event - ios

In my application I added a UIButton, but instead of having the standard touch event of the UIButton getting darker then becoming the standard color when touch events have ended, I want to add an animation that when you click on the UIButton the button gets smaller, then when touch events have ended the UIButton becomes back to regular size when touch events have ended. So far I have typed this code, but it does not seem to work. Please let me know how I can accomplish this animation.
// whatsTheGame is the UIButton I have
#IBAction func whatsTheGame(_ sender: UIButton) {
logo.isHighlighted = false
UIView.animate(withDuration: 0.3) {
if let centerLogo = self.logo {
centerLogo.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
}
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
UIView.animate(withDuration: 0.3) {
if let centerLogo = self.logo {
centerLogo.transform = CGAffineTransform(scaleX: 1, y: 1)
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
UIView.animate(withDuration: 0.3) {
if let centerLogo = self.logo {
centerLogo.transform = CGAffineTransform(scaleX: 1, y: 1)
}
}
}

Adding Touch Up Inside and Touch Down events
#IBAction func onTouchUpInside(_ sender: Any) {
let button = sender as! UIButton
UIView.animate(withDuration: 0.3) {
button.transform = CGAffineTransform(scaleX: 1, y: 1)
}
}
#IBAction func onTouchDown(_ sender: Any) {
let button = sender as! UIButton
UIView.animate(withDuration: 0.3) {
button.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
}
}

Related

UIButton that has an interupptable animation with UIViewPropertyAnimator

I want a button to expand (Scale up) when the user touches the button, and shrink back to normal when the touch leaves. Similar to iOS stock calculator before iOS 11. This is the class that I created a subclass of the button. It uses touchesbegan and touchesended to start, and or pause and revers the animation, but it does not shrink properly, and it doesn't animate.
class UIOutlineButton: UIButton {
var squishAnimation:UIPropertyAnimator!
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
squishAnimation = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 30, animations: {
let scale = self.transform.scaledBy(x: 0.8, y: 0.8)
let rotate = self.transform.rotated(by: 0.3)
self.transform.concatenating(rotate)
self.transform = scale
})
squishAnimation.isReversed = true
squishAnimation.startAnimation()
self.next?.touchesBegan(touches, with: event)
}
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
colorAnimation = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 30, animations: {
})
squishAnimation = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 30, animations: {
self.transform = CGAffineTransform.identity
})
squishAnimation.stopAnimation(true)
squishAnimation.startAnimation()
}
}
Here is an little snippet which will 'grow' a UIView and then return it to it's original size after a short delay:
/// Expands & Then Returns A UIView To It's Original Size
///
/// - Parameters:
/// - view: UIView
/// - duration: Double
/// - enable: (Void)
func animate(view: UIView, duration: Double, enable: #escaping () -> ()){
view.isUserInteractionEnabled = false
UIView.animate(withDuration: duration, animations: {
view.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
}) { (continueSequence) in
UIView.animate(withDuration: duration, animations: {
view.transform = .identity
}, completion: { (seqeunceFinished) in
enable()
})
}
}
You can then call it within an IBAction as follows:
#IBAction func enlargeButton(_ sender: UIButton){
sender.isUserInteractionEnabled = false
enlarge(view: sender duration: 1) { sender.isUserInteractionEnabled = true }
}

How to drag certain image in iOS?

Wanted to know how I can drag a image across screen and what code would be used. Tried looking up but only older versions of Swift have answer and no longer work. I want to drag the image, but not place finger on screen and it goes to that spot. Just drag.
Gives me the error:
"Use of undeclared type 'uitouch'"
import UIKit
class DraggableImage: UIImageView {
override func touchesMoved(touches: Set<uitouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
let position = touch.locationInView(superview)
center = CGPointMake(position.x, position.y)
}
}
}
You need to subclass UIImageView and in the init you need to set userInteractionEnabled = true and then override this method override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) well, my code is this:
class DraggableImage: UIImageView {
var localTouchPosition : CGPoint?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.red.cgColor
self.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
self.localTouchPosition = touch?.preciseLocation(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
let touch = touches.first
guard let location = touch?.location(in: self.superview), let localTouchPosition = self.localTouchPosition else{
return
}
self.frame.origin = CGPoint(x: location.x - localTouchPosition.x, y: location.y - localTouchPosition.y)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.localTouchPosition = nil
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func drawRect(rect: CGRect) {
// Drawing code
}
*/
}
This is how it looks
Hope this helps
Create a Nsobject Class for moving View and add following Code
import UIKit
class objectClass: UIImageView, UIGestureRecognizerDelegate {
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch = touches.first!
self.center = touch.location(in: self.superview)
}
}
in mainViewController make a object of NSobject class
var newView: objectClass = objectClass()
on button Action to add new View
#IBAction func objectAdded(theButton: UIButton!) {
let frame = CGRect(x: 100, y: 100, width: 44, height: 44)
newView = objectClass(frame: frame)
if theButton.titleLabel?.text == "image1" {
newView.image = UIImage(named: "1")
} else if theButton.titleLabel?.text == "image2" {
newView.image = UIImage(named: "2")
}else{
newView.image = UIImage(named: "3")
}
newView.contentMode = .scaleAspectFill
newView.isUserInteractionEnabled = true
self.view .addSubview(newView)
newView.alpha = 0
UIView .animate(withDuration: 0.4) {
self.newView.alpha = 1
}
UIView.animate(withDuration: 0.6, delay: 0, options: .curveEaseOut, animations: { () -> Void in
self.sliderViewBottomLayoutConstraint.constant = self.sliderViewBottomLayoutConstraint.constant - self.sliderViewBottomLayoutConstraint.constant
self.view.layoutIfNeeded()
}, completion: nil)
image1Button.isEnabled = false
image2Button.isEnabled = false
image3Button.isEnabled = false
let pinchGesture: UIPinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(ViewController.recognizePinchGesture(sender:)))
pinchGesture.delegate = self
let rotateGesture: UIRotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(ViewController.recognizeRotateGesture(sender:)))
let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.RemoveSelectedImageOnTap(sender:)))
tapGesture.numberOfTapsRequired = 2
self.newView.addGestureRecognizer(tapGesture)
self.newView.addGestureRecognizer(pinchGesture)
self.newView.addGestureRecognizer(rotateGesture)
}
func recognizePinchGesture(sender: UIPinchGestureRecognizer) {
sender.view!.transform = sender.view!.transform.scaledBy(x: sender.scale, y: sender.scale)
sender.scale = 1
}
func recognizeRotateGesture(sender: UIRotationGestureRecognizer) {
sender.view!.transform = sender.view!.transform.rotated(by: sender.rotation)
sender.rotation = 0
}

swift skscene touch and hold other action than just touching

So I recently made a swift game with sprite kit and now I am stuck.
I have a character selection screen and I want to show a description for the character if you hold on the character, but if you just touch it you choose it and play with it. I already have the code to play/show the description. I just need to know when to call the corresponding functions and how to differentiate if the node is held or just touched.
Thanks in advance
So, here is how you can do it using SKActions... Also, there is another way using SKAction but I can't really show you all the possibilities :) Anyways, here is the code which does what you want:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
let kLongPressDelayActionKey = "longPressDelay"
let kLongPressStartedActionKey = "longPressStarted"
let kStoppingLongPressActionKey = "stoppingLongPressActionKey"
let desc = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 150))
let button = SKSpriteNode(color: .purple, size: CGSize(width: 150, height: 150))
let other = SKSpriteNode(color: .yellow, size: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
addChild(desc)
addChild(button)
addChild(other)
button.position.y = -160
button.name = "button"
desc.alpha = 0.0
desc.name = "description"
other.name = "other"
other.alpha = 0.0
other.position.y = -320
}
private func singleTap(onNode:SKNode){
other.alpha = other.alpha == 0.0 ? 1.0 : 0.0
}
private func startLongPress(withDuration duration:TimeInterval){
//How long does it take before long press is fired
//If user moves his finger of the screen within this delay, the single tap will be fired
let delay = SKAction.wait(forDuration: duration)
let completion = SKAction.run({
[unowned self] in
self.desc.removeAction(forKey: self.kLongPressDelayActionKey)
self.desc.run(SKAction.fadeIn(withDuration: 0.5), withKey: self.kLongPressStartedActionKey)
})
self.desc.run(SKAction.sequence([delay,completion]), withKey: kLongPressDelayActionKey)
}
private func stopLongPress(){
//Fire single tap and stop long press
if desc.action(forKey: kLongPressDelayActionKey) != nil{
desc.removeAction(forKey: kLongPressDelayActionKey)
self.singleTap(onNode: self.other)
//or just stop the long press
}else{
desc.removeAction(forKey: kLongPressStartedActionKey)
//Start fade out action if it isn't already started
if desc.action(forKey: kStoppingLongPressActionKey) == nil {
desc.run(SKAction.fadeOut(withDuration: 0.2), withKey: kStoppingLongPressActionKey)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
//To stop the long press if we slide a finger of the button
if let touch = touches.first {
let location = touch.location(in: self)
if !button.contains(location) {
stopLongPress()
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
if button.contains(location) {
print("Button tapped")
startLongPress( withDuration: 1)
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
stopLongPress()
}
}
You can run the code and you will see that if you tap on the purple button, a yellow box will pop up. If you tap it again, it will hide. Also, if you hold your finger on purple box for 1 second, a white box will fade in. When you release the finger, it will fade out. As an addition, I have implemented touchesMoved, so if you slide your finger of the purple button while blue box is out, it will fade out as well.
So in this example, I have fused long press with single tap. Single tap is considered to be everything that is not long press. Eg, if user holds his finger 0.01 sec, or 0.5 sec, or 0.99 sec, it will be considered as single tap and yellow box will pop out. If you hold your finger for >= 1 second, the long press action will be triggered.
Another way would be to use gesture recognizers...And I will probably make an example for that later :)
And here is how you can do it using gesture recognizers:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var longPressGesture:UILongPressGestureRecognizer!
var singleTapGesture:UITapGestureRecognizer!
let kLongPressStartedActionKey = "longPressStarted"
let kStoppingLongPressActionKey = "stoppingLongPressActionKey"
let desc = SKSpriteNode(color: .white, size: CGSize(width: 150, height: 150))
let button = SKSpriteNode(color: .purple, size: CGSize(width: 150, height: 150))
let other = SKSpriteNode(color: .yellow, size: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(GameScene.longPress(_:)))
singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(GameScene.singleTap(_:)))
self.view?.addGestureRecognizer(longPressGesture)
self.view?.addGestureRecognizer(singleTapGesture)
addChild(desc)
addChild(button)
addChild(other)
button.position.y = -160
button.name = "button"
desc.alpha = 0.0
desc.name = "description"
other.name = "other"
other.alpha = 0.0
other.position.y = -320
}
private func stopLongPress(){
desc.removeAction(forKey: self.kLongPressStartedActionKey)
//Start fade out action if it isn't already started
if desc.action(forKey: kStoppingLongPressActionKey) == nil {
desc.run(SKAction.fadeOut(withDuration: 0.2), withKey: kStoppingLongPressActionKey)
}
}
func singleTap(_ sender:UITapGestureRecognizer){
if sender.state == .ended {
let location = convertPoint(fromView: sender.location(in: self.view))
if self.button.contains(location) {
self.other.alpha = self.other.alpha == 0.0 ? 1.0 : 0.0
}
}
}
func longPress(_ sender: UILongPressGestureRecognizer) {
let longPressLocation = convertPoint(fromView: sender.location(in: self.view))
if sender.state == .began {
if button.contains(longPressLocation) {
desc.run(SKAction.fadeIn(withDuration: 0.5), withKey: self.kLongPressStartedActionKey)
}
}else if sender.state == .ended {
self.stopLongPress()
}else if sender.state == .changed {
let location = convertPoint(fromView: sender.location(in: self.view))
if !button.contains(location) {
stopLongPress()
}
}
}
}

Retrieving the right position of a scaled UIView

I'm having trouble retrieving the current position of a view, absolutely in the iPhone screen, when the view is scaled.
Here is a minimal example of what I've done.
I have a single elliptical view on screen. When I tap, it grows and start to shiver to show that it is selected. Then I can drag the view all over the screen.
Here is the code:
ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// The View
let toDrag = MyView(frame: CGRect(x: 0, y: 0, width: 50, height: 40))
toDrag.center = CGPoint(x:100, y:100)
// The gesture recognizer
let panGestRec = UIPanGestureRecognizer(target: self, action:#selector(self.panView(sender:)))
toDrag.addGestureRecognizer(panGestRec)
self.view.addSubview(toDrag)
}
func panView(sender: UIPanGestureRecognizer) {
guard let senderView = sender.view else { return }
if sender.state == .recognized { print("Pan Recognized", terminator:"")}
if sender.state == .began { print("Pan began", terminator: "") }
if sender.state == .changed { print("Pan changed", terminator: "") }
if sender.state == .ended { print("Pan ended", terminator: "") }
let point = senderView.convert(senderView.center, to: nil)
print (" point \(point)")
let translation = sender.translation(in: self.view)
senderView.center = CGPoint(x: senderView.center.x + translation.x, y: senderView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
}
}
MyView
import UIKit
class MyView: UIView {
var isAnimated: Bool = false
override init(frame: CGRect) {
super.init(frame: frame) // calls designated initializer
self.isOpaque = false
self.backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.isOpaque = false
self.backgroundColor = UIColor.clear
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isAnimated = true
rotate1()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isAnimated = false
endRotation()
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
isAnimated = false
endRotation()
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {return}
context.addEllipse(in: rect)
context.setLineWidth(1.0)
context.setStrokeColor(UIColor.black.cgColor)
context.setFillColor(UIColor.red.cgColor)
context.drawPath(using: .fillStroke)
}
func rotate1() {
guard isAnimated else { return }
UIView.animate(withDuration: 0.05, delay:0.0, options: [], animations: {
self.transform = CGAffineTransform.init(rotationAngle:.pi/16).scaledBy(x: 1.5, y: 1.5)
}, completion: { _ in
self.rotate2()
})
}
func rotate2() {
guard isAnimated else { return }
UIView.animate(withDuration: 0.05, delay:0.0, options: [], animations: {
self.transform = CGAffineTransform.init(rotationAngle:.pi/(-16)).scaledBy(x: 1.5, y: 1.5)
}, completion: { _ in
self.rotate1()
})
}
func endRotation () {
// End existing animation
UIView.animate(withDuration: 0.5, delay:0.0, options: .beginFromCurrentState, animations: {
self.transform = CGAffineTransform.init(rotationAngle:0 ).scaledBy(x: 1, y: 1)
}, completion: nil)
}
}
With this version, here are some displays of the position
Pan began point (233.749182687299, 195.746572421573)
Pan changed point (175.265022520054, 181.332358181369)
Pan changed point (177.265022520054, 184.332358181369)
Pan changed point (177.265022520054, 184.332358181369)
Pan changed point (178.265022520054, 185.332358181369)
Pan changed point (179.265022520054, 186.332358181369)
Pan changed point (180.265022520054, 187.332358181369)
Pan changed point (180.265022520054, 188.332358181369)
Pan changed point (181.265022520054, 188.332358181369)
Pan RecognizedPan ended point (181.265022520054, 189.332358181369)
You can see that the first position (began point) isn't correct: the view was animated at that moment. The other positions are OK, when dragging, the view don't animate.
If I comment the code in rotate1 and rotate2, all the positions are correct, so I assume that the sizing and eventually the rotation of the view are interfering in the result. My question is: how can I retrieve the correct position of the view, when it is scaled? Obviously, the line
senderView.convert(senderView.center, to: nil)
didn't make what I thought : converting the coordinate to the fixed size screen coordinates?
I tried changing the animation from View animation to Core Animation (layer), and the result was the same.
So I tried to imagine a formula, with size, bounds and frame values, that could result in the correct position, but I did not succeed...
The only think that worked, was to stop immediately the animation with a transform, just before trying to retrieve the center, like that in panView
func panView(sender: UIPanGestureRecognizer) {
guard let senderView = sender.view else { return }
// Stop the transformation now!
senderView.transform = CGAffineTransform.init(rotationAngle:0 ).scaledBy(x: 1, y: 1)
if sender.state == .recognized { print("Pan Recognized", terminator:"")}
...
It isn't the solution I wanted, but I'll go with this for now...
In fact, I had another problem with view convert (Retrieve the right position of a subView with auto-layout)
and my problem was the same, so the solution is here...
I didn't use the method correctly. If I change using beninho85's solution (and even more cleaner cosyn's method to use just one view), it works! I can have the right coordinates even if the view is animated.
I just had to replace, in ViewController :
let point = senderView.convert(senderView.center, to: nil)
with
let point = senderView.convert(CGPoint(x:senderView.bounds.midX, y:senderView.bounds.midY), to: nil)

Swift 2.2: Recording swipe from one UIButton to another works slowly

I have an UIImageView on a storyboard and through the .swift file, I have added several UIButton as subviews into this UIImageView.
What I want to do is, when I swipe from one of these UIButton to another UIButton, the software should record which one it is when I put my finger on the screen, and which one it is when I lift it up.
Everything works correctly. Except, I must do it slowly (ie touch down, and pause before moving my finger, then swipe to the other view, and then pause slightly before touching up). If I naturally do the swipe, it does not trigger the subview.
I do have a line drawn that traces my movement, and I can confirm I am hitting on the SubView.
Any advice for this?
Function for the touchdown event:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Swipe trace
stSwiped = false
if let touch = touches.first as UITouch! {
stLastPoint = touch.locationInView(swipeTraceDisplay)
}
let touch = touches.first
let location: CGPoint = (touch?.locationInView(self.view))!
let buttons: [UIButton] = mapDisplay.subviews as! [UIButton]
for button in buttons {
if button.pointInside(self.view.convertPoint(location, toView: button.viewForBaselineLayout()), withEvent: nil) {
button.backgroundColor = UIColor.redColor()
selectedOriginUIButtion = button
selectedOrigin = arrayMLegs[button.tag - tagOffset]
hasSelectedOrigin = true
}
}
}
Function for the move event (this is irrelevant, but I'll put it on anyway):
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
stSwiped = true
if let touch = touches.first as UITouch! {
let currentPoint = touch.locationInView(swipeTraceDisplay)
drawLineFrom(stLastPoint, toPoint: currentPoint)
stLastPoint = currentPoint
}
}
Function for the touch up event:
override func touchesEnded(touches: Set, withEvent event: UIEvent?) {
if (!stSwiped) {
drawLineFrom(stLastPoint, toPoint: stLastPoint)
}
UIView.animateWithDuration(0.6, delay: 0, options: .CurveEaseIn, animations: {
self.swipeTraceDisplay.alpha = 0
}, completion: { finished in
self.swipeTraceDisplay.image = nil
})
let touch = touches.first
let location: CGPoint = (touch?.locationInView(self.view))!
let buttons: [UIButton] = mapDisplay.subviews as! [UIButton]
if (!hasSelectedOrigin) {
return
}
for button in buttons {
if button.pointInside(self.view.convertPoint(location, toView: button.viewForBaselineLayout()), withEvent: nil) {
button.backgroundColor = UIColor.redColor()
selectedDestination = arrayMLegs[button.tag - tagOffset]
UIView.animateWithDuration(1) {
button.backgroundColor = UIColor.whiteColor()
self.selectedOriginUIButtion.backgroundColor = UIColor.whiteColor()
}
makeEntry(selectedOrigin, destination: selectedDestination)
hasSelectedOrigin = false
return
}
}
selectedOriginUIButtion.backgroundColor = UIColor.whiteColor()
hasSelectedOrigin = false
}
Function for creating a button:
func makeButton(label: String, posX: Int, posY: Int) {
let pposX = CGFloat(posX) * 0.01
let pposY = CGFloat(posY) * 0.01
let button = UIButton(type: .Custom)
button.frame = CGRectMake(pposX * imageWidth, pposY * imageHeight, 44, 44)
button.setTitle(label, forState: UIControlState.Normal)
button.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.layer.borderColor = UIColor.redColor().CGColor
button.backgroundColor = UIColor.whiteColor()
button.layer.borderWidth = 4
button.userInteractionEnabled = false
mapDisplay.addSubview(button)
}
Make the button's hit area larger:
extension UIButton {
override public func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
let relativeFrame = self.bounds
let hitTestEdgeInsets = UIEdgeInsetsMake(-20, -20, -20, -20)
let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
return CGRectContainsPoint(hitFrame, point)
}
}
Thanks in advance!

Resources