Problem: Upon converting to Swift 2 I get the following error: "Value of optional type [NSIndexPath]? not unwrapped, did you mean to use "!" or "?"". The issue is that if I use '?', it gives an error saying I should use '!', and if I use '!' it gives an error saying I should use '?'. Thus it creates this nasty little bug loop that seems to be unfixable.
Code:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if identifier == Constants.SegueIdentifier {
if let selectedRowIndex = collectionView?.indexPathsForSelectedItems().last as? NSIndexPath {
if let cell = collectionView?.cellForItemAtIndexPath(selectedRowIndex) {
//We check if the selected Card is the one in the middle to open the chat. If it's not, we scroll to the side card selected.
if cell.frame.size.height > cell.bounds.size.height {
return true
} else {
collectionView?.scrollToItemAtIndexPath(selectedRowIndex, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)
return false
}
}
}
}
return true
}
I haven't been able to come up with any work arounds since it seems like I need to somehow unwrap it. Has anyone seen this problem?
indexPathsForSelectedItems() returns [NSIndexPath]? (optional), you have to add another question mark for optional chaining and remove as? NSIndexPath as the compiler knows the unwrapped type.
if let selectedRowIndex = collectionView?.indexPathsForSelectedItems()?.last {
Solution:
if let selectedRowIndex = collectionView!.indexPathsForSelectedItems()!.last! as? NSIndexPath
My only concern is type safety but it works in my project as it currently is.
Related
I've encountered a problem I can't solve myself. I have tried the Internet without any luck.
I'm still pretty new to Swift and coding, and right now following a guide helping me create an app.
Unfortunately, as I can understand, the app was written for Swift 3, and is giving me some issues since I'm using Swift 4.
I have to lines that gives me this warning:
String interpolation produces a debug description for an optional value; did you mean to make this explicit?
Use 'String(describing:)' to silence this warning Fix
Provide a default value to avoid this warning Fix
However, when I click one of Xcode's solutions I get another problem.
If I use the first fix, the app crashes and I get the following message:
Thread 1: Fatal error: Unexpected Segue Identifier;
If I use the second fix I have to assign a default value. And I don't know what this should be.
The whole passage of code is as follows.
It's the line starting with guard let selectedMealCell and the last one after default: that is causing the issues.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "AddItem":
os_log("Adding a new meal.", log: OSLog.default, type: .debug)
case "ShowDetail":
guard let mealDetailViewController = segue.destination as? MealViewController else {
fatalError("Unexpected destination: \(segue.destination)")
}
guard let selectedMealCell = sender as? MealTableViewCell else {
fatalError("Unexpected sender: \(sender)")
}
guard let indexPath = tableView.indexPath(for: selectedMealCell) else {
fatalError("The selected cell is not being displayed by the table")
}
let selectedMeal = meals[indexPath.row]
mealDetailViewController.meal = selectedMeal
default:
fatalError("Unexpected Segue Identifier; \(segue.identifier)")
}
}
So, the first suggested fix worked for you. It quieted the compile time warning, although admittedly String(describing:) is a weak solution.
In both cases, you need to unwrap the optional value. For the first case you should use:
guard let selectedMealCell = sender as? MealTableViewCell else {
if let sender = sender {
fatalError("Unexpected sender: \(sender))")
} else {
fatalError("sender is nil")
}
}
and in the second case:
fatalError("Unexpected Segue Identifier; \(segue.identifier ?? "")")
Then you got a runtime error:
"Unexpected Segue Identifier;"
That is telling you that your switch didn't match the first 2 cases and it ran the default case. The crash is caused because your code is explicitly calling fatalError. Your segue.identifier is apparently an empty string.
So your problem is actually in your Storyboard. You need to assign identifiers to your segues. Click on the segue arrows between your view controllers, and assign identifiers "AddItem" and "ShowDetail" to the proper segues. The segue identifier is assigned in the Attributes Inspector on the right in Xcode.
If you are prepared to write an small extension to Optional, it can make the business of inserting the value of an optional variable less painful, and avoid having to write optionalVar ?? "" repeatedly:
Given:
extension Optional: CustomStringConvertible {
public var description: String {
switch self {
case .some(let wrappedValue):
return "\(wrappedValue)"
default:
return "<nil>"
}
}
}
Then you can write:
var optionalWithValue: String? = "Maybe"
var optionalWithoutValue: String?
print("optionalWithValue is \(optionalWithValue.description)")
print("optionalWithoutValue is \(optionalWithoutValue.description)")
which gives:
optionalWithValue is Maybe
optionalWithoutValue is <nil>
You can also write print("value is \(anOptionalVariable)") -- the .description is redundant since print() uses CustomStringConvertible.description anyway -- but although it works you still get the annoying compiler warning.
You can use the following to automatically produce "nil" (or any other String) for nil values and for non-nil values use the description provided by CustomStringConvertible
extension String.StringInterpolation {
mutating func appendInterpolation<T: CustomStringConvertible>(_ value: T?) {
appendInterpolation(value ?? "nil" as CustomStringConvertible)
}
}
For your own types you have to conform to CustomStringConvertible for this to work:
class MyClass: CustomStringConvertible {
var description: String {
return "Whatever you want to print when you use MyClass in a string"
}
}
With this set up, you can simply use your optionals the same way as any other type, without any compiler warnings.
var myClass: MyClass?
myClass = MyClass()
print("myClass is \(myClass)")
This code bothers me. Below, I'm trying to find the first instance of a specific type of ViewController in a NavigationController's stack. Simple. But when I've found it, I have to then cast it to the type I just looked for, which seems redundant to me.
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
guard let foundViewController = viewControllers.first(where: { $0 is T }) as? T else {
return nil
}
self.popToViewController(foundViewController, animated:animated)
return foundViewController
}
Only thing I can think of is this...
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
guard let foundViewController = viewControllers.flatMap({ $0 as? T }).first() else {
return nil
}
self.popToViewController(foundViewController, animated:animated)
return foundViewController
}
...but I've repeatedly found using flatMap like this tends to confuse people reading the code, and, as correctly pointed out in the comments below, iterates over the entire collection whereas first doesn't do that.
So is there another way to solve this issue?
You can use case patterns to select the viewControllers of the type you are interested in and pop and return the first one you find:
extension UINavigationController {
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
for case let vc as T in viewControllers {
self.popToViewController(vc, animated: animated)
return vc
}
return nil
}
}
Example:
Use a button in OrangeViewController to return to GreenViewController earlier in the stack:
#IBAction func popToGreen(_ sender: UIButton) {
let greenVC = self.navigationController?.popToFirstViewController(
ofType: GreenViewController.self,
animated: true
)
// Modify a property in GreenViewController that
// will be moved into a label in viewWillAppear
greenVC?.labelText = "Returned here from Orange"
}
popToLastViewController(ofType:animated:)
You might also want a function to pop to the most recent viewController of a type. That is easily achieved with a simple modification (adding .reversed()):
func popToLastViewController<T:UIViewController>(ofType type:T.Type, animated: Bool) -> T? {
for case let vc as T in viewControllers.reversed() {
self.popToViewController(vc, animated: animated)
return vc
}
return nil
}
I'm in favor of combining flatMap and lazy to get the behavior of conditionally casting to T, stripping out mismatches, and not enumerating the whole array:
func popToFirstViewController<T:UIViewController>(ofType type:T.Type, animated:Bool) -> T? {
guard let foundViewController = viewControllers.lazy.flatMap({ $0 as? T }).first {
return nil
}
self.popToViewController(foundViewController, animated:animated)
return foundViewController
}
As for "confusing people that read the code:" flatMap is fairly idiomatic Swift, and will be less ambiguous with the upcoming rename to compactMap. If readers in your environment really have trouble, you could always write a small helper (generic or not) that performs the same work under a clearer name.
I am implementing this drop down menu from cocoaPod. It is pretty easy to implement and I got it to work.
https://github.com/PhamBaTho/BTNavigationDropdownMenu
However, as per instruction, I have implemented the following functions in viewDidLoad
self.navigationItem.titleView = menuView
menuView.didSelectItemAtIndexHandler = {[weak self] (indexPath: Int) -> () in
print("Did select item at index: \(indexPath)")
if indexPath == 0 {
print("Closest")
self?.sortByDistance()
} else if indexPath == 1 {
print("Popular")
self?.sortByRatings()
} else if indexPath == 2 {
print("My Posts")
self?.myPosts()
} else {
}
I am abit concerned as Xcode is telling me to put a ? or a ! just after self which was never done in other places of my program. Could someone please advise if this is totally acceptable or is there a better way of doing it? It just seems odd force unwrapping or putting my VC as optional...?
The whole point of {[weak self] ... is that the controller may be released and you don't want this block to strongly capture it and keep it in memory if it has been released by whatever presented it. As such the reference to self may be nil.
So, you definitely don't want to use !, and you should either user ? or and if let check.
I'd like to write following lines into one line statement:
var myBool = false
if let myButton = myView.subView.button as? MyButton {
myBool = !myButton.isValid
}
Is it possible to do it this way, that I have everything in short statement which will also return false if myButton is not the type of MyButton?
If you really want it on one line, you can do something like this:
let myBool = !((myView.subView.button as? MyButton)?.isValid ?? true)
This is the most concise way I've been able to come up with that has the expected behavior you outlined. However, it's far from easy to read.
The clear reason to want a single line approach however is perhaps so that myBool can be declared as a constant with let, right?
The alternative here could be a method:
func isValidButton(testView: UIView) -> Bool {
guard let button = testView as? MyButton else {
return false
}
return button.isValid
}
So this is multiple lines, but it's easier for a human to read what is going on here. And in the calling place, it's still one line and allows for the let declaration of your boolean variable:
let myBool = !isValidButton(myView.subView.button)
And keep in mind, this doesn't even have to be a method on the class. If you need it in just one spot, it can be a closure with local scope.
func viewDidLoad() {
super.viewDidLoad()
let isValidButton = { (testView: UIView) -> Bool in
guard let button = testView as? MyButton else {
return false
}
return button.isValid
}
let myBool = !isValidButton(myView.subView.button)
// do some things
}
As a note here, I've only assuming that your MyButton inherits from UIView and that UIView is perhaps what myView.subView.button is declared as returning. Realistically, your isValidButton() closure should take an argument of whatever type myView.subView.button returns (maybe it's UIButton) and presumably, that type is either a parent of MyButton or it is a protocol which MyButton conforms to.
Another option for a 1-liner is to use the nil coalescing operator along with map to apply the negation:
let myBool = ((myView.subView.button as? MyButton)?.isValid).map{!$0} ?? false
In Swift guide that as published on ibooks, as! operator was not mentioned. But in online reference and in some example code, they (i mean Apple in both cases) used as! operator.
Is there a difference between as and as! operators? If there are, can you explain please?
edit: Im so tired that i wrongly typed "is", instead of "as". That is now corrected...
as? will do an optional downcast - meaning if it fails it will return nil
so "Blah" as? Int will return Int? and will be a nil value if it fails or an Int if it does not.
as! forces the downcast attempt and will throw an exception if the cast fails. Generally you will want to favour the as? downcast
//ex optional as?
let nine = "9"
if let attemptedNumber = nine as? Int {
println("It converted to an Int")
}
//ex as!
let notNumber = "foo"
let badAttempt = notNumber as! Int // crash!
( You may find that you that an update is sitting there for the swift guide. It is mentioned for sure in the online version https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html )
operator is the forcefully unwrapped optional form of the as? operator. As with any force unwrapping though, these risk runtime errors that will crash your app should the unwrapping not succeed.
Further, We should use as to upcast if you wish to not write the type on the left side, but it is probably best practice to write it with normal typing as shown above for upcasting.
Example:
You use the as keyword to cast data types. UIWindow rootViewController is of type UIViewController. You downcast it to UISplitViewController.
Another better example can be taken as follows.
var shouldBeButton: UIView = UIButton()
var myButton: UIButton = shouldBeButton as UIButton
The as? operator returns an optional, and then we use optional binding to assign it to a temporary constant, and then use that in the if condition, like we are doing in the below example.
let myControlArray = [UILabel(), UIButton(), UIDatePicker()]
for item in myControlArray
{
if let myLabel = item as? UILabel
{
var storeText = myLabel.text
}
else if let someDatePicker = item as? UIDatePicker
{
var storeDate = someDatePicker.date
}
}