Swift - how to make inner property optional - swift

My question is in regards to optionals in swift. Let say i have the following defined already:
if let myCell = cell as? AECell {
if !myCell.someView.hidden{
//how do i use optional on someView, perhaps someView will not exists
}
}
as you can see, what if someView is nil , how do i use an optional here to only execute the if statement if someView is not nil ..i tried the question mark:
if !myCell.someView?.hidden but its syntax is not correct

if let myCell = cell as? AECell, let someView = myCell.someView {
// someView is unwrapped now
}

This should do it:
if let myCell = cell as? AECell, let myView = myCell.someView where !myView.hidden {
// This gets executed only if:
// - cell is downcast-able to AECell (-> myCell)
// - myCell.myView != nil (-> unwrapped)
// - myView.hidden == false
}

You could use optional chaining
if let myView = (cell as? AECell).someView {
if !myView.hidden{
// do something
}
}

To answer the question directly, yes you can use optionals this way. The someView property of your cell must be defined as optional.
class MyCell: UICollectionViewCell {
var someView: AECell?
}
Then you can use the following syntax:
myCell.someView?.hidden = true
The behavior you're talking about is very much akin to Objective-C's nil messaging behavior. In Swift,you want to lean more towards confirming the existence of an object before manipulating it.
guard let myView = myCell.someView as? AECell else {
// View is nil, deal with it however you need to.
return
}
myView.hidden = false

Related

UIImage returns nil on segue push

I have an image URL that needs to be parsed and displayed. The URL exists, but returns nil.
It successfully parses in the cellForRowAt function by calling cell.recipeImage.downloadImage(from: (self.tableViewDataSource[indexPath.item].image))
With this line the image displays. However, it doesn't exist when calling it in didSelectRowAt
RecipeTableViewController.swift
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let resultsVC = Storyboard.instantiateViewController(withIdentifier: "ResultsViewController") as! ResultsViewController
// Information to be passed to ResultsViewController
if (tableViewDataSource[indexPath.item] as? Recipe) != nil {
if isSearching {
resultsVC.getTitle = filteredData[indexPath.row].title
//resultsVC.imageDisplay.downloadImage(from: (self.filteredData[indexPath.row].image))
} else {
resultsVC.getTitle = tableViewDataSource[indexPath.row].title
// Parse images
resultsVC.imageDisplay.downloadImage(from: (self.tableViewDataSource[indexPath.row].image))
}
}
// Push to next view
self.navigationController?.pushViewController(resultsVC, animated: true)
}
extension UIImageView {
func downloadImage(from url: String) {
let urlRequest = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.sync {
self.image = UIImage(data: data!)
}
}
task.resume()
}
}
ResultsViewController.swift
class ResultsViewController: UIViewController {
var getTitle = String()
var getImage = String()
#IBOutlet weak var recipeDisplay: UILabel!
#IBOutlet weak var imageDisplay: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
recipeDisplay.text! = getTitle
}
...
}
Returns the error
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
From my understanding, the app is getting crashed at this line:
recipeDisplay.text! = getTitle
If it is, obviously this is not the proper way to do it. Just remove the force unwrapping because the text on the label here is nil by default. Force referencing a nil value will crash the app.
recipeDisplay.text = getTitle
UPDATED:
- Let's make sure that you wired the label and the outlets properly. Connect ti to the VC, not the File Owner.
You're calling view-related code on views that haven't been initialized yet. Remember, IBOutlets are implicitly unwrapped properties, so if you try to access them before they're initialized they'll force-unwrap and crash. So it's not that the UIImage is coming up nil, it's that recipeDisplay is nil and is getting force unwrapped.
The idiomatic iOS thing to do is to hand a view model of some sort (an object or a struct) to the view controller, and then let it do the work with that item once it has finished loading.
So, in you didSelect method, you could create your view model (which you'd need to define) and hand it off like this:
let title = filteredData[indexPath.row].title
let imageURL = self.tableViewDataSource[indexPath.row].image
let viewModel = ViewModel(title: title, imageURL: imageURL)
resultsVC.viewModel = viewModel
And then in your resultsVC, you'd do something like this:
override func viewDidLoad() {
super.viewDidLoad()
if let vm = viewModel {
recipeDisplay.text = vm.title
downloadImage(from: vm.imageURL)
}
}
So in your case all you'd need to do is hand those strings to your VC (you can wrap them up in a view model or hand them off individually) and then in that VC's viewDidLoad() that's where you'd call downloadImage(from:). That way there's no danger of calling a subview before that subview has been loaded.
One last note: Your download method should be a little safer with its use of the data and error variables, and its references to self. Remember, avoid using ! whenever you don't absolutely have to use it (use optional chaining instead), and unless you have a really good reason to do otherwise, always use [weak self] in closures.
I'd recommend doing it like this:
func downloadImage(from url: String) {
let urlRequest = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: urlRequest) { [weak self] (data,response,error) in
if let error = error {
print(error)
return
}
if let data = data {
DispatchQueue.main.sync {
self?.image = UIImage(data: data)
}
}
}
task.resume()
}
Update: Because the 'view model' concept was a little too much at once, let me explain.
A view model is just an object or struct that represents the presentation data a screen needs to be in a displayable state. It's not the name of a type defined by Apple and isn't defined anywhere in the iOS SDK. It's something you'd need to define yourself. So, in this case, I'd recommend defining it in the same fine where you're going to use it, namely in the same file as ResultsViewController.
You'd do something like this:
struct ResultsViewModel {
let title: String
let imageURL: String
}
and then on the ResultsViewController, you'd create a property like:
var viewModel: ResultsViewModel?
or if you don't like dealing with optionals, you can do:
var viewModel = ResultsViewModel(title: "", imageURL: "")
OR, you can do what you're already doing, but I'd highly recommend renaming those properties. getTitle sounds like it's doing something more besides just holding onto a value. title would be a better name. Same criticism goes for getImage, with the additional criticism that it's also misleading because it sounds like it's storing an image, but it's not. It's storing an image url. imageURL is a better name.

Why do I need to force the type using this Swift generic function?

I had some repetitive UIViewController boiler-plate scattered around that I wanted to encapsulate, so I defined this generic UIViewController extension method:
extension UIViewController {
func instantiateChildViewController<T: UIViewController>(
storyboardName: String? = nil,
identifier: String? = nil
) -> T {
let storyboard: UIStoryboard!
if let name = storyboardName {
storyboard = UIStoryboard(name: name, bundle: nil)
}
else {
storyboard = UIStoryboard(name: "\(T.self)", bundle: nil)
}
let vc: T!
if let identifier = identifier {
vc = storyboard.instantiateViewController(withIdentifier: identifier) as! T
}
else {
vc = storyboard.instantiateInitialViewController()! as! T
}
self.addChildViewController(vc)
self.view.addSubview(vc.view)
return vc
}
}
However, when I use this extension like so:
class ChildViewController: UIViewController { /*...*/ }
class ParentViewController: UIViewController {
private var childVC: ChildViewController!
//...
func setupSomeStuff() {
self.childVC = self.instantiateChildViewController() //<-- Compiler error
let vc: ChildViewController = self.instantiateChildViewController() //<-- Compiles!
self.childVC = vc
}
}
I get the compiler error Cannot assign value of UIViewController to type ChildViewController! on the line with the comment above. However, if I use an intermediate variable that I explicitly give a type to it works.
Is this a Swift bug? (Xcode 8.1) My interpretation of how generics work is that in this case T should equal the more specific ChildViewController, not the less constrained UIViewController. I get the same issue if I defined childVC as private var childVC: ChildViewController?, the only work-around I've found is the local variable, which obviously makes the extension less compelling, or to do an explicit cast like:
self.childVC = self.instantiateChildViewController() as ChildViewController
I've seen this too. I think there's some weird behavior around Optionals the compiler isn't dealing with as expected.
If you change the return value of the function to an optional value it should work without a problem.
func instantiateChildViewController<T: UIViewController>(//whateverParams) -> T!
or
func instantiateChildViewController<T: UIViewController>(//whateverParams) -> T?
Also, your childVC should be a var rather than a let if you're going to set it anyplace other than an initializer

Cast value & get property from it in one line

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

How to fix Objective C Methods SWIFT

How would I fix this problem?
Here's the code
//
// TTTImageView.swift
// TicTacToe
import UIKit
class TTTImageView: UIImageView {
var player:String?
var activated:Bool! = false
Problem is here and states "Method 'setPlayer' with objective c selector 'setPlayer:'conflicts with setter for 'player'with same objective c selector"
func setPlayer (_player:String){
self.player = _player
if activated == false{
if _player == "x"{
self.image = UIImage(named: "x")
}else{
self.image = UIImage(named: "o")
}
activated = true
}
}
}
I haven't tried anything yet but will greatly appreciate any help.
There error is explained here:
https://stackoverflow.com/a/28500768/3149796
There are lots of great Swift features that you can use instead of what you've got. Try a property observer (didSet) instead of creating a custom setter function. You can also clean up your code and make it safer with an optional binding (if let) and a where clause.
Also your activated property shouldn't be implicitly unwrapped (Bool!), but rather just Bool.
import UIKit
class TTTImageView: UIImageView {
var activated: Bool = false
var player: String? {
didSet {
if let player = self.player where self.activated == false {
if self.player == "x" {
self.image = UIImage(named: "x")
} else {
self.image = UIImage(named: "o")
}
self.activated = true
}
}
}
}
I had the same problem and I was able to fix it by using #nonobjc
More information here:
Swift 2, method 'setOn' with Objective-C selector 'setOn:' conflicts with setter for 'on' with the same Objective-C selector
change setPlayer to Player
I hope this helps

Type casting operator

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
}
}