I want to rotate a UIImage, I have managed to do so with the code below, however when I press the rotate button again, the image does not rotate anymore, could someone please explain why?
#IBAction func rotate(sender: UIButton) {
UIView.animateWithDuration(0.2, animations: {
self.shape.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4) * 2)
})
}
You are changing your shape image view's transform to a new, fixed value. If you tap on it again, the transform already has that value. You set the transform to the same value again, which doesn't change anything.
You need to define an instance variable to keep track of the rotation.
var rotation: CGFloat = 0
#IBAction func rotate(sender: UIButton)
{
UIView.animateWithDuration(0.2, animations:
{
self.rotation += CGFloat(M_PI_4) * 2 //changed based on Daniel Storm's comment
self.shape.transform = CGAffineTransformMakeRotation(rotation)
})
}
That way, each time your tap the button you'll change the rotation variable from it's previous value to a new value and rotate to that new angle.
Related
I'm working on a swift camera app and trying to solve the following problem.
My camera app, which allows taking a video, can change the camera focus and exposure when a user taps somewhere on the screen. Like the default iPhone camera app, I displayed the yellow square to show where the user tapped. I can see exactly where the user tapped unless triggering the timer function and updating the total video length on the screen.
Here is the image of the camera app.
As you can see there is a yellow square and that was the point I tapped on the screen.
However, when the timer counts up (in this case, 19 sec to 20sec) and updates the text of total video length, the yellow square moves back to the center of the screen. Since I put the yellow square at the center of the screen on my storyboard, I guess when the timer counts up and updates the label text, it also refreshing the yellow square, UIView, so displaying at the center of the screen (probably?).
So if I'm correct, how can I display the yellow square at the user tapped location regardless of the timer function, which updates UIlabel for every second?
Here is the code.
final class ViewController: UIViewController {
private var pointOfInterestHalfCompletedWorkItem: DispatchWorkItem?
#IBOutlet weak var pointOfInterestView: UIView!
#IBOutlet weak var time: UILabel!
var counter = 0
var timer = Timer()
override func viewDidLayoutSubviews() {
pointOfInterestView.layer.borderWidth = 1
pointOfInterestView.layer.borderColor = UIColor.systemYellow.cgColor
}
#objc func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
#objc func timerAction() {
counter += 1
secondsToHoursMinutesSeconds(seconds: counter)
}
func secondsToHoursMinutesSeconds (seconds: Int) {
// format seconds
time.text = "\(strHour):\(strMin):\(strSec)"
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let focusPoint = touches.first!.location(in: lfView)
showPointOfInterestViewAtPoint(point: focusPoint)
}
func showPointOfInterestViewAtPoint(point: CGPoint) {
print("point is here \(point)")
pointOfInterestHalfCompletedWorkItem = nil
pointOfInterestComplatedWorkItem = nil
pointOfInterestView.center = point
pointOfInterestView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
let animation = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) {
self.pointOfInterestView.transform = .identity
self.pointOfInterestView.alpha = 1
}
animation.startAnimation()
let pointOfInterestHalfCompletedWorkItem = DispatchWorkItem { [weak self] in
guard let self = self else { return }
let animation = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) {
self.pointOfInterestView.alpha = 0.5
}
animation.startAnimation()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: pointOfInterestHalfCompletedWorkItem)
self.pointOfInterestHalfCompletedWorkItem = pointOfInterestHalfCompletedWorkItem
}
}
Since I thought it's a threading issue, I tried to change the label text & to show the yellow square in the main thread by writing DispatchQueue.main.asyncAfter, but it didn't work. Also, I was not sure if it becomes serial queue or concurrent queue if I perform both
loop the timer function and constantly updating label text
detect UI touch event and show yellow square
Since UI updates are performed in the main thread, I guess I need to figure out a way to share the main thread for the timer function and user touch event...
If someone knows a clue to solve this problem, please let me know.
It isn't a threading issue. It is an auto layout issue.
Presumably you have positioned the yellow square view in your storyboard using constraints.
You are then modifying the yellow square's frame directly by modifying the center property; this has no effect on the constraints that are applied to the view. As soon as the next auto layout pass runs (triggered by the text changing, for example) the constraints are reapplied and the yellow square jumps back to where your constraints say it should be.
You have a couple of options;
Compute the destination point offset from the center of the view and then apply those offsets to the constant property of your two centering constraints
Add the yellow view programatically and position it by setting its frame directly. You can then adjust the frame by modifying center as you do now.
I am trying to animate a rotated label like this:
#IBOutlet fileprivate weak var loadingLabel: UILabel!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
loadingLabel.transform = CGAffineTransform(rotationAngle: CGFloat(0.2)) // rotation line
UIView.animate(withDuration: 2.0, animations: {
self.loadingLabel.transform = CGAffineTransform(translationX: 0, y: self.view.bounds.size.height)
})
}
When I comment out the rotation line of code (and keep the label unrotated), it works fine. But when I rotate it, the label starts off the screen at the beginning of the animation:
When I comment out the animation, the label is rotated perfectly fine (but no animation obviously):
How do I rotate the image and animate it, without having this weird placement?
Edit: To clarify: I want the label to start off rotated in the center of the screen, and just simply move the label. I do not want to rotate the image during the animation.
The correct answer is that you are supposed to concatenate the transformation matrices. If you don't want to do linear algebra then the easy way is that you use the transform to set the rotation and don't animate it, then animate the view's frame/center instead.
import UIKit
class V: UIViewController {
#IBOutlet var label: UILabel!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
label.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 6)
label.center.x += 300
UIView.animate(withDuration: 2) {
self.label.center.x -= 300
}
}
}
You can perform the animation with CABasicAnimation as it will give you more control on the animation and it has a completion block on which you can hide your label as well upon your requirement.
loadingLabel.transform = CGAffineTransform(rotationAngle: CGFloat(0.2)) // rotation line
let animationKey = "position.y"
CATransaction.begin()
let moveYAnimation = CABasicAnimation( keyPath: animationKey)
moveYAnimation.fromValue = loadingLabel.frame.origin.y
moveYAnimation.toValue = self.view.bounds.size.height
moveYAnimation.duration = 2
loadingLabel.layer.add( moveYAnimation, forKey: animationKey )
// Callback function
CATransaction.setCompletionBlock {
print("end animation")
self.loadingLabel.isHidden = true
}
// Do the actual animation and commit the transaction
loadingLabel.layer.add(moveYAnimation, forKey: animationKey)
CATransaction.commit()
Hope it will help you.
The first transform is ot of the animation block, it's why it begin out of the screen.
You should move it in the animation block, and use a completion handler to animate again.
UIView.animate(withDuration: 2.0, animations: {
//
}, completion: { (result) in
//
})
Be carefull, the angle is in radians.
I made a simple project, with swipe gesture recogniser and animation. I made my label to move and every 3 second increase number. With every swipe I need to decrease the number. My gesture recogniser object is tied with label, i.e. it works only in label bounds. When prog is working without animation everything is ok, but when it;s animated an is moving my gesture recogniser is doing nothing. How to make a gesture recogniser work at the same time as animation, i.e. while animated to respond to my swipes. Need help.
`
#IBOutlet weak var label1: UILabel!
var number : Int = 0
var timer = Timer()
#IBAction func label1SwipeRight(_ sender: UISwipeGestureRecognizer) {
number += 1
label1.text = String(number)
}
func animate1() {
UIView.animate(withDuration: 4.0, delay: 0.0, options: .allowUserInteraction, animations: {
let num1 : CGFloat = CGFloat(arc4random_uniform(667))
let num2 : CGFloat = CGFloat(arc4random_uniform(375))
self.label1.frame.origin.y = num1
self.label1.frame.origin.x = num2
}, completion: {(bool) in
self.animate1()
print("Animation1 completed")
})
}
func timerExample() {
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
label1.text = String(Int(label1.text!)! + 1)
}`
By default view objects block user interaction while an animation is "in flight". You need to use one of the "long form" animation methods, and pass in the option .allowUserInteraction. Something like this:
UIView.animate(duration: 0.5,
delay: 0.0,
options: .allowUserInteraction,
animations: {
myView.alpha = 0.5
})
Note, however, that if what you're animating is a view's position, the user won't be able to tap on the view object as it moves. That's because a position animation does not really animate the object from one place to another over time. It just creates that appearance. Behind the scenes, the object actually jumps to it's final position the moment the animation begins.
If you need to be able to tap/drag/swipe on objects while they're moving you will have to do that yourself. What you do is put a gesture recognizer on the parent view that encloses the entire range of motion (possibly the whole screen.) Then you need to use the presentation layer of your animating view's layer, translate the coordinates of the point from the gesture recognizer's coordinate space to the layer's coordinate space, and use the layer's hitTest method to figure out if the point is on the layer or not.
I have a project on Github called iOS-CAAnimation-group-demo that does something like that (It animates an image view along a complex path and you can tap on the image view to pause the animation while it's "in flight".
It's from several years ago, so it's written in Objective-C, but it should help to at least illustrate the technique.
I made a simple project, with swipe gesture recogniser and animation. I made my label to move and every 3 second increase number. With every swipe I need to decrease the number. My gesture recogniser object is tied with label, i.e. it works only in label bounds. When prog is working without animation everything is ok, but when it;s animated an is moving my gesture recogniser is doing nothing. How to make a gesture recogniser work at the same time as animation, i.e. while animated to respond to my swipes. Need help.
`
#IBOutlet weak var label1: UILabel!
var number : Int = 0
var timer = Timer()
#IBAction func label1SwipeRight(_ sender: UISwipeGestureRecognizer) {
number += 1
label1.text = String(number)
}
func animate1() {
UIView.animate(withDuration: 4.0, delay: 0.0, options: .allowUserInteraction, animations: {
let num1 : CGFloat = CGFloat(arc4random_uniform(667))
let num2 : CGFloat = CGFloat(arc4random_uniform(375))
self.label1.frame.origin.y = num1
self.label1.frame.origin.x = num2
}, completion: {(bool) in
self.animate1()
print("Animation1 completed")
})
}
func timerExample() {
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
label1.text = String(Int(label1.text!)! + 1)
}`
By default view objects block user interaction while an animation is "in flight". You need to use one of the "long form" animation methods, and pass in the option .allowUserInteraction. Something like this:
UIView.animate(duration: 0.5,
delay: 0.0,
options: .allowUserInteraction,
animations: {
myView.alpha = 0.5
})
Note, however, that if what you're animating is a view's position, the user won't be able to tap on the view object as it moves. That's because a position animation does not really animate the object from one place to another over time. It just creates that appearance. Behind the scenes, the object actually jumps to it's final position the moment the animation begins.
If you need to be able to tap/drag/swipe on objects while they're moving you will have to do that yourself. What you do is put a gesture recognizer on the parent view that encloses the entire range of motion (possibly the whole screen.) Then you need to use the presentation layer of your animating view's layer, translate the coordinates of the point from the gesture recognizer's coordinate space to the layer's coordinate space, and use the layer's hitTest method to figure out if the point is on the layer or not.
I have a project on Github called iOS-CAAnimation-group-demo that does something like that (It animates an image view along a complex path and you can tap on the image view to pause the animation while it's "in flight".
It's from several years ago, so it's written in Objective-C, but it should help to at least illustrate the technique.
How can I rotate my view on each button click?
When I try to:
#IBAction func replaceCurrencies(sender: AnyObject) {
UIView.animateWithDuration(Double(0.5), animations: {
self.arrows.transform = CGAffineTransformMakeRotation(CGFloat(M_PI))
}
}
it rotates just one time. It do not rotate for the next my clicks. How can I fix it?
This is because when you push your button again your view already have this transform. You need to reset it to CGAffineTransformIdentity or rotate from current transform. Second way is preferable because it will not jump to start before rotating:
#IBAction func replaceCurrencies(sender: AnyObject) {
UIView.animateWithDuration(0.5, animations: {
self.arrows.transform = CGAffineTransformRotate(self.arrows.transform, CGFloat(M_PI))
})
}