I have troubles in creating a very simple square button with the following behaviour:
When I set my finger on the button it should shrink down a bit (animated)
When I hold my finger on the button the button should keep at its small state.
When I lift my finger the button should grow back to its initial size (animated).
The problem is, when I touch/lift up while the animation is playing the button should stop the current animation and shrink/grow. Right now I can only interact with the button after the grow/shrink animation is fully played.
I tried to use the layer.removeAllAnimations() function in the touch functions ... without success. I tried to animate the View itself and the layer.
Here is my attempt of the UIControl that I used to build my custom button.
import UIKit
class TriggerPad: UIControl {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
// I init the Frame with w:200 h:100 in my ViewController
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
self.layer.frame = frame
self.layer.backgroundColor = UIColor.blueColor().CGColor
self.multipleTouchEnabled = true
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let oldFrame = self.layer.frame
self.layer.removeAllAnimations()
UIView.animateWithDuration(2) { () -> Void in
self.layer.frame.size.height = 50
self.layer.frame.size.width = 100
// I need to adjust the origin here
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
let oldFrame = self.layer.frame
self.layer.removeAllAnimations()
UIView.animateWithDuration(2) { () -> Void in
self.layer.frame.size.height = 100
self.layer.frame.size.width = 200
// I need to adjust the origin here
}
}
}
You can solve it adding to the UIView the animation option .AllowUserInteraction. Your code would be:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
UIView.animateWithDuration(2.0, delay: 0.0, options: UIViewAnimationOptions.AllowUserInteraction, animations: { () -> Void in
self.layer.frame.size.height = 50
self.layer.frame.size.width = 100
// I need to adjust the origin here
}, completion: nil)
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
UIView.animateWithDuration(2.0, delay: 0.0, options: UIViewAnimationOptions.AllowUserInteraction, animations: { () -> Void in
self.layer.frame.size.height = 100
self.layer.frame.size.width = 200
// I need to adjust the origin here
}, completion: nil)
}
But I think you can get the similar effect to the App you mention with:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
UIView.animateWithDuration(2.0, delay: 0.0, options: UIViewAnimationOptions.AllowUserInteraction, animations: { () -> Void in
self.transform = CGAffineTransformMakeScale(0.6, 0.6)
}, completion: nil)
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
UIView.animateWithDuration(2.0, delay: 0.0, options: UIViewAnimationOptions.AllowUserInteraction, animations: { () -> Void in
self.transform = CGAffineTransformIdentity
}, completion: nil)
}
Because this way it keeps the view's center.
Related
I have screen to test the device touch screen with popping bubbles. And some imageView added in subviews of bubbles made of cross for them. Then user swipe over the bubbles to check the touch screen.
And I want drawing on the same view. When user swipes the finger over the bubbles, a line will be drawn. I have separate class for drawing and assign it to main parent view of controller.
If I remove code for UIPanGestureRecognizer then drawing works and there are no lags.
If I add gesture to view for popping the bubbles like this
view.addGestureRecognizer(gestureRecognizer)
Then there is a lag, and drawing doesn't work.
I want both things like popping bubbles and drawing on view.
The main problem of this gesture is when I add this in view, then drawing works without any lag but popping bubbles doesn't work.
let gestureRecognizer : UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:)))
gestureRecognizer.maximumNumberOfTouches = 1
gestureRecognizer.minimumNumberOfTouches = 1
view.addGestureRecognizer(gestureRecognizer)
Drawing view class
import UIKit
class DrawingView: UIView {
var drawColor = UIColor.black
var lineWidth: CGFloat = 5
private var lastPoint: CGPoint!
private var bezierPath: UIBezierPath!
private var pointCounter: Int = 0
private let pointLimit: Int = 128
private var preRenderImage: UIImage!
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
initBezierPath()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initBezierPath()
}
func initBezierPath() {
bezierPath = UIBezierPath()
bezierPath.lineCapStyle = CGLineCap.round
bezierPath.lineJoinStyle = CGLineJoin.round
}
// MARK: - Touch handling
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)
bezierPath.move(to: lastPoint)
bezierPath.addLine(to: newPoint)
lastPoint = newPoint
pointCounter += 1
if pointCounter == pointLimit {
pointCounter = 0
renderToImage()
setNeedsDisplay()
bezierPath.removeAllPoints()
}
else {
setNeedsDisplay()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
pointCounter = 0
renderToImage()
setNeedsDisplay()
bezierPath.removeAllPoints()
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
touchesEnded(touches!, with: event)
}
// MARK: - Pre render
func renderToImage() {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
if preRenderImage != nil {
preRenderImage.draw(in: self.bounds)
}
bezierPath.lineWidth = lineWidth
drawColor.setFill()
drawColor.setStroke()
bezierPath.stroke()
preRenderImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
// MARK: - Render
override func draw(_ rect: CGRect) {
super.draw(rect)
if preRenderImage != nil {
preRenderImage.draw(in: self.bounds)
}
bezierPath.lineWidth = lineWidth
drawColor.setFill()
drawColor.setStroke()
bezierPath.stroke()
}
// MARK: - Clearing
func clear() {
preRenderImage = nil
bezierPath.removeAllPoints()
setNeedsDisplay()
}
// MARK: - Other
func hasLines() -> Bool {
return preRenderImage != nil || !bezierPath.isEmpty
}
}
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 }
}
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
}
I'm trying to make an animated UILabel using CATransition which can fade out the original text and fade in the new text when I touch the screen. Here is my code and I can't figure out why there is no animation. Please help me. I'm using Xcode 7.3.
var subtitle:UILabel!
var winsize:CGSize!
override func viewDidLoad() {
super.viewDidLoad()
let animation = CATransition()
animation.type = kCATransitionFade
animation.duration = 0.75
animation.fillMode = kCAFillModeBoth
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animation.delegate = self
self.subtitle = UILabel()
self.subtitle.text = "f"
self.subtitle.frame = CGRectMake(45, 30, 200, 50)
self.subtitle.font = UIFont.systemFontOfSize(25)
self.subtitle.textColor = UIColor.grayColor()
self.view.addSubview(self.subtitle)
self.subtitle.layer.addAnimation(animation, forKey: "animation")
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.subtitle.text="HighSchool"
}
With help from #matt, the following code works.
var subtitle:UILabel!
var winsize:CGSize!
override func viewDidLoad() {
super.viewDidLoad()
winsize = self.view.frame.size
self.subtitle = UILabel()
self.subtitle.text = "f"
self.subtitle.frame = CGRectMake(45, 30, 200, 50)
self.subtitle.font = UIFont.systemFontOfSize(25)
self.subtitle.textColor = UIColor.grayColor()
self.view.addSubview(self.subtitle)
UIView.transitionWithView(self.subtitle, duration: 0.5, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: nil, completion: nil)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.subtitle.text="HighSchool"
}
Like this:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let opts : UIViewAnimationOptions = .TransitionCrossDissolve
UIView.transitionWithView(self.subtitle, duration: 0.75, options: opts, animations: {
self.subtitle.text="HighSchool"
}, completion: nil)
}
Note: In the below question, I use the term 'lagging' when I probably mean 'latency' when drawing using the addCurveToPoint function.
Problem:
Both bezier curve functions, addQuadCurveToPoint and addCurveToPoint have one strength and one weakness each. The aim is to get the perfect combination of both, a perfect continuous smooth curved line that is lag-free when drawn. The images below show where the touch on the screen typically is in comparison to the updated drawing.
The below image uses the function addQuadCurveToPoint. It draws fast
with no lagging while drawing during touch events, but the end result is a
less perfect smooth curved line that appears more segmented.
The below image uses the function
addCurveToPoint. It draws near perfect continuous smooth curved
lines but is slower with some lag noticeable while drawing during
touch events.
Question:
Can anyone help explain or give a solution please:
how to get perfect addQuadCurveToPoint curved lines or lag-free addCurveToPoint curved lines?
Note: The focus of this question is immediate lagging from the initial touch event for addCurveToPoint, not lagging over time, and also the less perfect curve line for addQuadCurveToPoint.
This code example is just one type of many implementations of addCurveToPoint:
// Swift 2 code below tested using Xcode 7.0.1.
class drawView: UIView {
var path:UIBezierPath?
var incrementalImage:UIImage?
var points = [CGPoint?](count: 5, repeatedValue: nil)
var counter:Int?
var infoView:UIView = UIView()
var strokeColor:UIColor?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.multipleTouchEnabled = false
self.backgroundColor = UIColor.whiteColor()
path = UIBezierPath()
path?.lineWidth = 20.0
strokeColor = UIColor.darkGrayColor()
path?.lineCapStyle = CGLineCap.Round
}
override init(frame: CGRect) {
super.init(frame: frame)
self.multipleTouchEnabled = false
path = UIBezierPath()
path?.lineWidth = 20.0
}
override func drawRect(rect: CGRect) {
incrementalImage?.drawInRect(rect)
strokeColor?.setStroke()
path?.stroke()
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
counter = 0
let touch: AnyObject? = touches.first
points[0] = touch!.locationInView(self)
infoView.removeFromSuperview()
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch: AnyObject? = touches.first
let point = touch!.locationInView(self)
counter = counter! + 1
points[counter!] = point
if counter == 4{
points[3]! = CGPointMake((points[2]!.x + points[4]!.x)/2.0, (points[2]!.y + points[4]!.y)/2.0)
path?.moveToPoint(points[0]!)
path?.addCurveToPoint(points[3]!, controlPoint1: points[1]!, controlPoint2: points[2]!)
self.setNeedsDisplay()
points[0]! = points[3]!
points[1]! = points[4]!
counter = 1
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.drawBitmap()
self.setNeedsDisplay()
path?.removeAllPoints()
counter = 0
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
self.touchesEnded(touches!, withEvent: event)
}
func drawBitmap(){
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0.0)
strokeColor?.setStroke()
if((incrementalImage) == nil){
let rectPath:UIBezierPath = UIBezierPath(rect: self.bounds)
UIColor.whiteColor().setFill()
rectPath.fill()
}
incrementalImage?.drawAtPoint(CGPointZero)
path?.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
This code example is just one type of many implementations of addQuadCurveToPoint:
// Swift 2 code below tested using Xcode 7.0.1.
class DrawableView: UIView {
let path=UIBezierPath()
var previousPoint:CGPoint
var lineWidth:CGFloat=20.0
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override init(frame: CGRect) {
previousPoint=CGPoint.zero
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
previousPoint=CGPoint.zero
super.init(coder: aDecoder)
let panGestureRecognizer=UIPanGestureRecognizer(target: self, action: "pan:")
panGestureRecognizer.maximumNumberOfTouches=1
self.addGestureRecognizer(panGestureRecognizer)
}
override func drawRect(rect: CGRect) {
// Drawing code
UIColor.darkGrayColor().setStroke()
path.stroke()
path.lineWidth=lineWidth
path.lineCapStyle = .Round
}
func pan(panGestureRecognizer:UIPanGestureRecognizer)->Void
{
let currentPoint=panGestureRecognizer.locationInView(self)
let midPoint=self.midPoint(previousPoint, p1: currentPoint)
if panGestureRecognizer.state == .Began
{
path.moveToPoint(currentPoint)
}
else if panGestureRecognizer.state == .Changed
{
path.addQuadCurveToPoint(midPoint,controlPoint: previousPoint)
}
previousPoint=currentPoint
self.setNeedsDisplay()
}
func midPoint(p0:CGPoint,p1:CGPoint)->CGPoint
{
let x=(p0.x+p1.x)/2
let y=(p0.y+p1.y)/2
return CGPoint(x: x, y: y)
}
}