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.
Related
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()
}
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 am converting my app to Swift 3 at the moment and I have problems with this function I used to show the keyboard before.
Initializer for conditional binding must have Optional type, not 'CGFloat'
The error appears in the third line.
It's been a while since I've programmed the last time and so I am not sure how to solve this.
func keyboardWillShow(_ sender: Notification) {
if let userInfo = sender.userInfo {
if let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue.size.height {
let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue
let edgeInsets = UIEdgeInsetsMake(0, 0, keyboardHeight, 0)
UIView.animate(withDuration: duration!, animations: { () -> Void in
self.tableView.contentInset = edgeInsets
self.tableView.scrollIndicatorInsets = edgeInsets
self.view.layoutIfNeeded()
})
}
}
}
This is one of those rare situations where I recommend force-unwrapping. You know the userInfo contains this information, and you are hosed if it doesn't. Moreover, there is no need to pass through AnyObject or to call cgRectValue; you can cast all the way down to a CGRect in a single move. So I would write:
let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! CGRect).size.height
(Note that there is no if, because we are not doing a conditional binding; we simply cast, kaboom.)
[Note too that there is no need now to fetch the duration or to call animate or layoutIfNeeded; you can throw all of that away. We are already in an animation, and your changes to the contentInset and scrollIndicatorInsets will be animated in time to the keyboard.]
change
if let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue.size.height {
to
if let foo = userInfo[UIKeyboardFrameEndUserInfoKey] {
let keyboardHeight = (foo as as AnyObject).cgRectValue.size.height
--- UPDATE ---
or
if let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as AnyObject?).cgRectValue.size.height {
AnyObject? works because AnyObject can contain Optional itself. So you have to casting to Optional explicitly. Either AnyObject? or Optional<AnyObject> will work.
From research on StackOverflow I've learned that this error is caused by attempting to bind a type that isn't an optional however it doesn't make sense in this situation because it used guard instead. Here's my code:
func animateTransition(_ transitionContext: UIViewControllerContextTransitioning) {
// Here, we perform the animations necessary for the transition
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextFromViewControllerKey) else { return }
let fromView = fromVC.view
guard let toVC = transitionContext.viewController(forKey: UITransitionContextToViewControllerKey) else { return }
let toView = toVC.view
guard let containerView = transitionContext.containerView() else { return }
if self.presenting {
containerView.addSubview(toView)
}
let animatingVC = self.presenting ? toVC : fromVC
let animatingView = animatingVC.view
let appearedFrame = transitionContext.finalFrame(for: animatingVC)
var alpha: CGFloat = 1
if self.options.contains([.AlphaChange]) {
alpha = 0;
}
let initialAlpha = self.presenting ? alpha : 1
let finalAlpha = self.presenting ? 1: alpha
var dismissedFrame = appearedFrame
let startRect = CGRect(origin: appearedFrame.origin, size: containerView.bounds.size)
let offset = self.calculateStartPointOffset(startRect, options: self.options)
if options.contains([.Dissolve]) && !self.presenting {
dismissedFrame.size = containerView.bounds.size
dismissedFrame.origin = CGPointZero
} else {
dismissedFrame = CGRect(x: offset.x, y: offset.y, width: appearedFrame.width, height: appearedFrame.height)
}
let initialFrame = self.presenting ? dismissedFrame : appearedFrame
let finalFrame = self.presenting ? appearedFrame : dismissedFrame
animatingView?.frame = initialFrame
animatingView?.alpha = initialAlpha
let dumpingValue = CGFloat(self.options.contains([.Interactive]) ? 1 : 0.8)
UIView.animate(withDuration: self.transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: dumpingValue, initialSpringVelocity: 0.2, options: [UIViewAnimationOptions.allowUserInteraction, UIViewAnimationOptions.beginFromCurrentState],
animations:
{ () -> Void in
animatingView?.frame = finalFrame
animatingView?.alpha = finalAlpha
})
{ (completed) -> Void in
if !self.presenting {
fromView?.removeFromSuperview()
self.tDelegate?.didDissmisedPresentedViewController()
}
let cancelled = transitionContext.transitionWasCancelled()
transitionContext.completeTransition(!cancelled)
}
}
Xcode show an error on this line:
guard let containerView = transitionContext.containerView() else { return }
transitionContext.containerView() was changed to return a non-optional, so you can't use it to initialize a variable in a conditional binding like a guard or if let.
You should remove the guard from that line:
let containerView = transitionContext.containerView()
The container view in which a presentation occurs. It is an ancestor of both the presenting and presented view controller's views.
This containerView is being passed to the animation controller and It's return a non-optional
NOTE:
if let/if var optional binding only works when the result of the right side of the expression is an optional. If the result of the right side is not an optional, you can not use this optional binding. The point of this optional binding is to check for nil and only use the variable if it's non-nil.
I have some problems with my version of this loadingOverlay singleton.
What's supposed to happen, is it comes onto the screen, with a view and a label that has the text, "Loading, please wait." or something like that. then if loading is longer than 2 seconds (i've changed it to 10 for debugging) the text changes to a random cute phrase.
first of all the animation that should change the text doesn't seem to happen. instead, the text just instantly changes.
more importantly, If, for some reason, my asynchronous call block is executed multiple times, I only want the most recent call to it to run, and I want the previous instances of it to terminate before running.
I was reading about callbacks and promises, which look promising. Is that a swifty pattern to follow?
by the way, as I'm learning swift and iOS, I've been experimenting, and I tried [unowned self] and now i'm experimenting with [weak self], but I'm not really certain which is most appropriate here.
// from http://stackoverflow.com/questions/33064908/adding-removing-a-view-overlay-in-swift/33064946#33064946
import UIKit
class LoadingOverlay{
static let sharedInstance = LoadingOverlay()
//above swifty singleton syntax from http://krakendev.io/blog/the-right-way-to-write-a-singleton
var overlayView = UIView()
var spring: CASpringAnimation!
var springAway: CASpringAnimation!
var hidden = false
private init() {} //This line prevents others from using the default () initializer for this class
func setupSpringAnimation(startY: CGFloat, finishY: CGFloat) {
overlayView.layer.position.y = startY
spring = CASpringAnimation(keyPath: "position.y")
spring.damping = 10
spring.fromValue = startY
spring.toValue = finishY
spring.duration = 1.0
spring.fillMode = kCAFillModeBackwards
}
func showOverlay() {
print("show overlay")
overlayView.alpha = 1
hidden = false
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate,
let window = appDelegate.window {
setupSpringAnimation(-window.frame.height / 2, finishY: window.frame.height / 2)
let overlayViewFramesize = 0.65 * min(window.frame.height, window.frame.width)
overlayView.frame = CGRectMake(0, 0, overlayViewFramesize, overlayViewFramesize)
overlayView.center = window.center
overlayView.backgroundColor = UIColor.greenColor()
overlayView.clipsToBounds = true
overlayView.layer.cornerRadius = overlayViewFramesize / 8
let label = UILabel(frame: CGRectMake(0,0,overlayViewFramesize * 0.8 , overlayViewFramesize))
label.text = " \nLoading, please wait\n "
label.tag = 12
overlayView.addSubview(label)
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.numberOfLines = 0 //as many as needed
label.sizeToFit()
label.textAlignment = NSTextAlignment.Center
label.center = CGPointMake(overlayViewFramesize / 2, overlayViewFramesize / 2)
overlayView.bringSubviewToFront(label)
window.addSubview(overlayView)
overlayView.layer.addAnimation(spring, forKey: nil)
RunAfterDelay(10.0) {
if self.hidden == true { return }
//strongSelf boilerplate code technique from https://www.raywenderlich.com/133102/swift-style-guide-april-2016-update?utm_source=raywenderlich.com+Weekly&utm_campaign=ea47726fdd-raywenderlich_com_Weekly4_26_2016&utm_medium=email&utm_term=0_83b6edc87f-ea47726fdd-415681129
UIView.animateWithDuration(2, delay: 0, options: [UIViewAnimationOptions.CurveEaseInOut, UIViewAnimationOptions.BeginFromCurrentState, UIViewAnimationOptions.TransitionCrossDissolve], animations: { [weak self] in
guard let strongSelf = self else { return }
(strongSelf.overlayView.viewWithTag(12) as! UILabel).text = randomPhrase()
(strongSelf.overlayView.viewWithTag(12) as! UILabel).sizeToFit()
print ((strongSelf.overlayView.viewWithTag(12) as! UILabel).bounds.width)
(strongSelf.overlayView.viewWithTag(12) as! UILabel).center = CGPointMake(overlayViewFramesize / 2, overlayViewFramesize / 2)
}, completion: { (finished: Bool)in
print ("animation to change label occured")})
}
}
}
func hideOverlayView() {
hidden = true
UIView.animateWithDuration(1.0, delay: 0.0, options: [UIViewAnimationOptions.BeginFromCurrentState], animations: { [unowned self] in
//I know this is clunky... what's the right way?
(self.overlayView.viewWithTag(12) as! UILabel).text = ""
self.overlayView.alpha = 0
}) { [unowned self] _ in
//I know this is clunky. what's the right way?
for view in self.overlayView.subviews {
view.removeFromSuperview()
}
self.overlayView.removeFromSuperview()
print("overlayView after removing:", self.overlayView.description)
}
//here i have to deinitialize stuff to prepare for the next use
}
deinit {
print("Loading Overlay deinit")
}
}
What I basically wanted, was to be able to delay a block of code, and possibly cancel it before it executes. I found the answer here:
GCD and Delayed Invoking