I try to write an animation with Swift 5, following are some codes
let animations:(() -> Void) = {
self.keyboardOPT.transform = CGAffineTransform(translationX: 0,y: -deltaY)
if duration > 0 {
let options = UIView.AnimationOptions(rawValue: UInt((userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).intValue << 16))
UIView.animate(withDuration: duration, delay: 0, options: options, animations: animations, completion: nil)
} else {
animations()
}
}
But in animations: animations and animations() it shows error:
Variable used within its own initial value
You can not call itself when initializing.
You can achieve it like this also.
var animations:(() -> Void)!
animations = {
animations()
}
Related
I have a for loop as follows:
#objc private func resetValue() {
for i in stride(from: value, to: origValue, by: (value > origValue) ? -1 : 1) {
value = i
}
value = origValue
}
And when value is set it updates a label:
private var value = 1 {
didSet {
updateLabelText()
}
}
private func updateLabelText() {
guard let text = label.text else { return }
if let oldValue = Int(text) { // is of type int?
let options: UIViewAnimationOptions = (value > oldValue) ? .transitionFlipFromTop : .transitionFlipFromBottom
UIView.transition(with: label, duration: 0.5, options: options, animations: { self.label.text = "\(value)" }, completion: nil)
} else {
label.text = "\(value)"
}
}
I was hoping that if value=5 and origValue=2, then the label would flip through the numbers 5,4,3,2. However, this is not happening - any suggestions why, please?
I've tried using a delay function:
func delay(_ delay:Double, closure: #escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
and then placing the following within the stride code:
delay(2.0) { self.value = i }
However, this doesn't seem to work either.
Thanks for any help offered.
UIKit won't be able to update the label until your code is finished with the main thread, after the loop completes. Even if UIKit could update the label after each iteration of the loop, the loop is going to complete in a fraction of a second.
The result is that you only see the final value.
When you attempted to introduce the delay, you dispatched the update to the label asynchronously after 0.5 second; Because it is asynchronous, the loop doesn't wait for the 0.5 second before it continues with the next iteration. This means that all of the delayed updates will execute after 0.5 seconds but immediately one after the other, not 0.5 seconds apart. Again, the result is you only see the final value as the other values are set too briefly to be visible.
You can achieve what you want using a Timer:
func count(fromValue: Int, toValue: Int) {
let stride = fromValue > toValue ? -1 : 1
self.value = fromValue
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats:true) { [weak self] (timer) in
guard let strongSelf = self else {
return
}
strongSelf.value += stride
if strongSelf.value == toValue {
timer.invalidate()
}
}
}
I would also update the didSet to send the oldValue to your updateLabelText rather than having to try and parse the current text.
private var value = 1 {
didSet {
updateLabelText(oldValue: oldValue, value: value)
}
}
private func updateLabelText(oldValue: Int, value: Int) {
guard oldValue != value else {
self.label.text = "\(value)"
return
}
let options: UIViewAnimationOptions = (value > oldValue) ? .transitionFlipFromTop : .transitionFlipFromBottom
UIView.transition(with: label, duration: 0.5, options: options, animations: { self.label.text = "\(value)" }, completion: nil)
}
I have a strange issue with regard to entering text into a text field. I am currently using the code below. My code is modeled after the answer here.
class RocketViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, NSFetchedResultsControllerDelegate {
var offsetY:CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(RocketViewController.keyboardFrameChangeNotification(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardFrameChangeNotification(notification: Notification) {
if let userInfo = notification.userInfo {
let keyBoardFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0
let animationCurveRawValue = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIViewAnimationOptions.curveEaseInOut.rawValue)
let animationCurve = UIViewAnimationOptions(rawValue: UInt(animationCurveRawValue))
if let _ = keyBoardFrame, keyBoardFrame!.intersects(self.mainStackView.frame) {
self.offsetY = self.mainStackView.frame.maxY - keyBoardFrame!.minY
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.frame.origin.y = self.mainStackView.frame.origin.y - self.offsetY
self.rocketSelectTable.frame.origin.y = self.rocketSelectTable.frame.origin.y - self.offsetY
}, completion: nil)
} else {
if self.offsetY != 0 {
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.frame.origin.y = self.mainStackView.frame.origin.y + self.offsetY
self.rocketSelectTable.frame.origin.y = self.rocketSelectTable.frame.origin.y + self.offsetY
self.offsetY = 0
}, completion: nil)
}
}
}
}
}
In my view I have a table view with a fetched results controller as its data source, and below that are the text fields in a stack view, called mainStackView, that are eventually saved in a core data store.
I have gone through several iterations of this code with the same result, whether I compute the offset off the first responder, or simply the stack view. When a text field becomes the first responder, the view slides up nicely with the keyboard. However, as soon as I attempt to type in the field, the view snaps back to its original position. I am sure I am making a newbie mistake, but I can't figure out what I am doing wrong, and I have found nothing in my searches, except a similar question for android. Thanks in advance.
While I have not determined why I was seeing the behavior with the text field that I was seeing with changing the frame, I was able to stop the behavior by using a CGAffineTransform instead. My code is now:
#objc func keyboardFrameChangeNotification(notification: Notification) {
if let userInfo = notification.userInfo {
let keyBoardFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0
let animationCurveRawValue = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIViewAnimationOptions.curveEaseInOut.rawValue)
let animationCurve = UIViewAnimationOptions(rawValue: UInt(animationCurveRawValue))
if let _ = keyBoardFrame, keyBoardFrame!.intersects(self.mainStackView.frame) {
self.offsetY = self.mainStackView.frame.maxY - keyBoardFrame!.minY
let transformUp = CGAffineTransform.init(translationX: 0, y: (0 - self.offsetY))
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.transform = transformUp
self.rocketSelectTable.transform = transformUp
}, completion: nil)
} else {
if self.offsetY != 0 {
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.transform = CGAffineTransform.identity
self.rocketSelectTable.transform = CGAffineTransform.identity
self.offsetY = 0
}, completion: nil)
}
}
}
This code smoothly animates the movement of the views, and there is no snap back to the original position while typing in the text field. I hope this helps someone else.
I have been trying to get this animation to work for hours but to no avail. Here is a snapshot. So I have a price label that shows current price of the product, which is in a custom collectionviewcell. When the collectionView:willDisplayCell is called, I then call the below method on my custom collection view cell called animateDiscountPrice.
The animation i am trying to achieve is to add the compare_at price character by character to the end of the current price label. I tried setting the REPEAT option along with repeat count but the animation block is called only once and then the completion block is immediately called. There is no animation basically.
func animateDiscountPrice()
{
let priceText = NSMutableAttributedString(attributedString: self.priceLabel.attributedText!)
let originalPriceText = NSMutableAttributedString(attributedString: self.priceLabel.attributedText!)
let offset = priceText.length
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)) {
if let compareAtString : String = self.productData["compare_at_price_min"] as? String {
let compareAtPrice = NSMutableAttributedString(string: "$\(compareAtString)", attributes: myAttribute )
var i = 0
let count = compareAtString.characters.count+1
dispatch_async(dispatch_get_main_queue(), {
UIView.animateWithDuration(NSTimeInterval(count), delay:0, options: [.Repeat, .Autoreverse, .AllowUserInteraction, .CurveLinear],
animations: {
UIView.setAnimationRepeatCount(Float(count))
priceText.appendAttributedString(compareAtPrice.attributedSubstringFromRange(NSMakeRange(i, 1)))
priceText.addAttribute(NSForegroundColorAttributeName, value: discountColor, range: NSMakeRange(offset, i+1))
self.priceLabel.attributedText = priceText
i = i + 1
},
completion: { (finished) in
if(finished)
{
originalPriceText.appendAttributedString(compareAtPrice)
originalPriceText.addAttribute(NSForegroundColorAttributeName, value: discountColor, range: NSMakeRange(offset, compareAtPrice.length))
//self.priceLabel.attributedText = originalPriceText
}
}
)
})
}
}
}
and in the log lines I can see the animation is called only once for each cell being displayed, and calling completion methods afterwards.
In Swift I'm using UIView.animateWithDuration to animate a button and some text out of the frame. In the "completion" block, I perform a func called showQuestion which is another UIView animation. However, the first time the showQuestion function runs, the button and text that was previously hidden by the prior animation appears back in the frame, as if it had never moved.
I've checked a few questions on here, and none seem to directly address the issue.
EDIT #1
Here are the three functions.
func startQuiz(){
UIView.animateWithDuration(0.2, delay: 0.2, options: .CurveEaseIn, animations: {
self.goButton.frame.origin.y = self.view.frame.height + 5
self.languageLabel.frame.origin.y = self.view.frame.height + 5
self.label.frame.origin.y = self.view.frame.height + 5
}, completion: { finished in
self.generateFirstQuestion()
})
}
func generateFirstQuestion() {
var aBase = 0;
var qBase = 0;
if(self.lang == "fr") {
aBase = 25
qBase = 5
}
var offset = (self.currentQuestion * 5) + aBase
var question:String = self.questions[self.currentQuestion+qBase]
self.questionLabel.text = question
var answer1:String = self.answers[offset]
var answer2:String = self.answers[offset+1]
var answer3:String = self.answers[offset+2]
var answer4:String = self.answers[offset+3]
var answer5:String = self.answers[offset+4]
self.showQuestion()
}
func showQuestion(){
if(self.isAnimating){
UIView.animateWithDuration(0.2, delay: 0.2, options: .CurveEaseOut, animations: {
self.questionLabel.center.x = self.view.center.x
}, completion: { finished in
self.isAnimating = false
self.currentQuestion++
})
}
else {
var aBase = 0;
var qBase = 0;
if(self.lang == "fr") {
aBase = 25
qBase = 5
}
var offset = (self.currentQuestion * 5) + aBase
var question:String = self.questions[self.currentQuestion+qBase]
self.questionLabel.text = question
var answer1:String = self.answers[offset]
var answer2:String = self.answers[offset+1]
var answer3:String = self.answers[offset+2]
var answer4:String = self.answers[offset+3]
var answer5:String = self.answers[offset+4]
UIView.animateWithDuration(0.2, delay: 0.2, options: .CurveEaseIn, animations: {
self.questionLabel.frame.origin.x = 0-700
}, completion: { finished in
self.questionLabel.frame.origin.x = self.view.frame.width+5
self.isAnimating = true
self.showQuestion()
})
}
}
I keep getting an Cannot convert expression's type '[NSObject : AnyObject]?' to 'NSDictionary' error and I don't know what to do. I tried everything, looked everywhere. Can you please help? I am creating a custom keyboard in SWIFT and I am totally new at this so i could definitely use the help.
// Called when `UIKeyboardWillShowNotification` is sent.
func keyboardWillShow(aNotification: NSNotification) {
let info = aNotification.userInfo as NSDictionary **<<<<<<<<<ERROR HERE>>>>>>>**
let sizeBegin = info.objectForKey(UIKeyboardFrameBeginUserInfoKey).CGRectValue().size
let sizeEnd = info.objectForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue().size
let duration = info.objectForKey(UIKeyboardAnimationDurationUserInfoKey).doubleValue
let curve = info.objectForKey(UIKeyboardAnimationCurveUserInfoKey).integerValue
var animationCurve: UIViewAnimationCurve
if let value = UIViewAnimationCurve.fromRaw(curve) {
animationCurve = value
} else {
animationCurve = UIViewAnimationCurve.EaseInOut
}
let insets = UIEdgeInsets(top: 44, left: 0, bottom: sizeEnd.height, right: 0)
UIView.animateWithDuration(duration, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.textView.contentInset = insets
self.textView.scrollIndicatorInsets = insets
}, completion: nil)
}
// Called when `UIKeyboardWillHideNotification` is sent.
func keyboardWillHide(aNotification: NSNotification) {
let info = aNotification.userInfo as NSDictionary **<<<<<<<<<<ERROR HERE>>>>>>**
let sizeBegin = info.objectForKey(UIKeyboardFrameBeginUserInfoKey).CGRectValue().size
let sizeEnd = info.objectForKey(UIKeyboardFrameEndUserInfoKey).CGRectValue().size
let duration = info.objectForKey(UIKeyboardAnimationDurationUserInfoKey).doubleValue
let curve = info.objectForKey(UIKeyboardAnimationCurveUserInfoKey).integerValue
var animationCurve: UIViewAnimationCurve
if let value = UIViewAnimationCurve.fromRaw(curve) {
animationCurve = value
} else {
animationCurve = UIViewAnimationCurve.EaseInOut
}
let insets = UIEdgeInsets(top: 44, left: 0, bottom: sizeEnd.height, right: 0)
UIView.animateWithDuration(duration, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.textView.contentInset = insets
self.textView.scrollIndicatorInsets = insets
}, completion: nil)
}
Your dictionary is of type [NSObject:AnyObject]? which is an Optional type that must be unwrapped to be used. Since it is an Optional, it could be nil. One safe way of dealing with this is to use the nil coalescing operator ?? to unwrap it:
let info = (aNotification.userInfo ?? [:]) as NSDictionary
That will either unwrap your dictionary if it is not nil or give you a new empty dictionary if it is nil.