How would one programmatically save an image generated from a playground to the resources folder?
I have generated some photos through filters in my playground that I would like to save somehow. The resources folder seems like a good fit. What if I wanted to save the image to the desktop?
I've seen this done with saving images for apps, but I just want to save it to my desktop (or a specified folder in a specified location.)
Create folder first in 'Documents' with name 'Shared Playground Data' otherwise app will give error.
SWIFT 5:
let image = // your UIImage
let filepath = playgroundSharedDataDirectory.appendingPathComponent("export.jpg")
let imagedata = image.jpegData(compressionQuality: 1.0)
try! imagedata?.write(to: URL.init(fileURLWithPath: filepath.path))
print("write to \(filepath)")
In Xcode 11, the folder is not in ~/Documents anymore for iOS playgrounds but buried deep in ~/Library/Developer/. You have to print the path and create the Shared Playground Data folder manually to save a file from a playground:
import PlaygroundSupport
// ...
let url = playgroundSharedDataDirectory.appendingPathComponent("example.png")
print(url)
try! image.pngData()!.write(to: url)
Here's how to save it to the shared data directory:
Create a path to the image:
let path = XCPlaygroundSharedDataDirectoryURL.appendingPathComponent("export.jpg")!
(you may need to create the directory ~/Documents/Shared Playground Data/)
Then to save your output (in this case a CIImage from a filter)
let temp:CGImage = context.createCGImage(output, from: output.extent)!
let image = UIImage(cgImage: temp)
let data = UIImageJPEGRepresentation(image, 1.0)
try! data?.write(to: path)
While the first answer helped point me in the right direction, the lack of ability to validate the required playgroundSharedDataDirectory path exists, the requirement to create that directory in order for the path to work, as well as force unwrapping (using !) the try can lead to frustration as well as drag out the process.
Here is a safer, more convenient solution that checks the name you provide for extra safety:
func writeImage(_ image: UIImage, name: String) {
if name.isEmpty || name.count < 3 {
print("Name cannot be empty or less than 3 characters.")
return
}
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
print("No documents directory found.")
return
}
let imagePath = documentsDirectory.appendingPathComponent("\(name).png")
let imagedata = image.jpegData(compressionQuality: 1.0)
do {
try imagedata?.write(to: imagePath)
print("Image successfully written to path:\n\n \(documentsDirectory) \n\n")
} catch {
print("Error writing image: \(error)")
}
}
Call using:
writeImage(image, name: "MyNewImage")
A break down of what is happening:
The method writeImage checks to see if the documents directory exists.
If the documents directory does not exist (unlikely), stop process.
If the documents directory exists, carry on and write the image to the path.
Related
I have a WKWebView app which i will like to clear a specific cache when image is uploaded .
When i upload an image, the name remains same but the old image will be override with new one, like old image is logo.png, new image will be renamed with logo.png also. But because of cache users will still be seeing the old image.
I have below line of code to clear cache once image has been uploaded and it working fine, but my only problem now is that all cached data is being cleared also.
Is there any way i can clear cache for just the image was uploaded, maybe by passing the image name?
Or just clear cache for images?
static func clearCache(){
let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let fileManager = FileManager.default
do {
// Get the directory contents urls (including subfolders urls)
let directoryContents = try FileManager.default.contentsOfDirectory( at: cacheURL, includingPropertiesForKeys: nil, options: [])
for file in directoryContents {
print("CACHE = ", file)
do {
try fileManager.removeItem(at: file)
}
catch let error as NSError {
debugPrint("Ooops! Something went wrong: \(error)")
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
From the loop i got this
CACHE =
file:///private/var/mobile/Containers/Data/Application/BFEB788B-9EA4-4921-A902-230869CAC814/Library/Caches/com.myapp.ios.sellers/
CACHE =
file:///private/var/mobile/Containers/Data/Application/BFEB788B-9EA4-4921-A902-230869CAC814/Library/Caches/google-sdks-events/
CACHE =
file:///private/var/mobile/Containers/Data/Application/BFEB788B-9EA4-4921-A902-230869CAC814/Library/Caches/com.apple.WebKit.WebContent/
CACHE =
file:///private/var/mobile/Containers/Data/Application/BFEB788B-9EA4-4921-A902-230869CAC814/Library/Caches/WebKit/
It seems like you need to find the file you want to remove when you are in the for loop loop for files. Remove that specific image when the for loop hits. Right now that for loop removes all cache in directoryContents.
hope you are doing good in development. I have a question regarding Zipping currently i am using Zip framework which I am using for zip the all captured image in device but the problem here is I do not want to save the Zip file in document directory instead I wanna save this memory itself. I am struggling since last week please let me know how can we achieve the zipping without saving it in local directory filepath
Also when I captured the image successfully I saved it to local directory using
let documentsDirectory = FileManager.default.urls(for:
.documentDirectory, in: .userDomainMask).first!
let fileName = "image.jpg"
let fileURL = documentsDirectory.appendingPathComponent(fileName)
if let data = UIImageJPEGRepresentation(image, 1.0),
!FileManager.default.fileExists(atPath: fileURL.path) {
do {
// writes the image data to disk
try data.write(to: fileURL)
print("file saved")
} catch {
print("error saving file:", error)
}
}
After when I am trying to send the image before our server I want to make it all one file so I have implemented the Zip (framework)
Here filpathArray meant all captured image in local path
do {
var urls = [URL]()
for i in 0..<(self.filePathArray.count)
{
urls.append(self.filePathArray[i] as! URL)
}
if self.filePathArray.count > 0 {
let zipFiles = try Zip.quickZipFiles(urls, fileName: "AllFiles")
}
}
Here zipping is done successfully but it saved in local path so when using app container I can able to see the images, I do not want to see the images in device like apple sandbox or Xcode container itself
I want to make zipping on the flow like without saving it document directory, Thanks in advance.
I am trying load SCNParticleSystem from download bundle which i am not able to load.
Path for the resource.
file:///var/mobile/Containers/Data/Application/A91E9970-CDE1-43D8-B822-4B61EFC6149B/Documents/so/solarsystem.bundle/Contents/Resources/
let objScene = SCNParticleSystem(named: "stars", inDirectory: directory)
This object is nil.
This is a legitimate problem since SceneKit does not provide an out-of-the-box solution for initializing particle systems from files that are outside of the main bundle (the only init method SCNParticleSystem.init(named:inDirectory:) implies that SCNParticleSystem.scnp files are in the main bundle).
Luckily for us .scnp files are just encoded/archived SCNParticleSystem instances that we can easily decode/unarchive using NSKeyedUnarchiver:
extension SCNParticleSystem {
static func make(fromFileAt url: URL) -> SCNParticleSystem? {
guard let data = try? Data(contentsOf: url),
let object = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data),
let system = object as? SCNParticleSystem else { return nil }
return system
}
}
If you do not need to support iOS 9 and iOS 10 you can use NSKeyedUnarchiver.unarchivedObject(ofClass: SCNParticleSystem.self, from: data) instead of NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(_:) and type casting, which was introduced in iOS 11.0.
Another issue that you're most likely to encounter is missing particle images. That is because by default SceneKit will look for them in the main bundle. As of current versions of iOS (which is iOS 12) and Xcode (Xcode 10) particle images in .scnp files (particleImage property) are String values which are texture filenames in the main bundle (that might change, but probably won't, however there's not much else we could use).
So my suggestion is to take that filename and look for the texture file with the same name in the same directory where the .scnp file is:
extension SCNParticleSystem {
static func make(fromFileAt url: URL) -> SCNParticleSystem? {
guard let data = try? Data(contentsOf: url),
let object = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data),
let system = object as? SCNParticleSystem else { return nil }
if let particleImageName = system.particleImage as? String {
let particleImageURL = url
.deletingLastPathComponent()
.appendingPathComponent(particleImageName)
if FileManager.default.fileExists(atPath: particleImageURL.path) {
system.particleImage = particleImageURL
}
}
return system
}
}
You can just set the URL of the image file and SceneKit will handle it from there.
As a little side-note, the recommended directory for downloadable content is Application Support directory, not Documents.
Application Support: Use this directory to store all app data files except those associated with the user’s documents. For example, you might use this directory to store app-created data files, configuration files, templates, or other fixed or modifiable resources that are managed by the app. An app might use this directory to store a modifiable copy of resources contained initially in the app’s bundle. A game might use this directory to store new levels purchased by the user and downloaded from a server.
(from File System Basics)
Don't have enough reps to add the comment so adding it as the answer.
The answer by Lësha Turkowski works for sure but was had issues with loading the particle images using only NSURL.
All particles were appearing square which meant,
If the value is nil (the default), SceneKit renders each particle as a
small white square (colorized by the particleColor property).
SCNParticleSystem particleImage
In the documentation it says You may specify an image using an
NSImage (in macOS) or UIImage (in iOS) instance, or an NSString or
NSURL instance containing the path or URL to an image file.
Instead of using the NSURL, ended up using the UIImage and it loaded up fine.
extension SCNParticleSystem {
static func make(fromFileAt url: URL) -> SCNParticleSystem? {
guard let data = try? Data(contentsOf: url),
let object = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data),
let system = object as? SCNParticleSystem else { return nil }
if let particleImageName = system.particleImage as? String {
let particleImageURL = url
.deletingLastPathComponent()
.appendingPathComponent(particleImageName)
if FileManager.default.fileExists(atPath: particleImageURL.path) {
// load up the NSURL contents in UIImage
let particleUIImage = UIImage(contentsOfFile: particleImageURL.path)
system.particleImage = particleUIImage
}
}
return system
}
}
I found out, that sometimes when dragging a SCNParticleSystem file into your project (probably form a different project) a silent error can happen due to some bugs in Xcode. As a result you can't get a reference to an instance of your SCNParticleSystem.
Solution: Check your BuildSettings in your target. The SCNPaticleSystem AND the associated ImageFile should be listed there and then you should get it right. (see screenShot below)
This question already has answers here:
Save An Image To Application Documents Folder From UIView On IOS
(6 answers)
Closed 5 years ago.
I want to in my app, be able to allow users to save large images(jpg) as well as data for each image(txt) and load the images/data. I'm having trouble figuring out where to save these images and text files. Userdefault wouldnt work because of the size of the image files and I don't want to save in the documents directory because then the user can access and potentially corrupt the data.
Where is a good place to save large data files for my app so I can load them later?
You can save and retrieve your files in application directory folder. Also you can use iCloud to save your files.
Use below code if you want to save and retrieve files from Directory folder .
Xcode 8.3.2 Swift 3.x. Using NSKeyedArchiver and NSKeyedUnarchiver
Reading file from documents
let documentsDirectoryPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let documentsDirectoryPath = NSURL(string: documentsDirectoryPathString)!
let jsonFilePath = documentsDirectoryPath.appendingPathComponent("Filename.json")
let fileManager = FileManager.default
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: (jsonFilePath?.absoluteString)!, isDirectory: &isDirectory) {
let finalDataDict = NSKeyedUnarchiver.unarchiveObject(withFile: (jsonFilePath?.absoluteString)!) as! [String: Any]
}
else{
print("File does not exists")
}
Write file to documents
NSKeyedArchiver.archiveRootObject(finalDataDict, toFile:(jsonFilePath?.absoluteString)!)
If for some reason you don't want to use the documents directory, or you want those data in a separate folder, or you don't want to permanently save them, you can also create your own temporary directory
saving data as file in a new directory (swift 3):
func saveToMyDirectory(data: Data, filename: String) {
var tempDirectoryURL = NSURL.fileURL(withPath: NSTemporaryDirectory(), isDirectory: true)
tempDirectoryURL = tempDirectoryURL.appendingPathComponent(filename)
do {
try data?.write(to: tempDirectoryURL)
} catch {}
}
Alternative approach: use Apple's UIDocumentInteractionController or UIActivityViewController and let the user choose how to save his/her documents:
https://developer.apple.com/documentation/uikit/uidocumentinteractioncontroller
https://developer.apple.com/documentation/uikit/uiactivityviewcontroller
I am caching CKRecord on client and fileUrl of CKAsset too. Can fileUrl change from time to time? Asset / data itself is not changing.
The fileURL of an asset will not change, but if this is an asset you downloaded from the server the data is only guaranteed to exist at that location until the completion block of the operation is called. After that point the asset's backing file may be cleaned up at any time to free up disk space.
After downloading an asset from the server you should move or copy the backing file to another location in your application's container if you'd like to keep it.
The archived URL is a full path, but since iOS 8 or so the container URL that your app uses to store its files changes everytime the app starts.
So the archived URL is wrong.
This seems like a bug in the way CKAssets are archived to me, they should be archiving and unarchiving the partial path relative to the app's container. Not the full path.
I guess I'll file a radar.
Edit: here's a solution:
extension CKAsset {
var url: URL? {
let path = fileURL.path
if FileManager.default.fileExists(atPath: path) {
return fileURL
} else if let index = path.range(of: "/Library/Caches/") {
// archived CKAssets store full-path URLs but since iOS 8 or so
// the path of our container (home-dir) changes every time we are
// started. So the full path is useless. Try to mangle it.
let suffix = String(path.suffix(from: index.upperBound))
let adjustedUrl = URL.caches/suffix
if FileManager.default.fileExists(atPath: adjustedUrl.path) {
return adjustedUrl
}
}
return nil
}
}
public extension URL {
static var caches: URL {
return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first ?? URL(fileURLWithPath: "")
}
}