UserDefaults to Image code builds, but crashes when ran - swift

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.

Related

Convert UIView to UIImage to save as binary data in Core Data

I am trying to save a UIView as a UIImage to save in the Core Data database. My code is causing a compile error at self.canVasView.image stating:
Value of type 'UIView' has no member 'image'.
I think part of this code works in Swift 4 but it is not working in Swift 5.
var canVasView = UIView()
#objc func hhh() {
let photo = self.canVasView.image
let data = UIImagePNGRepresentation(photo!)
if cdHandler.saveObject(pic: data!){
}
}
You need to use a specific subclass of UIView.
Try using:
var canVasView = UIImageView()
Of course, you need to actually set the .image property to some UIImage in order for the call to UIImagePNGRepresentation to actually work.

Instance variables are nil after I try to set them

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.

Saving image into core data and fetching it into tableview cell

I did a set up of CoreData with entity "users" and attribute "username" (when I type something in uitextfield it saves into core data and I got readings from core data into tableview cell). That is working like a charm. But I also want to display chosen picture from imagepickercontroller and that is also working fine when I choose picture but when I click save I think I got it saved. I don't know if it is saved because I eliminated all error so I got no errors, but it won't read that picture into tableview cell. I put new attribute "image" with type binary data into core data file but code does not work.
This is code for saving into core data
let newUser = NSEntityDescription.insertNewObjectForEntityForName("Users", inManagedObjectContext: context)
newUser.setValue(nameField.text, forKey: "username")
let picture = UIImageJPEGRepresentation(photoImageView.image!, 1);
newUser.setValue(picture, forKey: "image")
And this is code where tableview reads data from core data
cell.imageView!.image = person.valueForKey("image") as? UIImage
I also tried with
[UIImage, imageWithData:self.myEvent.picture];
but Swift 2.0 don't have that syntax imageWithData
person.valueForKey("image") as? UIImage won't work.
Use UIImage(data:) to convert the retrieved NSData to UIImage.
for retrieving image from core data i used this and it is finally working.
let image = person.valueForKey("image") as? NSData
cell.imageView!.image = UIImage(data: image!)

Swift UIPasteboard not copying PNG

My problem is really odd. In the simulator the .png copies to clipboard fine and I can paste the image in the Contacts app on Simulator. But when I put the app on the phone, the png is not copied to clipboard.
let img = UIImage(named: "myimage")
let data = NSData(data: UIImagePNGRepresentation(img) )
UIPasteboard.generalPasteboard().setData(data, forPasteboardType: "public.png")
That's the code I'm using but like I said it does not copy to the clipboard. I'm using this code within the context of a keyboard, although that shouldn't matter when copying to a clipboard. If anyone has any ideas please let me know. Thanks in advance! Oh this is my first app in Swift and my first iOS app, so I don't have the seasoned experience to know if this is a Swift issue or something I'm just missing. =\
Make sure the code runs fine in your host app (not a keyboard extension app).
For example, check if the read image has the same resolution:
//the Pasteboard is nil if full access is not granted
let pbWrapped: UIPasteboard? = UIPasteboard.generalPasteboard()
if let pb = pbWrapped {
var type = UIPasteboardTypeListImage[0] as! String
if (count(type) > 0) && (image != nil) {
pb.setData(UIImagePNGRepresentation(image), forPasteboardType: type)
var readDataWrapped: NSData? = pb.dataForPasteboardType(type)
if let readData = readDataWrapped {
var readImage = UIImage(data: readData, scale: 2)
println("\(image) == \(pb.image) == \(readImage)")
}
}
}
If the pasteboard object is nil in your keyboard app that means you haven't provided full access to the keyboard: Copying and pasting image into a textbook in simulator
I believe you can use this line to do what you want (not able to test it out right now):
let image = UIImage(named: "myimage.png")
UIPasteboard.generalPasteboard().image = image;
Hopefully that works, I'm a little rusty with UIPasteboard.
There are lots of bugs and issues with the UIPasteboard class, so I'm really not surprised that you're having issues with something that so obviously is supposed to work. The documentation isn't that helpful either, to be honest. But try this; this worked for me on a physical device, and it's different to the above methods that are supposed to work but evidently don't for a bunch of people.
guard let imagePath = NSBundle.mainBundle().pathForResource("OliviaWilde", ofType: "jpg") else
{ return }
guard let imageData = NSData(contentsOfFile: imagePath) else { return }
let pasteboard = UIPasteboard.generalPasteboard()
pasteboard.setData(imageData, forPasteboardType: "public.jpeg")
You can use either "public.jpeg" or "public.png" if the source file is .jpg; it still works. I think it only changes the format of the thing that gets pasted?
Also, did you try adding the file extension in your first line of code where you create the UIImage? That might make it work too.
Evidently use of this class is temperamental, not just in this use case. So even though we're doing same thing, only difference in this code is we're creating the NSData from a path rather than a UIImage. Lol let me know if that works for you.
Ensure that RequestsOpenAccess is set to YES under NSExtension > NSExtensionAttributes in the extension's info.plist

How to properly check if non-Optional return value is valid?

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 !