I've been trying to get this working for a day now and I'm still failing. I want to copy a number of files into the Documents folder of my app from the bundle when the app is installed, but this makes the user wait for a long time with the app showing the splash screen.
So I thought I'd make an initial UIAlertView with a UIProgressView as a subview that gets updated every time a file is copied into the documents folder. However the alert shows and the progress bar never gets updated. My logic was:
Set up the UIProgressView and UIAlertView as instance variables of my ViewController.
In ViewDidLoad, present the alert and set the delegate
In - (void)didPresentAlertView:(UIAlertView *)alertView perform a for loop that copies the files and updates the UI. the code was :
- (void)didPresentAlertView:(UIAlertView *)alertView{
NSString *src, *path;
src = // path to the Bundle folder where the docs are stored //
NSArray *docs = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:src error:nil];
float total = (float)[docs count];
float index = 1;
for (NSString *filename in docs){
path = [src stringByAppendingPathComponent:filename];
if ([[NSFileManager defaultManager]fileExistsAtPath:path]) {
... // Copy files into documents folder
[self performSelectorOnMainThread:#selector(changeUI:) withObject:[NSNumber numberWithFloat:index/total] waitUntilDone:YES];
index++;
}
}
[alertView dismissWithClickedButtonIndex:-1 animated:YES];
}
And the code for ChangeUI is
- (void) changeUI: (NSNumber*)value{
NSLog(#"change ui %f", value.floatValue);
[progressBar setProgress:value.floatValue];
}
However this just updates the UI from 0 to 1, although the NSLog prints all the intermediate values. Does anyone here know what I'm doing wrong?
Thanks in advance.
The problem is that your loop is on the main thread, and thus the UI has no chance to update until the very end. Try doing the work on a background thread using GCD:
dispatch_async(DISPATCH_QUEUE_PRIORITY_DEFAULT, ^
{
NSString *src, *path;
src = // path to the Bundle folder where the docs are stored //
NSArray *docs = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:src error:nil];
float total = (float)[docs count];
float index = 1;
for (NSString *filename in docs){
path = [src stringByAppendingPathComponent:filename];
if ([[NSFileManager defaultManager]fileExistsAtPath:path]) {
... // Copy files into documents folder
dispatch_async(dispatch_get_main_queue(), ^{ [self changeUI:[NSNumber numberWithFloat:index/total]]; } );
index++;
}
}
dispatch_async(dispatch_get_main_queue(), ^{ [alertView dismissWithClickedButtonIndex:-1 animated:YES]; } );
} );
Related
I am sending database file successfully between iOS devices with the following code:
-(void) doSendDatabase {
UIView *viewTemp = [[UIView alloc] init];
viewTemp.frame = CGRectMake(0.0f, 0.0f, 300, 300);
NSString *currentDatabaseName;
// This is the full path and file name with ext
currentDatabaseName = [self.databases objectAtIndex:[[mainTableView indexPathForSelectedRow] row]];
NSURL *url = [[NSURL alloc] initFileURLWithPath:currentDatabaseName];
UIActivityViewController * airDrop = [[UIActivityViewController alloc]
initWithActivityItems:#[url]
applicationActivities:nil];
airDrop.popoverPresentationController.sourceView = self.view;
[self presentViewController:airDrop
animated:YES
completion:nil];
[url release];
[airDrop release];
[viewTemp release];}
This code works and the database successfully gets sent from the sending iOS device to the receiving device. However, the databases are stored in the Documents/Inbox folder (by design I suppose). I simply want to move the received database files from the Inbox folder up one level into the Documents folder. From what I'm reading I need to handle this in openURL in the App Delegate - but am not sure how to go about this. Any help would be greatly appreciated.
Thank you.
Ok - here's what I did to resolve the problem.
(1) I created a handleInboxItems method in the App Delegate.
-(bool) handleInboxItems {
bool success = YES;
// Get the DBAccess object
DBAccess *dbAccess = [[DBAccess alloc] init];
// Get the Func object
Func *funcObject = [[Func alloc] init];
NSMutableArray *docDatabases;
// get a list of all database files in the Documents/Inbox folder ans store them in the inboxDatabases array
NSMutableArray *inboxDatabases = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *inboxDirectory = [documentsDirectory stringByAppendingPathComponent:#"Inbox"];
NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:inboxDirectory];
for (NSString *inboxFileAndPath in directoryEnumerator)
{
//check to see if any of the files in the inbox folder end in the database extension - if so then save it in the inboxDatabases array
if ([[inboxFileAndPath pathExtension] isEqualToString:#“dbext”])
{
[inboxDatabases addObject:[inboxDirectory stringByAppendingPathComponent:inboxFileAndPath]];
}
}
// now go through the inboxDatabases array and copy them from the Documents/Inbox folder to the Documents folder
// loop through all inbox database and see if any of the database names already exist in Documents - if so then we need to tack on a sequential number
for (NSString *inboxDatabaseFileAndPath in inboxDatabases)
{
NSString *inboxDatabaseName = [[inboxDatabaseFileAndPath lastPathComponent] stringByDeletingPathExtension];
// Get the databases array from the DBAccess class (from the Documents folder) - need to get each time since we are moving files in there
docDatabases = [dbAccess getAllDatabases];
// does the inbox database already exist in the documents folder?
NSUInteger arrayIndex = [docDatabases indexOfObject:[funcObject databaseNameToFullPathName:allTrim(inboxDatabaseName)]];
int i = 0;
while (arrayIndex != NSNotFound)
{
++i;
NSString *tempDatabaseName = [NSString stringWithFormat:[inboxDatabaseName stringByAppendingString:#" %d"],i];
// see if the database (with sequential number) already exists
arrayIndex = [docDatabases indexOfObject:[funcObject databaseNameToFullPathName:allTrim(tempDatabaseName)]];
if (arrayIndex == NSNotFound)
{
// it does not exist, we can use this name
inboxDatabaseName = tempDatabaseName;
}
}
// give it full path and extension
NSString *docDatabaseFileAndPathToWrite = [funcObject databaseNameToFullPathName:allTrim(inboxDatabaseName)];
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
success = [fileManager copyItemAtPath:inboxDatabaseFileAndPath toPath:docDatabaseFileAndPathToWrite error:&error];
if (success)
{
// delete the inbox database file
success = [fileManager removeItemAtPath:inboxDatabaseFileAndPath error:&error];
if (!success)
{
NSAssert1(0,#"Failed to delete inbox database:'%#'.",[error localizedDescription]);
}
}
else
{
NSAssert1(0,#"Failed to copy inbox database to documents folder:'%#'.",[error localizedDescription]);
}
}
[dbAccess release];
[funcObject release];
[inboxDatabases release];
return success;}
(2) Added a call to this new method in the didFinishLaunchingWithOptions in the App Delegate just in case there is anything stuck in the inbox upon startup.
(3) I added the openURL method to the App Delegate in order to call handleInboxItems. After done, I send a notification so that I can refresh my database list.
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
bool success = [self handleInboxItems];
if (success)
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIF_DATABASE_AIRDROPPED object:self];
return success;}
That's it - works as I need it to.
Im trying to implement loading progress by MBProgressHUD.
Here is my code:
//Previously defined #property(nonatomic) NSString *sec;
-(void) viewDidLoad{
HUD = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUD];
HUD.delegate = self;
[HUD showWhileExecuting:#selector(loadData) onTarget:self withObject:nil animated:YES];
//testing the "return" value
NSLog(#"%#", sec);
}
-(void) loadData{
//get data from file named "results.csv"
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
NSString *saving = [docPath stringByAppendingPathComponent:#"results.csv"];
if([[NSFileManager defaultManager] fileExistsAtPath: saving]){
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:saving];
NSString *dataResults = [[NSString alloc]initWithData:[fileHandle availableData] encoding:NSUTF8StringEncoding];
[fileHandle closeFile];
sec = dataResults;
sec = [sec substringFromIndex: [sec length] - 20];
}
}
As you can see in the "loadData" method I need to set the "sec" NSString as the content that saved in the "results.csv" file.
You can see another thing, that after the HUD code: "showWhileExecuting" I was put NSLog to test if it really gets the content of the file and it returns (NULL)!!
Before I added this HUD thing, It did get the real value from the file, but now its return null.
Off-course when I put the NSLog code line at the end of the "loadData" method, it prints a real value instead of (NULL).
So, the big problem that I cant get the variables from the HUD method.
The only solution that I was thinking of, was to create another file and there store all the new variables, but its not help's me.
Anyone know what Im talking about and can help me?
What you see is normal. Your NULL showing NSLog is executed immediately after the showWhileExecuting call, before the loadData has been scheduled to run. You should implement the delegate protocol MBProgressHUDDelegate,
- (void)hudWasHidden:(MBProgressHUD *)hud{
NSLog(#"%#", sec);
}
When loadData completes, the HUD will hide, and call this method. That is when the result of loadData is available to you.
obcit: not tested, from memory, with an 'antique' MBProgressHUD , but a good chance it should work for you.
I use this code to save some PDF data to a file, send it to another app using the "Open In" menu, then delete the file when that's done:
- (void)openIn:(NSData *)fileData {
// save the PDF data to a temporary file
NSString *fileName = [NSString stringWithFormat:#"%#.pdf", self.name];
NSString *filePath = [NSString stringWithFormat:#"%#/Documents/%#", NSHomeDirectory(), fileName];
BOOL result = [fileData writeToFile:filePath atomically:TRUE];
if (result) {
NSURL *URL = [NSURL fileURLWithPath:filePath];
UIDocumentInteractionController *controller = [[UIDocumentInteractionController interactionControllerWithURL:URL] retain];
controller.delegate = self;
[controller presentOpenInMenuFromBarButtonItem:self.openInButton animated:TRUE];
}
}
- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller {
// when the document interaction controller finishes, delete the temporary file
NSString *fileName = [NSString stringWithFormat:#"%#.pdf", self.name];
NSString *filePath = [NSString stringWithFormat:#"%#/Documents/%#", NSHomeDirectory(), fileName];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
}
This has worked fine until iOS 8. Now, the file is created and I can verify that it contains the correct content, the Open In menu appears, I can select an app, and the delegate method runs and cleans up the file. But instead of iOS switching to the selected app and copying the file into it as it did before, the Open In menu simply closes when I select an app, and the file is not copied.
This works if I give the UIDocumentInteractionController an existing file. It also works if I use the provided fileData but change the destination filename to the filename of an existing file. This suggests a permissions problem -- as if new files are created in iOS 8 with default permissions that UIDocumentInteractionController can't read.
Does anyone know what's happening and how I can work around it?
It looks like the order of operations has changed slightly in iOS 8. DidDismissOpenInMenu used to run after the file was finished sending, but now it runs after the file begins sending. This means my cleanup code was sometimes running before the file was finished sending, leaving no file to send. I figured this out after noticing that smaller files were being sent okay; apparently the processing for smaller files was finishing before my cleanup code got them, but the processing for larger files was not.
To ensure the correct timing, but also clean up files that are created when the user opens the DocumentInteractionController and then dismisses the controller without doing anything, I changed my methods like this:
- (void)openIn:(NSData *)fileData {
// save the PDF data to a temporary file
NSString *fileName = [NSString stringWithFormat:#"%#.pdf", self.name];
NSString *filePath = [NSString stringWithFormat:#"%#/Documents/%#", NSHomeDirectory(), fileName];
BOOL result = [fileData writeToFile:filePath atomically:TRUE];
if (result) {
self.sendingFile = FALSE;
NSURL *URL = [NSURL fileURLWithPath:filePath];
UIDocumentInteractionController *controller = [[UIDocumentInteractionController interactionControllerWithURL:URL] retain];
controller.delegate = self;
[controller presentOpenInMenuFromBarButtonItem:self.openInButton animated:TRUE];
}
}
- (void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application {
// the user chose to send the file, so we shouldn't clean it up until that's done
self.sendingFile = TRUE;
}
- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller {
if (!self.sendingFile) {
// the user didn't choose to send the file, so we can clean it up now
[self openInCleanup];
}
}
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
// the user chose to send the file, and the sending is finished, so we can clean it up now
[self openInCleanup];
self.sendingFile = FALSE;
}
- (void)openInCleanup {
// delete the temporary file
NSString *fileName = [NSString stringWithFormat:#"%#.pdf", self.name];
NSString *filePath = [NSString stringWithFormat:#"%#/Documents/%#", NSHomeDirectory(), fileName];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
}
Update for iOS 11
Before iOS 11, it seems that the operating system kept a copy of the file available until the receiving app was finished reading it, even though my cleanup function ran as soon as the file was sent out from my app. In iOS 11, this changed and the receiving app fails to read the file because my app deletes it before that's done. So now instead of saving the temporary file to Documents and using the openInCleanup method to delete it immediately, I'm saving the temporary file to tmp and emptying the tmp folder next time the app launches. This approach should also work with older iOS versions. Just remove openInCleanup, change Documents to tmp in the paths, and add this to applicationDidFinishLaunching:
// clear the tmp directory, which will contain any files saved for Open In
NSString *tmpDirectory = [NSString stringWithFormat:#"%#/tmp", NSHomeDirectory()];
NSArray *tmpFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:tmpDirectory error:NULL];
for (NSString *tmpFile in tmpFiles) {
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#/%#", tmpDirectory, tmpFile] error:NULL];
}
After reading this post, I already hoped to have found the solution to a similar problem:
For me, as of iOS 8, sharing was only working with Mail.app. It was failing for Dropbox, etc.
Turns out it was something else:
On my interactionController I was setting an annotation like this:
interactionController.annotation = #"Some text"
For unknown reasons, this prevented Dropbox to open at all. There were no error messages or anything. Removing this line solved the issue.
This is the function to download images , in this function i have to update the UI, the UILabels that shows total images and counter value (downloaded images).
thats why i am calling a thread.
-(void) DownloadImages
{
NSLog(#"Images count to download %lu",(unsigned long)[productID count]);
if ([productID count] > 0)
{
// Document Directory
NSString *myDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
for (i = 0; i < [productID count]; i++)
{
[NSThread detachNewThreadSelector: #selector(UpdateLabels) toTarget:self withObject:nil];
// [self UpdateLabels]; this does not work….
NSLog(#"image no %d", i);
//[self Status];
NSString *imageURL = [NSString stringWithFormat:#"http://serverPath/%#/OnlineSale/images/products/%#img.jpg",clientSite,[productID objectAtIndex:i]];
UIImage *image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
// Reference path
NSString *imagePath = [NSString stringWithFormat:#"%#/%#-1-lrg.jpg",myDirectory,[productID objectAtIndex:i]];
NSData *imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0f)];
[imageData writeToFile:imagePath atomically:YES];
}
NSLog(#"Downloading images completed...");
}
}
Problem:
- here for every new image i have to call every time a new NSThread, there may be thousands of images and it looks bad that there will be thousands threads.
in simulator i have tested it for 3000 images, but in my iPad when testing it gives the error,,,
App is Exited and terminated due to memory pressure.
i guess the error is due to this calling approach of new thread every time.
what i am doing wrong here.?????????????
i have searched there are two different options but i don't know which one is right option,
Options are:
- [self performSelectorInBackground:#selector(UpdateLabels) withObject:nil];
- [NSThread detachNewThreadSelector: #selector(UpdateLabels) toTarget:self withObject:nil];
and i also want to know that if i use
[NSThread cancelPreviousPerformRequestsWithTarget:self];
just after the call of selector, is it right approach or not..???
My suggestion is to use NSOperationQueue . you could then set the maxConcurrentOperationCount property to whatever you like.
In addition in your case is the best approach.
Make some research on what are your maxConcurrentOperationCount with how many concurrent thread your iphone could run without any issue.
NSOperationQueue will manage this for you run new thread with this limitation.
It should look something like this:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue setMaxConcurrentOperationCount:500];
for (i = 0; i < [productID count]; i++) {
[myQueue addOperationWithBlock:^{
// you image download here
}];
}
but check apple reference first.
I want to implement a progress bar in my application. The process happening is the app copying a directory into the iOS documents directory. Usually takes anywhere from 7-10 seconds (iPhone 4 tested). My understanding of progress bars is you update the bar as things are happening. But based on the copying of the directory code, I am not sure how to know how far along it is.
Can anyone offer any suggestions or examples on how this can be done? Progress bar code are below and the copying of the directory code.
Thanks!
UIProgressView *progressView = [[UIProgressView alloc] initWithProgressViewStyle: UIProgressViewStyleBar];
progressView.progress = 0.75f;
[self.view addSubview: progressView];
[progressView release];
//Takes 7-10 Seconds. Show progress bar for this code
if (![fileManager fileExistsAtPath:dataPath]) {
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *imageDataPath = [bundlePath stringByAppendingPathComponent:_dataPath];
if (imageDataPath) {
[fileManager copyItemAtPath:imageDataPath toPath_:dataPath error:nil];
}
}
define NSTimer *timer in your .h file
if (![fileManager fileExistsAtPath:dataPath]) {
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *imageDataPath = [bundlePath stringByAppendingPathComponent:_dataPath];
timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:#selector(updateProgressView) userInfo:nil repeats:YES];
[timer fire];
if (imageDataPath) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[fileManager copyItemAtPath:imageDataPath toPath_:dataPath error:nil];
};
}
}
and add this method
- (void) updateProgressView{
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *imageDataPath = [bundlePath stringByAppendingPathComponent:_dataPath];
NSData *allData = [NSData dataWithContentsOfFile:imageDataPath];
NSData *writtenData = [NSData dataWithContentsOfFile:dataPath];
float progress = [writtenData length]/(float)[allData length];
[pro setProgress:progress];
if (progress == 1.0){
[timer invalidate];
}
}
If it takes this long because there are many files in that directory, you could copy the files one by one in a loop. To determine the progress, you could/should simply assume that copying each files takes the same amount of time.
Note that you don't want to block the UI for this 7-10 seconds, so you need to copy on a separate non-main thread. Setting the progress bar, like all UI code, needs to be done on the mean thread using:
dispatch_async(dispatch_get_main_queue(), ^
{
progressBar.progress = numberCopied / (float)totalCount;
});
The cast to float gives you slightly (depending on number of files) better accuracy, because a pure int division truncates the remainder.