can't understand what's wrong here. I want to set some instance variables for view controller:
private func simpleViewControllerAtIndex(index: Int) -> UIViewController! {
let controller: SimpleTutorialController = storyboard?.instantiateViewControllerWithIdentifier("SimpleTutorialController") as! SimpleTutorialController
let mainText = "simple_main_\(index + 1)".localized
let detailText = "simple_detail_\(index + 1)".localized
print(mainText)
print(detailText)
controller.mainText? = mainText
controller.detailText? = detailText
print(controller.mainText)
print(controller.detailText)
return controller
}
and in logs I see something strange:
Aprendizagem
Palavra
nil
nil
How to solve the issue?
I'm sure you mean assigning the text variables without the question marks like this:
controller.mainText = mainText
controller.detailText = detailText
If you have the question marks the assignment only succeeds when the variable, in this case mainText, is not nil. It failed just because it was nil and thus stayed nil.
Change controller.mainText? to controller.mainText and similar for controller.detailText?.
When referring to optional variables, you don't need the ? when accessing them. You can use it when the variable is already set, but it is still not necessary.
Related
I am coming from reading code from a ton of StackOverFlow posts for what seems to be a rather simple procedure, but when I try to implement what I've learned in my own code it fails.
In my "ViewController" didSelectRowAt function, I initialized the user defaults
let song = tableViewData[indexPath.section].songData[indexPath.row - 1]
let songImage = song.artwork
UDM.shared.defaults.setValue(song.title, forKey: "name")
UDM.shared.songCover.setValue(songImage, forKey: "cover")
Then created a class to hold the UserDefaults
class UDM{
static let shared = UDM()
//let defaults = UserDefaults(suiteName: com.CTMVenturesInc.MusicTesters.saved.data)
let defaults = UserDefaults()
let songCover = UserDefaults()
//other funcs
}
Following that in my "TealViewController" I created the label & image element
#IBOutlet var label: UILabel!
#IBOutlet var coverImage: UIImageView!
Lastly in the viewDidLoad of TealViewController I set the values
if let value = UDM.shared.defaults.value(forKey: "name") as? String{
label.text = value
}
if let value = UDM.shared.songCover.value(forKey: "cover") as? MPMediaItemArtwork{
coverImage.image = value.image(at: CGSize(width: 400, height: 400))
}
This runs and works perfectly with just the text default, but when I try to include the image code I get this run error
Thread 1: "Attempt to insert non-property list object <MPConcreteMediaItemArtwork: 0x2830c4c80> for key cover"
So I found this post Save images in NSUserDefaults? and tried to implement in my view controller replacing this
UDM.shared.songCover.setValue(songImage, forKey: "cover")
with this
[UDM.shared.songCover.set: UIImagePNGRepresentation(songImage) forKey: "cover"]
But get a cannot find forKey error. I looked at this post How to pass UI image from one view controller to another? and tried this
UDM.shared.songCover.standardUserDefaults().setObject(UIImagePNGRepresentation(songImage), forKey:"cover")}
and got even more errors
What is it that I am not putting together here?
problem 1
The only things you can save into user defaults are NSString, NSData, NSArray, and NSDictionary (or Swift classes that are bridged to them).
MPMediaItemArtwork is not one of those; neither is UIImage.
To save something into user defaults that is not one of those, you must archive it somehow, meaning convert it (serialize it) into an NSData. You are not going to be able to do that with MPMediaItemArtwork, so you need to come up with another strategy for saving whatever it is that is important to you about this object.
Just to give an example (which seemed to be where you were heading), a UIImage derived from the MPMediaItemArtwork can be archived to an NSData, and now you can put it into user defaults.
However, that's not a very good way of saving an image; it would be better to save its data directly as a file to disk.
problem 2
User defaults is not itself some deep object where you can write into the properties of objects it already contains and your architecture of having two different properties both of which are merely instantiations of UserDefaults is very weird. Instead of passing through a second object, just make a piece of data of the right type, and call UserDefaults.standard.set... to set it by a key.
I've set up an if statement which does things depending on an int value stored in a UILabel, however, my code can't determine the value of the int inside the label.
This is because the if statement runs within the viewdidload function. I'm basically looking for how I can pass the label value to my view. I'm struggling to make it work.
Here's the code:
override func viewDidLoad()
{
// If statement
var NumberRead = Int(Number.text!)
if NumberRead! <= 2 {
Picture.image = Pic1
} else {
Picture.image = Pic2
}
}
#IBOutlet weak var Number: UILabel!
Any suggestions for better ways to handle this would be amazing.
Thanks
I don't think the problem is that you use this code in viewDidLoad, but that your UILabel is empty when you run the code and the code is unwrapping a nil value. You have to set the UILabel Number to a value before you use the if statement.
I don't know what value you expect in the Number label, but either set it with an initial value, e.g. 0, or when this view controller is called through segueing from another view controller pass on the value the label should have. The code should then work fine.
How about:
set the default image to pic2, then use optional binding to check the value
Picture.image = Pic2
if let numTxt = Number.text {
if let num = Int(numTxt) {
if num <= 2 {
Picture.image = Pic1
}
}
}
You should separate your data and logic from the view. It is absurd to put a value into a label and try to interpret it. Please consider reading up on the MVC (model view controller) pattern which is the underpinning of iOS.
Your view controller should have a variable, say number to keep track of the value that determines which image to show.
var number: Int = 0 {
didSet {
imageView.image = number <= 2 ? pic1 : pic2
label.text = "\(number)"
}
}
On naming
Your variable names are also a mess. By convention variables are lowerCamelCase. If you are naming an UIImageView, the name picture is too generic and could be misleading. Also avoid names like pic1, pic2, better is something like activeImage, inactiveImage.
Let's say we have two instances of UILabel that, to us, are equivalent:
let label1 = UILabel()
label1.text = "Hello world"
let label2 = UILabel()
label2.text = "Hello world"
Let's say the two instances are in an array of type [UIView]:
let views: [UIView] = [label1, label2]
Is there any way to perform an equality check that would find these two instances of UIView to be equivalent without knowing which type they are ahead of time (and therefore which common properties to compare on)?
(Any way to use the fact that these instances are both have a dynamicType of UILabel and dynamically run through the key/value properties of the UILabel class and compare each value that can be compared?)
What's happening:
label1 == label2 // false
views[0] == views[1] // false
What's desired:
areTheSame(label1, label2) // true
areTheSame(views[0], views[1]) // true
We are looking for a way to compare two separate instances, so we can't use ===.
Swift has no reflection so this is not possible. We can't even get a list of attributes.
Also note that for many types there is no definition of equality. Even comparing two floating point values is a problem.
Exactly for this reason we have the Equatable protocol. If you want to compare types, define equality on them. That equality can then go as deep as needed, without the need for any dynamic (unsafe) behavior.
Just to explain another corner case, for example, on UILabel there are some properties that you definitely don't want to compare, namely things like nextResponder or superview. Trying to deep compare those properties would actually end up in a loop. Usually it's not possible to compare two objects for equality without knowing exactly what should and what should not be compared.
Something like this should work:
let views: [UIView] = [label1, label2]
func areTheSameLabel(view1: UIView, _ view2: UIView) -> Bool {
guard let label1 = view1 as? UILabel,
let label2 = view2 as? UILabel else { return false }
return label1.text = label2.text
}
print(areTheSameLabel(label1, label2)) // Should print "true"
print(areTheSameLabel(views[0], views[1])) // Should print "true"
In response to your comments, I think the best avenue is to create a protocol:
protocol ViewEquatable {
var backgroundColor: UIColor? { get set }
var text: String? { get set }
// add any other properties you want to compare here...
}
Then write a function to compare them:
func ==<T: ViewComparable>(lhs: T, rhs: T) -> Bool {
return (lhs.backgroundColor == rhs.backgroundColor) &&
(lhs.text == rhs.text) &&
// whatever other comparison tests you need go here...
}
Something like that is probably your best option, though your requirements are too vague to give a complete answer...
In my application I'm using the as operator to check what type of UI element I'm working with. I ran into an issue where UILabel is successfully checking as a view.
let label = UILabel()
label.text = "some text"
if let myLabel = label as? UIView {
print("its a view ") // succeeds
}
I also receive the warning that states:
warning: Conditional cast from 'UILabel' to 'UIView' always succeeds.
Question
Is there a way to add a constraint to this as check that will cause this check to fail as expected?
Try this:
if label.isMemberOfClass(UIView.self) {
print("its a view ") // succeeds
}
isMemberOfClass checks for UIView and no classes that inherit from it
Here's another approach, which is the one I needed for my particular situation as I was attempting to check a generic (T). Since I didn't specify that in the question, I'll won't accept this as the answer, but here is the solution:
if let label = obj as? UILabel where label.isMemberOfClass(UILabel.self) {
// ...
}
if let view = obj as? UIView where view.isMemberOfClass(UIView.self) {
// ...
}
I've run into an odd case when trying to check a return value, and I'm wondering how to do this "properly", in the Swift sense.
I have an NSStatusItem (named item), and I'm trying to assign the NSStatusItem an NSImage. When I make the NSImage, since I pass it a string value for the image name, I want to make sure that the NSImage is actually valid (what if I mistype the image name string?).
The first thing I tried was this:
if let image: NSImage? = NSImage(named: "CorrectIconName") {
item.image = image
}
But this gives the error "Bound value in a conditional binding must be of Optional type". I thought that saying image: NSImage? made it clear that it was Optional, but I guess not.
I changed that to this:
let image: NSImage? = NSImage(named: "CorrectIconName")
if image {
item.image = image
}
Which works totally fine. But I don't get why this works, while the first example doesn't. It seems more-or-less to be the exact same thing. And since the first one didn't compile, I thought I'd try some other routes...
Since NSImage(named:) does return an NSImage and not an NSImage?, I thought I'd see what happened if I assigned the return value of the constructor directly to item:
item.image = NSImage(named: "CorrectIconName")
Which works, but doesn't allow for the error checking I want to do. If the string is wrong, the NSStatusItem gets nil for an image, which leads to me having an invisible status bar item.
Next, I tried this:
let image: NSImage = NSImage(named: "CorrectIconName")
if image {
item.image = image
}
But this gives the error "Type 'NSImage' does not confirm to protocol 'LogicValue'", which I guess means you aren't allowed to check if it's nil or not with an if statement.
However, you can check whether it is nil by doing the following:
let image: NSImage = NSImage(named: "CorrectIconName")
if image != nil {
item.image = image
}
So, here's the question: how exactly is one supposed to check a return value if it isn't Optional?
It actually is Optional, the compiler just isn't showing it to you.
In Apple's documentation about working with Objective-C objects, it says that all objects imported from Objective-C APIs are actually implicitly unwrapped optionals (like we would manually declare with !):
In some cases, you might be absolutely certain that an Objective-C
method or property never returns a nil object reference. To make
objects in this special scenario more convenient to work with, Swift
imports object types as implicitly unwrapped optionals. Implicitly
unwrapped optional types include all of the safety features of
optional types. In addition, you can access the value directly without
checking for nil or unwrapping it yourself. [source]
Unfortunately, the compiler/syntax checker doesn't treat them as such. Therefore, the correct way of checking would be to declare image as the type that the NSImage initializer is actually returning, an implicitly unwrapped optional NSImage:
let image: NSImage! = NSImage(named: "CorrectIconName")
if image {
// do something
}
Alternate method (via #vacawama):
if let image = NSImage(named: "CorrectIconName") as NSImage! {
// do something
}
There is a bit of a mismatch between Objective-C initializers which may return nil and Swift's init semantics. If you invoke SomeObject(...) it's defined by the language to create an instance of SomeObject NOT SomeObject?
There are a few examples of initializers in the Cocoa/Foundation frameworks that, from a Swift perspective, generate an NSSomething? not the implied NSSomething
There are myriad way for Apple to address this without compromising the Swift type system, but until they do, there are several ways for you to "Do the Right Thing."
Re-wrap the object in a conditional - this works but is clunky:
var maybeImage: NSImage? = NSImage(named: "CorrectIconName")
if let image = maybeImage {
}
Better is to make your own function for loading NSImage
func loadImageNamed(name: String) -> NSImage? {
return NSImage(named: name)
}
func doSomethingWithAnImage() {
if let image = loadImageNamed( "CorrectIconName") {
....
}
}
Alternatively you can extend NSImage.
extension NSImage {
class func imageWithName(name: String) -> NSImage? {
return NSImage(named: name)
}
}
Now you can do the natural thing.
if let image = NSImage.imageWithName("CorrectIconName") {
}
This seems to be the right thing for the Swift/Cocoa API and I hope Apple does something similar in the future.
Unless I'm missing something completely obvious, this is more a matter of opinion. You should probably be storing the return in a variable and then checking the contents of that variable. I think this is mostly because what if that variable ended up not being declared if the return was optional? The if statement would have a fit!
This is pertaining to the first part of your question, which also seems like the rest of your question, but I might be confused...
I had similar problem in IOS and solved it using the ?-operator:
if let im = UIImage(named: "Meetup.png")? {
imageLayer.contents = im.CGImage
}
Without the the ? it would not enter the if branch, but when using ? it does !