Why isn't my UISlider animating? - ios

According to Apple's documentation, it is possible to programmatically set the value of a UISlider with a smooth animation. I'm attempting to do so from a custom view controller, the UI is being defined from a storyboard.
The Context
In my example I'm attempting to update the slider value from a custom view controller, the UI is being defined from a storyboard. The example only renders a single slider.
When the user releases the slider, the value is reset to 0.
The Code
import UIKit
class ViewController: UIViewController {
#IBOutlet var mySlider: UISlider!
#IBAction func resetSlider() {
mySlider.setValue(0, animated:true)
NSLog("Reset!")
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
resetSlider is linked to the Touch Up Inside event.
The Problem
When resetSlider is called the value does change on the interface, but it does not animate (the value simply "jumps" to 0). My goal is to have the value gracefully shift back to zero.
Note: "Reset!" only displays once (per click), which indicates that resetSlider is not being called multiple times.
Why isn't this UISlider animating?
The Video
Since IB is so visual, here is a video of the situation, password is code

the setValue animated parameter doesn't actually perform an animation, but rather it enables animation.
To trigger the animation, you need to use UIView.animateWithDuration, and pass in the setValue command as the animation:
UIView.animateWithDuration(0.2, animations: {
self.mySlider.setValue(0, animated:true)
})
Swift 5
UIView.animate(withDuration: 0.2, animations: {
self.mySlider.setValue(0, animated:true)
})

The solution of #Slifty is correct. But if you want UISlider is as smooth as defauft, so you can decrease the time animation to below 0.2. It may work such as 0.1/2 or 0.1/3.

Related

Swift: how to finish a UIView draw animation before changing a label

I have a little problem regarding the timing of an animation.
The sequence should be:
Button is pressed
Animation is displayed
Label is changed
In reality the sequence is:
Button is pressed
Label is changed
Animation is displayed
Here is the code of the main VC:
import Foundation
import UIKit
class MainVC: UIViewController {
var counter: Int = 0
#IBOutlet weak var counterLabel: UILabel!
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var animationView: AnimationView!
#IBAction func nextButtonPressed(_ sender: UIButton) {
counter += 1
animationView.progress = 1
//this view draws a progress bar and animates it from 0 to 100% via CABasicAnimation.
counterLabel.text = "\(counter)"
animationView.progress = 0
//the progress bar is reset
}
override func viewDidLoad() {
super.viewDidLoad()
nextButton.setTitle("Next")
counterLabel.text = "\(counter)"
}
}
I've experimented with the dispatch queue, but I just can't get it to work right. Any ideas on how to solve this?
You don't show enough of your code for us to be able to help you specifically.
In general, you would set some object to be the delegate of your CAAnimation, and implement the method func animationDidStop(_ anim: CAAnimation, finished flag: Bool).
In that method you'd then change the label text (assuming finished == true.)
I don't really like the delegate pattern for animations. If you have multiple animations it can be painful to handle custom completion code for each one. What I've done is use the fact that you can attach objects to CAAnimation objects, and attach a closure to the animation. I then set the view controller to be the delegate, and in the view controller's animationDidStop(), I look for a closure attached to the animation, and execute it if there is one.
Edit:
See this article for a discussion on using key-value coding to attach objects to CAAnimation and CALayer objects:
Core Animation Key-value coding extensions
It's old, and written using Objective-C, but the concepts are the same in Swift, and very useful. For a more general discussion on key-value coding, which is updated for Swift, see this article: https://developer.apple.com/documentation/objectivec/nsobject/nskeyvaluecoding
(The two functions you need to know in Swift are setValue(_:forKeyPath:) and value(forKey:).)

CAAnimation: Syncing view's alpha with presentation layer's opacity

I'm animating the opacity of a view's layer using CAKeyframeAnimation, when app goes to background, the animation's removed, but I need to make the view's alpha to be the same as the animation, should I do:
view.alpha = view.layer.presentationLayer.opacity
???
Thanks!
Update:
I have three labels overlapping with each other, I used key frame animation to animate their opacity with different key frame values (for opacity) to mimic a crossfade animation. The problem is when app goes to background, the animations are removed (according to https://forums.developer.apple.com/thread/15796) so they all have alpha 1 and overlap with each other, that's why I wanted to sync the view with their presentation layer.
If the goal is to capture the opacity when the app goes in background, you can add an observer for UIApplicationDidEnterBackground, capture the opacity, cancel the animation, and set the alpha. E.g., in Swift:
class ViewController: UIViewController {
#IBOutlet weak var viewToAnimate: UIView!
private var observer: NSObjectProtocol!
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(forName: .UIApplicationDidEnterBackground, object: nil, queue: .main) { [weak self] notification in
if let opacity = self?.viewToAnimate.layer.presentation()?.opacity {
self?.viewToAnimate.layer.removeAllAnimations()
self?.viewToAnimate.alpha = CGFloat(opacity)
}
}
}
deinit {
NotificationCenter.default.removeObserver(observer)
}
// I'm just doing a basic animation, but the idea is the same whatever animation you're doing
#IBAction func didTapButton(_ sender: Any) {
UIView.animate(withDuration: 10) {
self.viewToAnimate.alpha = 0
}
}
}
If your goal is to remember it even if the app is terminated, then you'd need to save this in persistent storage. But if your goal is to merely set the alpha while the app is suspended and/or running in background, the above is sufficient.

Xcode Swipe Gesture doesn't change constraint constant

I have an issue with the swipe gesture recognizer in Xcode. What I want to let the code do is the following: When you swipe upwards a view with floating buttons disappears. When you swipe downwards, the floating buttons appear again. But of course, it doesn't work :D
I can change the position of the view manually in StoryBoard without messing the constraints up.
Everything is connected properly.
It doesn't give an error message either, just doesn't work.
What can I do?
I use the following code:
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var BottomFloatersBottomLayoutGuide: NSLayoutConstraint!
#IBAction func UpSwipe(_ sender: UISwipeGestureRecognizer) {
BottomFloatersBottomLayoutGuide.constant = -400
}
#IBAction func DownSwipe(_ sender: UISwipeGestureRecognizer) {
BottomFloatersBottomLayoutGuide.constant = 0
}
}
You have to put it in this function:
UIView.animate(withDuration: 1.0){
//your code here
}
Everything in that block will animate: change colors, a view's frame etc. So also your constrains.
Edit: Woops, I am wrong. In UIView.animate, self.view.layoutIfNeeded() needs to be called. The change of your constrains should be outside that block. layoutIfNeeded() updates the constrains.
Your code should look like this:
#IBAction func UpSwipe(_ sender: UISwipeGestureRecognizer) {
BottomFloatersBottomLayoutGuide.constant = -400
UIView.animate(withDuration: 1.0){
self.view.layoutIfNeeded()
}
}

UISliders and UILabels and viewControllers

How would I be able to increase/decrease the size of my UILabel by using UISlider which is in a different viewController?
I have a viewController1 that has the UILabel1 and I have viewController2 which has a UISlider. With the UISlider I have another label,UILabel2, just to see how big the text will be. I want UILabel1 to increase/decrease also instead of just one label to increase/decrease.
The code being used for UISLider is,
#IBOutlet weak var label: UILabel!
#IBOutlet weak var slider: UISlider!
#IBAction func sizeChanged(sender: UISlider) {
let senderValue = CGFloat(sender.value)
label.font = UIFont(name: label.font.fontName, size: senderValue)
}
This code with UILabel is for viewController2 and I want to change the size of another UILabel thats in viewController1.
This is viewController1:
import UIKit
class ViewController1: ViewController {
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var scrollView1: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView1.contentSize.height = 5000
scrollView1.contentSize.width = 375
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
This is viewController2:
#IBOutlet weak var label: UILabel!
#IBOutlet weak var slider: UISlider!
#IBAction func sizeChanged(sender: UISlider) {
let senderValue = CGFloat(sender.value)
label.font = UIFont(name: label.font.fontName, size: senderValue)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
Any help would be great.
In computer programming, when something seems difficult or complicated, you should look for ways to break it down into smaller problems that are easier to solve. Yours is a fine example. You asked:
How would I be able to increase/decrease the size of my UILabel by using UISlider which is in a different viewController?
So, what are the steps that anyone would need to perform to make this happen? There are basically three steps here:
Get a slider's value.
Send a value from one view controller to another.
Use a value to set the size of a label.
How do I get a slider's value? This is straightforward. Set the slider's target to the view controller that manages it and its action to some action in the view controller. That method will be called when the slider changes. The action will look like this:
#IBAction func sliderValueChanged(sender: UISlider) {
let value = sender.value
// do something with the value
}
How do I send data from one view controller to another? There are lots of questions on SO that cover this. Of those, Passing Data Between View Controllers is perhaps the canonical one. There are lots of options, including:
Have one view controller call a method in the other. If the view controller with the slider has a reference to the one with the label, it only has to call some method when the slider changes to pass the new value to the other controller. Or, maybe it's the view controller with the label that has a reference to the one with the slider, which is pretty typical if the slider's controller is created by the label's controller. In that case, the label's controller can call a method to retrieve the value.
Broadcast the data to anyone who is listening using notifications. When the slider changes, it's action can post a notification with the new value. Any object, including the controller with the label, can listen for that notification and act on it.
Use a proper data model. The MVC (model-view-controller) paradigm is big in Cocoa, and if your app is anything beyond trivial it should have its own data model. That model may be a reasonable place to store the slider's latest setting, and the controller with the label can read it from there when its view appears.
Stash the value somewhere. Global variables are a short path to a badly design application, but their simplicity is appealing to beginners. A better choice may be the defaults system, which at least lets the value persist when the app quits.
So, lots of options there. Forget about the slider and the label and think about how the view controllers in your app should communicate with each other. Once you figure that out, the slider setting is just one more thing that they have to say to each other. The style you choose will tell you what to put in the action method above in place of the comment.
How do I set the size of a label? It's a little unclear what you mean by setting the size. Do you want to change the font size, or the width of the label, or the width and height? In any case, there are accessors for all the properties of a label that you might want to set, so check out the docs. When the label's view controller gets a new value via one of the methods above, it should update the appropriate property of the label. You typically connect the label to an IBOutlet property in the view controller to give the controller easy access to the label.
I think, you can look at NSNotificationCenter's functionality, especially at NSNotification's userInfo:
parameter. You can pass your slider's value to userInfo from first VC and then listen to this notification in second VC.
Great example of this method in Objective-C:
https://stackoverflow.com/a/7896761

Swift: dispatch_async is effect is inconsistent for animation

I have a globalstate in my app. Depending on the state the GUI is different.
When I go from the start View A to View B I have globalstate 3
It should show an information screen, but it doesn't. BUT: When the View B has loaded only once and I jump from View C/D/E back to View B, then the code work perfectly. (You have to be in View A to get in View B.)
I use a lot dispatch_async(dispatch_get_main_queue.. that isn't good style, is it?
Why is my animation not loading at the beginning? What is good style? Thank you for answers and sorry for mistakes (english isn't my mothertongue)
override func viewDidLoad() {
super.viewDidLoad()
dispatch_async(dispatch_get_main_queue(), {
self.animateTheInformationViewWhenGlobalStateIsThree()
})
}
func animateTheInformationViewWhenGlobalStateIsThree() {
print("GLOGBALSTATE \(globalState)") //it is 3
if globalState == 3 {
setGlobalState(3)
dispatch_async(dispatch_get_main_queue(), {
GUITools.animateTheInformationView(self.tableView, animateBottomLayout: self.animationBottomConstraint, value: self.negativValue)
})
print("THE POSITIV VALUE THE NEGATIV")
}
//GUITools-Static-Class:
class func animateTheInformationView(tableView: UITableView, animateBottomLayout: NSLayoutConstraint, value: CGFloat) {
dispatch_async(dispatch_get_main_queue(), {
animateBottomLayout.constant += value
UIView.animateWithDuration(Constants.animationTime, animations: { () -> Void in
tableView.layoutIfNeeded()
},completion: {
(value: Bool) in
})
})
}
EDIT
With viewDidAppear it works. But the animation isn't a real animation. The tableView "jumps". So there is no sliding/animation.
I deleted all dispatch_async..
override func viewDidAppear(animated: Bool) {
self.animateTheInformationViewWhenGlobalStateIsSeven()
}
viewDidLoad() does not mean that your view is already visible. Since it's not visible yet you cannot apply animations to it.
viewDidLoad() is only meant to configure your view controller's view and set up your view hierarchy - i.e. to add subviews.
What you want to use is viewWillAppear() (or viewDidAppear()) to start your animation as soon as the view becomes (or became) visible.
Also all the dispatch_async calls are most likely unnecessary. You usually only need them when you are not on the main (= UI) thread. Simply remove them.

Resources