I'm writing an iOS application using Swift 2 and I would like to save profile picture of an account locally in a Realm database. I can't find any documentation or people talking about that.
Is it possible? And how?
May be is it bad to do that?
You can store images as NSData. Given you have the URL of an image, which you want to store locally, here is a code snippet how that can be achieved.
class MyImageBlob {
var data: NSData?
}
// Working Example
let url = NSURL(string: "http://images.apple.com/v/home/cb/images/home_evergreen_hero_iphone_medium.jpg")!
if let imgData = NSData(contentsOfURL: url) {
var myblob = MyImageBlob()
myblob.data = imgData
let realm = try! Realm()
try! realm.write {
realm.add(myblob)
}
}
May be it is a bad idea to do that?
The rule is simple:
If the images are small in size, the number of images is small and they are rarely changed, you can stick with storing them in the database.
If there is a bunch images, you are better off writing them directly to the file system and just storing the path of the image in the database.
Here is how that can be done:
class MyImageStorage{
var imagePath: NSString?
}
let url = NSURL(string: "http://images.apple.com/v/home/cb/images/home_evergreen_hero_iphone_medium.jpg")!
if let imgData = NSData(contentsOfURL: url) {
// Storing image in documents folder (Swift 2.0+)
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let writePath = documentsPath?.stringByAppendingPathComponent("myimage.jpg")
imgData.writeToFile(writePath, atomically: true)
var mystorage = MyImageStorage()
mystorage.imagePath = writePath
let realm = try! Realm()
try! realm.write {
realm.add(mystorage)
}
}
Please note: Both code samples are not reliable methods for downloading images since there are many pitfalls. In real world apps / in production, I'd suggest to use a library intended for this purpose like AFNetworking or AlamofireImage.
ProblemSlover's solution updated for Swift 2.2:
Important Note: the only change is that now stringByAppendingPathComponent has been removed as of Swift 2.1, so you will get an error saying:
stringByAppendingPathComponent is unavailable: use URLByAppendingPathComponent on NSURL instead.
Please keep in mind that while it may be annoying, it has been removed for a reason and you should follow apple's recommendation to use NSURL instead. BUT if you wish to proceed with this solution, the fix for Swift 2.1+ is to explicitly cast documentsPath to NSString:
class MyImageStorage{
var imagePath: NSString?
}
let url = NSURL(string: "http://images.apple.com/v/home/cb/images/home_evergreen_hero_iphone_medium.jpg")!
if let imgData = NSData(contentsOfURL: url) {
//Explicitly cast to NSString
let documentsPath = (NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString)
let writePath = documentsPath.stringByAppendingPathComponent("myimage.jpg")
imgData.writeToFile(writePath, atomically: true)
var mystorage = MyImageStorage()
mystorage.imagePath = writePath
let realm = try! Realm()
try! realm.write {
realm.add(mystorage)
}
}
Related
Using this code, I extract an image from a Share Extension and I write it to a directory I created in an App Group.
let content = self.extensionContext!.inputItems[0] as! NSExtensionItem
let contentType = kUTTypeImage as String
for attachment in content.attachments as! [NSItemProvider] {
if attachment.hasItemConformingToTypeIdentifier(contentType) {
attachment.loadItem(forTypeIdentifier: contentType, options: nil) { data, error in
// from here
if error == nil {
let url = data as! NSURL
let originalFileName = url.lastPathComponent
if let imageData = NSData(contentsOf: url as URL) {
let img = UIImage(data:imageData as Data)
if let data = UIImagePNGRepresentation(img!) {
// write, etc.
}
}
}
}
Anything is working fine.
What I'd like to know is if it is possible to reduce some code: in particular, after if error == nil, I:
cast data to NSURL;
use NSURL to get a NSData;
use NSData to get a UIImage;
use UIImage to get a UIImagePNGRepresentation;
Aside from avoiding the creation of the imageData variable, isn't there a way to (safely) achieve the same goal with fewer steps?
First of all you need to use native Data and URL instead of NSData & NSURL also if you want to write file in DocumentDirectory then you can directly use that imageData no need to make UIImage object from it and then convert it to data using UIImagePNGRepresentation.
if let url = data as? URL, error == nil {
let originalFileName = url.lastPathComponent
if let imageData = try? Data(contentsOf: data) {
// write, etc.
var destinationURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
destinationURL.appendPathComponent("fileName.png")
try? imageData.write(to: destinationURL)
}
}
This question already has an answer here:
Swift write to file (iOS)
(1 answer)
Closed 6 years ago.
I was trying to make a module using Swift that can add a class to the database(ClassA.plist). The code is like this:
class AddClass:UITableViewController {
#IBOutlet weak var txtClassName: UITextField!
#IBAction func Save(_ sender: Any) {
let plistPath = Bundle.main.path(forResource: "ClassA", ofType:"plist")
let array = NSMutableArray(contentsOfFile: plistPath!)
let AddData = NSDictionary(object: txtClassName.text!, forKey: "name" as NSCopying)
array?.add(AddData)
array?.write(toFile: plistPath!, atomically: false)
DispatchQueue.main.async {
super.tableView.reloadData()
}
}
And my storyboard is like this:
Storyboard
Save Button
However, as I write something in the textfield and click Save, nothing changed in my database. No waring, error or logs is shown.Does anyone know how to solve this problem?
You can't write to a file in the app bundle. The app bundle is read-only. You will need to copy your file to one of your sandbox directories like the documents directory before you will be able to change it.
As many have pointed out, the app's bundle is read-only. What you can do is copy the file if you really want to use it and put it in the documentDirectory where you can read and write to it.
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let plistPath = paths.appending("/data.plist")
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: plistPath)
{
// Default plist name is Info. Just using ClassA
let bundle = Bundle.main.path(forResource: "ClassA", ofType: "plist")
try! fileManager.copyItem(atPath: bundle!, toPath: plistPath)
}
let data = NSMutableDictionary(contentsOfFile: plistPath)
data?.setObject("Hello World", forKey: "name" as NSCopying)
data?.write(toFile: plistPath, atomically: true)
print(data)
Alright, Im fairly new to the url locating process (I always just use explicit names) and everyone is new to MSMessageStickers. However. I need to pull in an array of image urls to use as MSStickers from a folder of images I have copied into my MessagesExtension target in my project here (it is starterPack):
It may the Swift 3 syntax screwing this up or something else, but I CANT find any way to just get the right url of this folder and get all the images inside of it. The following is successful in making stickers out of PNGS with specific names:
for i in (1..<2) {
if let url = Bundle.main.url(forResource: "test\(i)", withExtension: "png") {
do {
//let sticker = try MSSticker(contentsOfFileURL: url, localizedDescription: "")
let sticker = try MSSticker(contentsOfFileURL: url, localizedDescription: "")
//print("SUCCESS", url)
stickers.append(sticker)
} catch {
print(error)
}
}
}
And I have adapted the following to Swift 3 from similar question How to get array of UIImage, from folder, in Swift? :
if let path = NSBundle.mainBundle().resourcePath {
let imagePath = path + "/images"
let url = NSURL(fileURLWithPath: imagePath)
let fileManager = NSFileManager.defaultManager()
let properties = [NSURLLocalizedNameKey,
NSURLCreationDateKey, NSURLLocalizedTypeDescriptionKey]
do {
let imageURLs = try fileManager.contentsOfDirectoryAtURL(url, includingPropertiesForKeys: properties, options:NSDirectoryEnumerationOptions.SkipsHiddenFiles)
print("image URLs: \(imageURLs)")
// Create image from URL
var myImage = UIImage(data: NSData(contentsOfURL: imageURLs[0])!)
} catch let error1 as NSError {
print(error1.description)
}
}
But because the folder is not technically in my project folder but rather in the messagesExtension folder as you can see, I think that is why it cant find it.
I need to bring in and get the url of all the images contained in my stickers folder. What am I doing wrong?
I have a line in a code which became deprecated, there are suggestions in XCode what to replace it with, but I can't get my head around the difference, these are my three lines which worked:
let path = NSBundle.mainBundle().pathForResource("example", ofType: ".p12")
let pkcs12Data = NSData.dataWithContentsOfMappedFile(path!)
let cf: CFDataRef = pkcs12Data as! CFDataRef
Now according to warning and suggestions I changed my code to:
let path = NSBundle.mainBundle().pathForResource("example", ofType: ".p12")
let pkcs12Data = NSData(contentsOfFile: path!)
let cf: CFDataRef = pkcs12Data as! CFDataRef
Which gives me an error:
EXC_BAD_INSTRUCTION (CODE=EXC_I386_INVOP SUBCODE=0x0)
A slightly safer version:
guard
let url = NSBundle.mainBundle().URLForResource("example", withExtension: ".p12"),
let data = NSData(contentsOfURL: url)
else { // Do something because you couldn't get the file or convert it to NSData }
let dataPtr = CFDataCreate(kCFAllocatorDefault, UnsafePointer<UInt8>(data.bytes), data.length)
Note, using file based URLs instead of string paths.
When deciding which routines to call, choose ones that let you specify paths using NSURL objects over those that specify paths using strings. Most of the URL-based routines were introduced in OS X v10.6 and later and were designed from the beginning to take advantage of technologies like Grand Central Dispatch. This gives your code an immediate advantage on multicore computers while not requiring you to do much work.
From File System Programming Guide
In Swift 4, however, you should do it this way:
Xcode 9.2 seems to treat the NSData as Data automatically when the two optionals are put in the same guard-let clause.
I have to put the two optionals in separate guard-let clause, as bellow:
// guard let url = Bundle.main.url(forResource: "example", withExtension: ".p12"),
// let data = NSData(contentsOf: url) else {
// return
// }
guard let url = Bundle.main.url(forResource: "example", withExtension: ".p12") else {
return
}
guard let data = NSData(contentsOf: url) else {
return
}
let bytes = data.bytes.assumingMemoryBound(to: UInt8.self)
let cfData = CFDataCreate(kCFAllocatorDefault, bytes, data.length) // CFData object you want
#Abizern’s answer works, but using CFDataCreateWithBytesNoCopy instead of CFDataCreate is more effective.
I am trying to take a picture and upload the image into an iCloud database, however I keep getting the error: Type 'UIImage' does not conform to protocol 'CKRecordValue'
The code I am trying to use to upload the file is as follows:
data.setObject(picFile.image, forKey: "picture")
Is there something wrong with the line of code, or am I missing something else?
I had to convert it to a CKAsset. I used the following code:
let imageData = UIImageJPEGRepresentation(picFile.image, 1)
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentDirectory = paths[0] as String
let myFilePath = documentDirectory.stringByAppendingPathComponent("picture")
imageData.writeToFile(myFilePath, atomically: true)
let url = NSURL(fileURLWithPath: myFilePath)
let asset = CKAsset(fileURL: url)
data.setObject(asset, forKey: "picture"