How do you delete all the contents of a directory without deleting the directory itself? I want to basically empty a folder yet leave it (and the permissions) intact.
E.g. by using a directory enumerator:
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:path];
NSString *file;
while (file = [enumerator nextObject]) {
NSError *error = nil;
BOOL result = [fileManager removeItemAtPath:[path stringByAppendingPathComponent:file] error:&error];
if (!result && error) {
NSLog(#"Error: %#", error);
}
}
Swift
let fileManager = NSFileManager.defaultManager()
let enumerator = fileManager.enumeratorAtURL(cacheURL, includingPropertiesForKeys: nil, options: nil, errorHandler: nil)
while let file = enumerator?.nextObject() as? String {
fileManager.removeItemAtURL(cacheURL.URLByAppendingPathComponent(file), error: nil)
}
Try this:
NSFileManager *manager = [NSFileManager defaultManager];
NSString *dirToEmpty = ... //directory to empty
NSError *error = nil;
NSArray *files = [manager contentsOfDirectoryAtPath:dirToEmpty
error:&error];
if(error) {
//deal with error and bail.
}
for(NSString *file in files) {
[manager removeItemAtPath:[dirToEmpty stringByAppendingPathComponent:file]
error:&error];
if(error) {
//an error occurred...
}
}
in swift 2.0:
if let enumerator = NSFileManager.defaultManager().enumeratorAtPath(dataPath) {
while let fileName = enumerator.nextObject() as? String {
do {
try NSFileManager.defaultManager().removeItemAtPath("\(dataPath)\(fileName)")
}
catch let e as NSError {
print(e)
}
catch {
print("error")
}
}
}
Swift 2.1.1:
public func deleteContentsOfFolder()
{
// folderURL
if let folderURL = self.URL()
{
// enumerator
if let enumerator = NSFileManager.defaultManager().enumeratorAtURL(folderURL, includingPropertiesForKeys: nil, options: [], errorHandler: nil)
{
// item
while let item = enumerator.nextObject()
{
// itemURL
if let itemURL = item as? NSURL
{
do
{
try NSFileManager.defaultManager().removeItemAtURL(itemURL)
}
catch let error as NSError
{
print("JBSFile Exception: Could not delete item within folder. \(error)")
}
catch
{
print("JBSFile Exception: Could not delete item within folder.")
}
}
}
}
}
}
Swift 3 if anyone needs it for a quick cut/paste
let fileManager = FileManager.default
let fileUrls = fileManager.enumerator(at: folderUrl, includingPropertiesForKeys: nil)
while let fileUrl = fileUrls?.nextObject() {
do {
try fileManager.removeItem(at: fileUrl as! URL)
} catch {
print(error)
}
}
The documentation for contentsOfDirectoryAtPath:error: says:
The search is shallow and therefore does not return the contents of any subdirectories. This returned array does not contain strings for the current directory (“.”), parent directory (“..”), or resource forks (begin with “._”) and does not traverse symbolic links.
Thus:
---( file != #"." && file != #".." )---
is irrelevant.
You can extend the NSFileManager like this:
extension NSFileManager {
func clearFolderAtPath(path: String) -> Void {
for file in subpathsOfDirectoryAtPath(path, error: nil) as? [String] ?? [] {
self.removeItemAtPath(path.stringByAppendingPathComponent(file), error: nil)
}
}
}
Then, you can clear the folder like this: NSFileManager.defaultManager().clearFolderAtPath("the folder's path")
Georg Fritzsche answer for Swift did not work for me. Instead of reading the enumerated object as a String, read it as NSURL.
let fileManager = NSFileManager.defaultManager()
let url = NSURL(string: "foo/bar")
let enumerator = fileManager.enumeratorAtURL(url, includingPropertiesForKeys: nil, options: nil, errorHandler: nil)
while let file = enumerator?.nextObject() as? NSURL {
fileManager.removeItemAtURL(file, error: nil)
}
Why not deleting the whole directory and recreate afterwards? Just get the file attributes and permissions before deleting it, and then recreate it with the same attributes.
Related
This code doesn't working with Swift 3 anymore.
imageData = NSData(base64EncodedString: mediaFile, options: NSDataBase64DecodingOptions.fromRaw(0)!)
So is this one.
imageData = NSData(base64EncodedString: mediaFile, options: .allZeros)
Instead of using NSData use directly Swift 3 native Data.
if let decodedData = Data(base64Encoded: mediaFile, options: .ignoreUnknownCharacters) {
let image = UIImage(data: decodedData)
}
Swift 4.1:
Sometimes string has prefix data:image/png;base64 will make base64Encoded return nil, for this situation:
extension String {
func base64ToImage() -> UIImage? {
if let url = URL(string: self),let data = try? Data(contentsOf: url),let image = UIImage(data: data) {
return image
}
return nil
}
}
Demo code:
let results = text.matches(for: "data:image\\/([a-zA-Z]*);base64,([^\\\"]*)")
for imageString in results {
autoreleasepool {
let image = imageString.base64ToImage()
}
}
extension String {
func matches(for regex: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex)
let results = regex.matches(in: self, range: NSRange(self.startIndex..., in: self))
return results.map {
//self.substring(with: Range($0.range, in: self)!)
String(self[Range($0.range, in: self)!])
}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
}
PS: For more about data uri:
https://en.wikipedia.org/wiki/Data_URI_scheme
https://github.com/nodes-vapor/data-uri
Swift
Swift 3.0 does not recommend to use NS any more and the same case with NSData as well
if let decodedImageData = Data(base64Encoded: mediaFile, options: .ignoreUnknownCharacters) {
let image = UIImage(data: decodedImageData)
}
In Objective-C
NSURL *url = [NSURL URLWithString:base64String];
NSData *decodedImageData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:decodedImageData];
I implemented this as UIImage extension
extension UIImage {
/*
#brief decode image base64
*/
static func decodeBase64(toImage strEncodeData: String!) -> UIImage {
if let decData = Data(base64Encoded: strEncodeData, options: .ignoreUnknownCharacters), strEncodeData.characters.count > 0 {
return UIImage(data: decData)!
}
return UIImage()
}
}
You can write like this way
let data = NSData(base64Encoded: mediaFile, options: NSData.Base64DecodingOptions(rawValue: 0))
Hope it will help you
Is there any way to read/write file tags without shell commands? Already tried NSFileManager and CGImageSource classes. No luck so far.
An NSURL object has a resource for key NSURLTagNamesKey. The value is an array of strings.
This Swift example reads the tags, adds the tag Foo and write the tags back.
let url = NSURL(fileURLWithPath: "/Path/to/file.ext")
var resource : AnyObject?
do {
try url.getResourceValue(&resource, forKey: NSURLTagNamesKey)
var tags : [String]
if resource == nil {
tags = [String]()
} else {
tags = resource as! [String]
}
print(tags)
tags += ["Foo"]
try url.setResourceValue(tags, forKey: NSURLTagNamesKey)
} catch let error as NSError {
print(error)
}
The Swift 3+ version is a bit different. In URL the tagNames property is get-only so it's necessary to bridge cast the URL to Foundation NSURL
var url = URL(fileURLWithPath: "/Path/to/file.ext")
do {
let resourceValues = try url.resourceValues(forKeys: [.tagNamesKey])
var tags : [String]
if let tagNames = resourceValues.tagNames {
tags = tagNames
} else {
tags = [String]()
}
tags += ["Foo"]
try (url as NSURL).setResourceValue(tags, forKey: .tagNamesKey)
} catch {
print(error)
}
#vadian's answer in Swift 4.0
('NSURLTagNamesKey' has been renamed to 'URLResourceKey.tagNamesKey')
let url = NSURL(fileURLWithPath: "/Path/to/file.ext")
var resource : AnyObject?
do {
try url.getResourceValue(&resource, forKey: URLResourceKey.tagNamesKey)
var tags : [String]
if resource == nil {
tags = [String]()
} else {
tags = resource as! [String]
}
print(tags)
tags += ["Foo"]
try url.setResourceValue(tags, forKey: URLResourceKey.tagNamesKey)
} catch let error as NSError {
print(error)
}
You can do it sneakily† using the undocumented PHAsset.ALAssetURL property, but I'm looking for something documented.
† In Objective-C, this will help
#interface PHAsset (Sneaky)
#property (nonatomic, readonly) NSURL *ALAssetURL;
#end
Create the assetURL by leveraging the localidentifier of the PHAsset.
Example:
PHAsset.localidentifier returns 91B1C271-C617-49CE-A074-E391BA7F843F/L0/001
Now take the 32 first characters to build the assetURL, like:
assets-library://asset/asset.JPG?id=91B1C271-C617-49CE-A074-E391BA7F843F&ext=JPG
You might change the extension JPG depending on the UTI of the asset (requestImageDataForAsset returns the UTI), but in my testing the extensions of the assetURL seems to be ignored anyhow.
I wanted to be able to get a URL for an asset too. However, I have realised that the localIdentifier can be persisted instead and used to recover the PHAsset.
PHAsset* asset = [PHAsset fetchAssetsWithLocalIdentifiers:#[localIdentifier] options:nil].firstObject;
Legacy asset URLs can be converted using:
PHAsset* legacyAsset = [PHAsset fetchAssetsWithALAssetUrls:#[assetUrl] options:nil].firstObject;
NSString* convertedIdentifier = legacyAsset.localIdentifier;
(before that method gets obsoleted...)
(Thanks holtmann - localIdentifier is hidden away in PHObject.)
Here is working code tested on iOS 11 both simulator and device
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
[result enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
PHAsset *asset = (PHAsset *)obj;
[asset requestContentEditingInputWithOptions:nil completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
NSLog(#"URL:%#", contentEditingInput.fullSizeImageURL.absoluteString);
NSString* path = [contentEditingInput.fullSizeImageURL.absoluteString substringFromIndex:7];//screw all the crap of file://
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isExist = [fileManager fileExistsAtPath:path];
if (isExist)
NSLog(#"oh yeah");
else {
NSLog(#"damn");
}
}];
}];
Read the bottom!
The resultHandler for PHImageManager.requestImage returns 2 objects: result and info.
You can get the original filename for the PHAsset (like IMG_1043.JPG) as well as its full path on the filesystem with:
let url = info?["PHImageFileURLKey"] as! URL
This should work right, but for some reason it doesn't. So basically, you have to copy your image to a file then access that then delete it.
The PHImageFileURLKey is usable to get the original file name, but you cannot actually access that file. It probably has to do with the fact that code in the background can access the file while other apps can delete it.
Here is a PHAsset extension written in Swift that will retrieve the URL.
extension PHAsset {
func getURL(completionHandler : #escaping ((_ responseURL : URL?) -> Void)){
if self.mediaType == .image {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
self.requestContentEditingInput(with: options, completionHandler: {(contentEditingInput: PHContentEditingInput?, info: [AnyHashable : Any]) -> Void in
completionHandler(contentEditingInput!.fullSizeImageURL as URL?)
})
} else if self.mediaType == .video {
let options: PHVideoRequestOptions = PHVideoRequestOptions()
options.version = .original
PHImageManager.default().requestAVAsset(forVideo: self, options: options, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable : Any]?) -> Void in
if let urlAsset = asset as? AVURLAsset {
let localVideoUrl: URL = urlAsset.url as URL
completionHandler(localVideoUrl)
} else {
completionHandler(nil)
}
})
}
}
}
I'm converting our app over to use the Photos Framework of iOS8, the ALAsset framework is clearly a second class citizen under iOS8.
I'm having a problem is that our architecture really wants an NSURL that represents the location of the media on "disk." We use this to upload the media to our servers for further processing.
This was easy with ALAsset:
ALAssetRepresentation *rep = [asset defaultRepresentation];
self.originalVideo = rep.url;
But I'm just not seeing this ability in PHAsset. I guess I can call:
imageManager.requestImageDataForAsset
and then write it out to a temp spot in the file system but that seems awfully heavyweight and wasteful, not to mention potentially slow.
Is there a way to get this or am I going to have refactor more of my app to only use NSURLs for iOS7 and some other method for iOS8?
If you use [imageManager requestAVAssetForVideo...], it'll return an AVAsset. That AVAsset is actually an AVURLAsset, so if you cast it, you can access it's -url property.
I'm not sure if you can create a new asset out of this, but it does give you the location.
SWIFT 2.0 version
This function returns NSURL from PHAsset (both image and video)
func getAssetUrl(mPhasset : PHAsset, completionHandler : ((responseURL : NSURL?) -> Void)){
if mPhasset.mediaType == .Image {
let options: PHContentEditingInputRequestOptions = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
mPhasset.requestContentEditingInputWithOptions(options, completionHandler: {(contentEditingInput: PHContentEditingInput?, info: [NSObject : AnyObject]) -> Void in
completionHandler(responseURL : contentEditingInput!.fullSizeImageURL)
})
} else if mPhasset.mediaType == .Video {
let options: PHVideoRequestOptions = PHVideoRequestOptions()
options.version = .Original
PHImageManager.defaultManager().requestAVAssetForVideo(mPhasset, options: options, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [NSObject : AnyObject]?) -> Void in
if let urlAsset = asset as? AVURLAsset {
let localVideoUrl : NSURL = urlAsset.URL
completionHandler(responseURL : localVideoUrl)
} else {
completionHandler(responseURL : nil)
}
})
}
}
If you have a PHAsset, you can get the url for said asset like this:
[asset requestContentEditingInputWithOptions:editOptions
completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
NSURL *imageURL = contentEditingInput.fullSizeImageURL;
}];
Use the new localIdentifier property of PHObject. (PHAsset inherits from this).
It provides similar functionality to an ALAsset URL, namely that you can load assets by calling the method
+[PHAsset fetchAssetsWithLocalIdentifiers:identifiers options:options]
All the above solutions won't work for slow-motion videos. A solution that I found handles all video asset types is this:
func createFileURLFromVideoPHAsset(asset: PHAsset, destinationURL: NSURL) {
PHCachingImageManager().requestAVAssetForVideo(self, options: nil) { avAsset, _, _ in
let exportSession = AVAssetExportSession(asset: avAsset!, presetName: AVAssetExportPresetHighestQuality)!
exportSession.outputFileType = AVFileTypeMPEG4
exportSession.outputURL = destinationURL
exportSession.exportAsynchronouslyWithCompletionHandler {
guard exportSession.error == nil else {
log.error("Error exporting video asset: \(exportSession.error)")
return
}
// It worked! You can find your file at: destinationURL
}
}
}
See this answer here.
And this one here.
In my experience you'll need to first export the asset to disk in order to get a fully accessible / reliable URL.
The answers linked to above describe how to do this.
Just want to post the hidden gem from a comment from #jlw
#rishu1992 For slo-mo videos, grab the AVComposition's
AVCompositionTrack (of mediaType AVMediaTypeVideo), grab its first
segment (of type AVCompositionTrackSegment), and then access its
sourceURL property. – jlw Aug 25 '15 at 11:52
In speking of url from PHAsset, I had once prepared a util func on Swift 2 (although only for playing videos from PHAsset). Sharing it in this answer, might help someone.
static func playVideo (view:UIViewController, asset:PHAsset)
Please check this Answer
Here's a handy PHAsset category:
#implementation PHAsset (Utils)
- (NSURL *)fileURL {
__block NSURL *url = nil;
switch (self.mediaType) {
case PHAssetMediaTypeImage: {
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = YES;
[PHImageManager.defaultManager requestImageDataForAsset:self
options:options
resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
url = info[#"PHImageFileURLKey"];
}];
break;
}
case PHAssetMediaTypeVideo: {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[PHImageManager.defaultManager requestAVAssetForVideo:self
options:nil
resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if ([asset isKindOfClass:AVURLAsset.class]) {
url = [(AVURLAsset *)asset URL];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
break;
}
default:
break;
}
return url;
}
#end
I had similiar problem with video files, what worked for me was:
NSString* assetID = [asset.localIdentifier substringToIndex:(asset.localIdentifier.length - 7)];
NSURL* videoURL = [NSURL URLWithString:[NSString stringWithFormat:#"assets-library://asset/asset.mov?id=%#&ext=mov", assetID]];
Where asset is PHAsset.
I need to access every file in a folder, including file that exist within nested folders. An example folder might look like this.
animals/
-k.txt
-d.jpg
cat/
-r.txt
-z.jpg
tiger/
-a.jpg
-p.pdf
dog/
-n.txt
-f.jpg
-p.pdf
Say that I wanted to run a process on every file within "animals" that isn't folder. What would be the best way to iterate through the folder "animals" and all of its subfolders to access every file?
Thanks.
Use NSDirectoryEnumerator to recursively enumerate files and directories under the directory you want, and ask it to tell you whether it is a file or directory. The following is based on the example listed at the documentation for -[NSFileManager enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:]:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *directoryURL = … // URL pointing to the directory you want to browse
NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
NSDirectoryEnumerator *enumerator = [fileManager
enumeratorAtURL:directoryURL
includingPropertiesForKeys:keys
options:0
errorHandler:^BOOL(NSURL *url, NSError *error) {
// Handle the error.
// Return YES if the enumeration should continue after the error.
return YES;
}];
for (NSURL *url in enumerator) {
NSError *error;
NSNumber *isDirectory = nil;
if (! [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
// handle error
}
else if (! [isDirectory boolValue]) {
// No error and it’s not a directory; do something with the file
}
}
Maybe you can use something like this:
+(void)openEachFileAt:(NSString*)path
{
NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:path];
for (NSString * file in enumerator)
{
// check if it's a directory
BOOL isDirectory = NO;
NSString* fullPath = [path stringByAppendingPathComponent:file];
[[NSFileManager defaultManager] fileExistsAtPath:fullPath
isDirectory: &isDirectory];
if (!isDirectory)
{
// open your file (fullPath)…
}
else
{
[self openEachFileAt: fullPath];
}
}
}
Here is a swift version:
func openEachFile(inDirectory path: String) {
let subs = try! FileManager.default.subpathsOfDirectory(atPath: path)
let totalFiles = subs.count
print(totalFiles)
for sub in subs {
if sub.hasSuffix(".DS_Store") {
//a DS_Store file
}
else if sub.hasSuffix(".xcassets") {
//a xcassets file
}
else if (sub as NSString).substring(to: 4) == ".git" {
//a git file
}
else if sub.hasSuffix(".swift") {
//a swift file
}
else if sub.hasSuffix(".m") {
//a objc file
}
else if sub.hasSuffix(".h") {
//a header file
}
else {
// some other file
}
let fullPath = (path as NSString).appendingPathComponent(sub)
}
}
Here's a solution using -subpathsOfDirectoryAtPath:rootPath, with file URLs and modern Objective-C nullability bells & whistles.
typedef void (^FileEnumerationBlock)(NSURL *_Nonnull fileURL);
#interface NSFileManager (Extensions)
- (void)enumerateWithRootDirectoryURL:(nonnull NSURL *)rootURL
fileHandler:(FileEnumerationBlock _Nonnull)fileHandler
error:(NSError *_Nullable *_Nullable)error;
#end
#implementation NSFileManager (Extensions)
- (void)enumerateWithRootDirectoryURL:(NSURL *)rootURL
fileHandler:(FileEnumerationBlock)fileHandler
error:(NSError **)error {
NSString *rootPath = rootURL.path;
NSAssert(rootPath != nil, #"Invalid root URL %# (nil path)", rootURL);
NSArray *subs = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:rootPath
error:error];
if (!subs) {
return;
}
for (NSString *sub in subs) {
fileHandler([rootURL URLByAppendingPathComponent:sub]);
}
}
#end
… and the same in Swift:
func enumerate(rootDirectoryURL rootURL: NSURL, fileHandler:(URL:NSURL)->Void) throws {
guard let rootPath = rootURL.path else {
preconditionFailure("Invalid root URL: \(rootURL)")
}
let subs = try NSFileManager.defaultManager().subpathsOfDirectoryAtPath(rootPath)
for sub in subs {
fileHandler(URL: rootURL.URLByAppendingPathComponent(sub))
}
}
This code worked for me.
NSMutableString *allHash;
-(NSString*)getIterate:(NSString*)path {
allHash = [NSMutableString stringWithString:#""];
NSDirectoryEnumerator *de= [[NSFileManager defaultManager] enumeratorAtPath:path];
NSString *file;
BOOL isDirectory;
for(file in de)
{
//first check if it's a file
NSString* fullPath = [NSString stringWithFormat:#"%#/%#",path,file];
BOOL fileExistsAtPath = [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDirectory];
NSLog(#"Check=>%d",fileExistsAtPath);
if (!isDirectory) //its a file
{
//Do with filepath
}
else{ //it's a folder, so recurse
[self enumerateFolder:fullPath];
}
}
return allHash;
}
-(void) enumerateFolder:(NSString*)fileName
{
NSDirectoryEnumerator *de= [[NSFileManager defaultManager] enumeratorAtPath:fileName];
NSString* file;
BOOL isDirectory;
for(file in de)
{
//first check if it's a file
BOOL fileExistsAtPath = [[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:&isDirectory];
if (fileExistsAtPath) {
if (!isDirectory) //its a file
{
//Do with file
}
else{ //it's a folder, so recurse
[self enumerateFolder:file];
}
}
else printf("\nenumeratefolder No file at path %s",[file UTF8String]);
}
}
In Swift we can try the following.
let docsDir = NSHomeDirectory().appending("/Documents")
let localFileManager = FileManager()
let dirEnum = localFileManager.enumerator(atPath: docsDir)
while let file = dirEnum?.nextObject() as? String {
if file.hasSuffix(".doc") {
print(docsDir.appending("/\(file)"))
}
}
Reference
https://developer.apple.com/documentation/foundation/filemanager/1408726-enumerator