Start and stop UISlider animation in Swift? - animation

The question is simple - how to start and stop animation for UISlider programmatically?
func playSliderAnimation() {
view.slider.value = 0
UIView.animate(withDuration: duration) {
self.view.slider.setValue(1, animated: true)
}
}
func stopSliderAnimation() {
view.slider.value = 0
}
tried a lot of combinations for stopSliderAnimation - animating, without of animation, small animation duration, dispath into main queue - nothing works. The circle jumps left somewhere outside the bounds

Related

Adding SKNodes into a ScrollView?

On Android, I've previously used CCScrollView (part of Cocos2D-X) to add nodes to a scrollable container, with all the extras like the elastic bounce, scrollbars, swipe gestures, etc.
But I can't work out how to do this in Xcode. I know there's UIScrollView, but I'm writing for macOS. And there's SKCameraNode, but this seems to target the whole scene, rather than just part of the scene, I think.
I'd like to create a horizontal scrollable container for interactive sprite nodes. I know I could simply make the node move in relation to the mouse's x value, but that's not as pleasing as a simple scrollview.
I guess I could place a second scene within the scrollview, but that seems overkill and a performance hit. There's also third party solutions like SwiftySKScrollView but I'd like to avoid relying on such dependencies.
override func mouseDown(with event: NSEvent) {
scroller.removeAllActions()
scrollerPosX = scroller.position.x - event.location(in: self).x
}
override func mouseDragged(with event: NSEvent) {
mouseMomentum = event.deltaX
for node in nodes(at: event.location(in: self)) {
if node.isMember(of: Scroller.self) {
scroller.position.x = event.location(in: self).x + scrollerPosX
}
}
}
// This needs work to improve the ease out momentum, and a boundary bounce needs adding
override func mouseUp(with event: NSEvent) {
let moveby = SKAction.moveBy(x: mouseMomentum*10, y: 0, duration: 0.3)
moveby.timingMode = .easeOut
scroller.run(moveby)
}

Why Does Autoreversing the Added Animation "Mess Up" the Final Result?

I have an application that animates a small, red subview using added animations. The first animation moves the view down by 100 points. The second animation kicks in at the half way point and moves the view to the right by 100 points. The down and the right animations are additive resulting in a diagonal movement toward the lower right. Additionally, both animations are autoreversed so that the view is restored to its original position over the top of the small, green "marker" subview.
From the above gif one can see that the view animation behaves as expected until the autoreversing has completed whereupon the view jumps to the left (apparently by 100 points). Typically such a jump would be the result of not having set the state of the view to match the final frame of the animation. However, the view state is indeed being correctly(?) set by the animator's completion method - this seems to be borne out by the information provided by the print statements.
What is the cause of the view's final, leftward jump?
See the relevant code below. A complete project can be downloaded from GitHub
I have a hunch that the calls to UIView.setAnimationRepeatAutoreverses in each of the two animation closures might be the culprit. It is not clear to me that what I have done in that regard is in alignment with the framework's expectations.
class ViewController: UIViewController {
private var markerView: UIView!
private var animationView: UIView!
override func viewDidLoad() {
view.backgroundColor = .black
view.addGestureRecognizer(UITapGestureRecognizer.init(target: self, action: #selector(tapGestureHandler)))
let frame = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 10, height: 10)
markerView = UIView(frame: frame)
markerView.backgroundColor = .green
view.addSubview(markerView)
animationView = UIView(frame: frame)
animationView.backgroundColor = .red
view.addSubview(animationView)
}
#objc private func tapGestureHandler(_ sender: UITapGestureRecognizer) {
animate()
}
// Move down; half way through begin moving right while still moving down.
// Autoreverse it.
private func animate() {
let distance: CGFloat = 100
let down = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.y += distance
}
let right = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.x += distance
}
print("\nCenter: \(self.animationView.center)")
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
animator.addAnimations(right, delayFactor: 0.5)
animator.addCompletion { _ in
print("Center: \(self.animationView.center)")
self.animationView.center.x -= distance
self.animationView.center.y -= distance
print("Center: \(self.animationView.center)")
}
animator.startAnimation()
}
}
Update 1
I have added two other versions of the animate method which are informative. Based upon what I learned from these two versions, I altered the title of this question.
animate2: The calls to UIView.setAnimationRepeatAutoreverses have been commented out in each of the down and right animation functions. This modified code functions as expected. Of course we do not get the smooth autoreversing effect.
extension ViewController {
private func animate2() {
let distance: CGFloat = 100
let down = {
//UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.y += distance
}
let right = {
//UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.x += distance
}
print("\nCenter: \(self.animationView.center)")
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
animator.addAnimations(right, delayFactor: 0.5)
animator.addCompletion { _ in
print("Center: \(self.animationView.center)")
self.animationView.center.x -= distance
self.animationView.center.y -= distance
print("Center: \(self.animationView.center)")
}
animator.startAnimation()
}
}
animate3: The addition of the second (i.e. right) animation is commented out. This modified code functions as expected. Of course we do not get the movement to the right.
extension ViewController {
private func animate3() {
let distance: CGFloat = 100
let down = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.y += distance
}
let right = {
UIView.setAnimationRepeatAutoreverses(true)
self.animationView.center.x += distance
}
print("\nCenter: \(self.animationView.center)")
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
//animator.addAnimations(right, delayFactor: 0.5)
animator.addCompletion { _ in
print("Center: \(self.animationView.center)")
self.animationView.center.y -= distance
//self.animationView.center.x -= distance
print("Center: \(self.animationView.center)")
}
animator.startAnimation()
}
}
It appears that calling UIView.setAnimationRepeatAutoreverses in both of the animation functions is "messing things up". It is not clear to me if this can be rightly be regarded as an iOS bug.
Just comment the line under your animate() method ::
let animator = UIViewPropertyAnimator(duration: 2, curve: .linear, animations: down)
animator.addAnimations(right, delayFactor: 0.5)
animator.addCompletion { _ in
print("Center: \(self.animationView.center)")
//self.animationView.center.x -= distance //<--- Comment or remove this line.
self.animationView.center.y -= distance
print("Center: \(self.animationView.center)")
}

UITableView cell height animation

I do widget. I need to changed cell height with animation. When click "show more" go extra controls.
enter image description here
I wrote this code
tableView.beginUpdates()
showModes(alpha: 1, duration: 0.2, delay: 0.1)
tableView.endUpdates()
func showModes(alpha: CGFloat, duration: CGFloat, delay: CGFloat) {
if tableView.numberOfRows(inSection: 0) != 0 {
for i in 0...tableView.numberOfRows(inSection: 0)-1 {
let indexPath = IndexPath(row: i, section: 0)
if let cell = tableView.cellForRow(at: indexPath) as? WidgetCell {
UIView.animate(withDuration: TimeInterval(duration), delay: TimeInterval(delay) ,animations: {
if alpha == 0 {
cell.favoriteConstraint.constant = -45
} else {
cell.favoriteConstraint.constant = 10
}
cell.favoriteMode.alpha = alpha
self.tableView.layoutIfNeeded()
})
}
}
}
}
But it does not work smoothly. it dramatically twitches up and down. How do I get this to work smoothly, for example, in the weather widget by Apple?
I can't recognise what is a problem definetly because you posted only part of your code. But I can suggest several checks to do
Did you set self-sizing cells for UITableView? AtableView.rowHeight = UITableViewAutomaticDimension;
Did you added constraint (favoriteConstraint) to cell's contentView
Also when animating constraint changes you don't need to change it inside animation closure.
The standard code looks like
cell.favoriteConstraint.constant = newValue
UIView.animateWithDuration(0.3) {
cell.contentView.layoutIfNeeded()
}
Also you don't need to wrap your method call. This must be ok:
showModes(alpha: 1, duration: 0.2, delay: 0.1)
tableView.beginUpdates()
tableView.endUpdates()

Clock minute-hand disappears when attempting to rotate it

Modus Operandi:
1) Use an UIImageView of a base Clock Image.
2) Add MinuteHand & HourHand sublayers (containing their respective images) to the UIImageView layer.
Problem: both sublayers disappear when attempting to perform a rotation transformation.
Note: 1) I've removed the 'hour' code & ancillary radian calculations to simplify code.
2) The 'center' is the center of the clock. I had adjusted the coordinates to actually pin the hands to the clock's center.
3) The ViewDidLayoutSubviews() appear to be okay. I got the clock + hands.
class ClockViewController:UIViewController {
private let minuteLayer = CALayer()
#IBOutlet weak var clockBaseImageView: UIImageView!
#IBOutlet weak var datePicker: UIDatePicker!
override func viewDidLayoutSubviews() {
guard var minuteSize = UIImage(named: "MinuteHand")?.size,
var hourSize = UIImage(named: "HourHand")?.size
else {
return
}
var contentLayer:CALayer {
return self.view.layer
}
var center = clockBaseImageView.center
// Minute Hand:
minuteLayer.setValue("*** Minute Hand ***", forKey: "id")
minuteSize = CGSize(width: minuteSize.width/3, height: minuteSize.height/3)
minuteLayer.contents = UIImage(named: "MinuteHand")?.cgImage
center = CGPoint(x: 107.0, y: 40.0)
var handFrame = CGRect(origin: center, size: minuteSize)
minuteLayer.frame = handFrame
minuteLayer.contentsScale = clockBaseImageView.layer.contentsScale
minuteLayer.anchorPoint = center
clockBaseImageView.layer.addSublayer(minuteLayer)
}
Here's my problem: Attempting to rotate the minute hand via 0.01 radians:
func set(_ time:Date) {
minuteLayer.setAffineTransform(CGAffineTransform(rotationAngle: .01)) // random value for test.
}
Before rotation attempt:
After attempting to rotate minute hand:
The hand shifted laterally to the right vs rotate.
Why? Perhaps due to the pivot point?
I think this will solve your problem, Take a look and let me know.
import GLKit // Importing GLKit Framework
func set(_ time:Date) {
minuteLayer.transform = CGAffineTransformMakeRotation(CGFloat(GLKMathDegreesToRadians(0.01)))
}
Note: this solution doesn't solve the issue about rotating a CALayer. Instead, it bypasses the issue by replacing the layer with a subview and rotating the subview via:
func set(_ time:Date) {
minuteView.transform = CGAffineTransform(rotationAngle: 45 * CGFloat(M_PI)/180.0)
}
Here's the result:
Still, it would be nice to know how to rotate a CALayer.

Swift animations appear to be working in reverse

I'm very new to iOS/xcode development, I'm trying to do something very basic as part of a tutorial exercise which is to fade a UILabel in once a user has completed a game.
So I'm hiding the label when the view loads:
override func viewDidLayoutSubviews() {
winMessageLabel.alpha = 0
}
And then on completion of the game I'm trying to fade it back in:
UIView.animateWithDuration(1, animations: { () -> Void in
self.winMessageLabel.alpha = 1
self.view.layoutIfNeeded()
})
But rather than fade in it appears with alpha 1 and then fades out. I get the same behaviour if I try to animate the x/y coordinates whereby it jumps to where I want it to go and then animates back to its original position
I've just worked it out - the layout is invalid so if I call layoutIfNeeded() prior to my animation it sorts everything out and then the label animates as expected:
self.view.layoutIfNeeded()
UIView.animateWithDuration(1, animations: { () -> Void in
self.winMessageLabel.alpha = 1
self.view.layoutIfNeeded()
})

Resources