Show UIAlertView during UIActivity:activityViewController - iphone

I have a set of UIActivities where I prepare my data into a given format and then attach it to an email the user can send. I'm using a subclass of UIActivity and I'm doing all the work in -(void)activityViewController:
- (UIViewController *)activityViewController
{
[self.alert show];
NSString *filename = [NSString stringWithFormat:#"%#.gpx", self.activity.title];
__block MFMailComposeViewController *mailComposeVC = [[MFMailComposeViewController alloc] init];
mailComposeVC.mailComposeDelegate = self;
[mailComposeVC setSubject:[NSString stringWithFormat:#"GPX export for %# activity", self.activity.title]];
[mailComposeVC setMessageBody:#"Generated with Slopes" isHTML:NO];
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
CBCFileExporter *exporter = [[CBCFileExporter alloc] init];
NSData *exportContents = [exporter exportActivity:self.activity inFileFormat:CBCFileExportTypeGPX error:nil];
[mailComposeVC addAttachmentData:exportContents mimeType:#"application/gpx+xml" fileName:filename];
});
[self.alert dismissWithClickedButtonIndex:0 animated:YES];
return mailComposeVC;
}
The specific issue I'm running into is that the UIAlertView doesn't actually show until the dispatch_sync completes. I realize the dispatch_sync might(?) be blocking the main thread as it waits, but the problem is I need to wait until the attachment is generated before returning from that method call (MFMailComposeViewController docs say you can't add attachment once the view is presented).
How can I get an alertview to show while a non-trivial task the main thread has to wait for completion has to run?

Given that the mail view controller specifically disallows adding an attachment to the mail compose view controller once it has been presented, what you probably need to do here is create and present an "interstitial" view controller with an indeterminate progress indicator, start the export process in the background, and then when that process is complete, create and fully populate the mail compose view controller with the attachment, then present it.
That requirement that it be fully populated before being presented means that there won't be a simple "do this in the background and call me back" approach possible.

Ick.
For what it's worth, I had to give up (after 4 hours of fighting with all kinds of blocks, performOnThread, etc) on using the activityViewController method to directly return a UI and instead switch to the performActivity method. PerformActivity is supposed to be for UI-less activities, but it's the only async-compatable one.
I have to set my main ViewController (the one showing the activity sheet) as a delegate to the UIActivities, then call my delegate back with the message VC once the export is ready:
- (void)performActivity
{
__block UIAlertView *alert = [[UIAlertView alloc] init];
alert.title = #"Generating Export";
[alert show];
//get rid of the activity sheet now - can't present the mail modal if this is active
[self activityDidFinish:YES];
__block CBCGPXEmailActivity *weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
CBCFileExporter *exporter = [[CBCFileExporter alloc] init];
NSString *filename = [NSString stringWithFormat:#"%#.gpx", weakSelf.activity.title];
NSData *exportContents = [exporter exportActivity:weakSelf.activity inFileFormat:CBCFileExportTypeGPX error:nil];
//dispatch after to make sure there was time to remove the action sheet
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
MFMailComposeViewController *mailComposeVC = [[MFMailComposeViewController alloc] init];
[mailComposeVC setSubject:[NSString stringWithFormat:#"GPX export for %# activity", weakSelf.activity.title]];
[mailComposeVC setMessageBody:#"Generated with Slopes" isHTML:NO];
[mailComposeVC addAttachmentData:exportContents mimeType:#"application/gpx+xml" fileName:filename];
[weakSelf.delegate showMailViewController:mailComposeVC];
[alert dismissWithClickedButtonIndex:0 animated:YES];
});
});
}

Related

Objective-C Tested app on iPhone, updated data, data doesnt change

I just built my first app and now I started testing it on my phone. It looks great when I first launch the app after building it, the launch images appears and then my json data is loaded via NSURL and displays properly on the app. But when I close down the app, update the data via php and mysql and re open it the launch image does not appear and my app is not updated. Is it possible to have the app launch like it did when I first launched it, always have launch image and also get the new data?
Here is my code if it helps.
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadJSON];
}
- (void)loadJSON
{
Reachability *networkReachability = [Reachability reachabilityForInternetConnection];
NetworkStatus networkStatus = [networkReachability currentReachabilityStatus];
if (networkStatus == NotReachable) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"Error" message: #"Connection Failed" delegate: self cancelButtonTitle:#"Refresh" otherButtonTitles:nil]; [alert show]; [alert release];
});
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:#"http://url.com/GetData.php"];
NSData *data = [NSData dataWithContentsOfURL:url options:0 error:nil];
NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSArray *firstItemArray = array[0];
NSString *yesNoString = firstItemArray[0];
NSString *dateString = firstItemArray[1];
NSString *timeString = firstItemArray[2];
NSString *homeString = firstItemArray[3];
NSString *awayString = firstItemArray[4];
NSString *lastUpdatedString = firstItemArray[5];
dispatch_async(dispatch_get_main_queue(), ^{
self.YesOrNo.text = yesNoString;
self.date.text = [#"For " stringByAppendingString:dateString];
self.time.text = timeString;
self.home.text = homeString;
self.away.text = awayString;
self.lastUpdated.text = lastUpdatedString;
self.lastUpdatedText.text = #"Last Updated";
self.vs.text = #"vs";
});
});
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
[self loadJSON];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)dealloc {
[_YesOrNo release];
[_date release];
[_time release];
[_vs release];
[_home release];
[_away release];
[_lastUpdatedText release];
[_lastUpdated release];
[super dealloc];
}
#end
If someone can point me in the right direction, that would be great and appreciated, thanks.
When you hit the round home button when running an app, it just puts it into the background, where it continues to run in a type of catatonic state. When you tap its icon again, it just wakes up, and doesn't re-launch. If you'd like to have your app completely quit when the user hits the home button, use the info.plist option "UIApplicationExitsOnSuspend" also known as "Application does not run in background." Set this to YES in your info.plist, and you'll get a fresh start every time. You can access this by clicking on your project in Xcode in the Project Navigator mode, and select the "info" tab at the top middle.
As it is your first app, you should try reading about the various devices and os versions.
Read about various application states, also the methods that are present in the AppDelegateClass, that get called when the app enters into various states,try reading about them.
So what has happened in your case is the device that you are using is Multitasking one. So when you press the home button or the sleep button the game goes to background, and is not killed. So next time when you tap on the application icon on your device, it brings it back to the foreground and does not relaunch it hence your Viewdidload method won't be called and your changes won't get reflected.
So, now to terminate your app, you can go through this link
http://www.gottabemobile.com/2013/02/10/how-to-close-apps-on-iphone/
Hope this helps.

Updating Interface from Result of NSURL Request

I have an app that updates the interface depending on the result of a NSURL request. I have set it up so that the request is fired when my app comes into the foreground, and only if the current view controller is called "ProfileViewController".
My problem is that the interface locks up for a few seconds every time I bring the app back from the background. I am trying to fully understand main/background threads, but am not sure what I can do to make the app remain responsive while the NSURL check is being performed. Any assistance would be great! Thanks!
In my View Did Load Method:
-(void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(appReturnsActive) name:UIApplicationDidBecomeActiveNotification
object:nil];
//additional code
}
Then in my App Returns Active Method:
- (void)appReturnsActive {
[myTimer invalidate];
myTimer = nil;
//ONLY WANT TO PERFORM THE UPDATE IF VIEWING THE PROFILE VIEW CONTROLLER
UIViewController *currentVC = self.navigationController.visibleViewController;
NSString * name = NSStringFromClass([currentVC class]);
if ([name isEqualToString:#"ProfileViewController"]) {
[activityIndicator startAnimating];
[activityIndicatorTwo startAnimating];
locationManagerProfile.delegate = self;
locationManagerProfile.desiredAccuracy = kCLLocationAccuracyBest;
[locationManagerProfile startUpdatingLocation];
}
}
Finally, in my Did Update Location Method, I get the distance between the user and the location. If the result is equal to 1, then I update the Interface to show different buttons. This is where the interface freezes up:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation
*)newLocation fromLocation:(CLLocation *)oldLocation {
CLLocation *currentLocation = newLocation;
if (currentLocation != nil) {
[self performSelectorOnMainThread:#selector(buttonUpdate:)
withObject:NULL waitUntilDone:NO];
}
}
New Method:
-(void)buttonUpdate {
NSString *userLongitude =[NSString stringWithFormat:#"%.8f",
currentLocation.coordinate.longitude];
NSString *userLatitude = [NSString stringWithFormat:#"%.8f",
currentLocation.coordinate.latitude];
[locationManagerProfile stopUpdatingLocation];
NSString *placeLatitude = [[NSUserDefaults standardUserDefaults]
stringForKey:#"savedLatitude"];
NSString *placeLongitude = [[NSUserDefaults standardUserDefaults]
stringForKey:#"savedLongitude"];
NSString *distanceURL = [NSString
stringWithFormat:#"http://www.website.com/page.php?
lat1=%#&lon1=%#&lat2=%#&lon2=%#",userLatitude, userLongitude, placeLatitude,
placeLongitude];
NSData *distanceURLResult = [NSData dataWithContentsOfURL:[NSURL
URLWithString:distanceURL]];
NSString *distanceInFeet = [[NSString alloc] initWithData:distanceURLResult
encoding:NSUTF8StringEncoding];
if ([distanceInFeet isEqualToString:#"1"])
{
UIBarButtonItem *btnGo = [[UIBarButtonItem alloc] initWithTitle:#"Button 1"
style:UIBarButtonItemStyleBordered target:self
action:#selector(actionTwo)];
self.navigationItem.rightBarButtonItem = btnGo;
UIBarButtonItem *btnGoTwo = [[UIBarButtonItem alloc] initWithTitle:#"Button
Two" style:UIBarButtonItemStyleBordered target:self
action:#selector(actionOne)];
self.navigationItem.rightBarButtonItem = btnGoTwo;
self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:btnGo,
btnGoTwo, nil];
}
}
Your CoreLocation delegate methods (e.g. "didUpdateToLocation", etc.) are all happening in the background, on secondary threads, while everything UI-related needs to happen on the main thread.
To fix your problem, you should modify your UI on the main thread. One of the handy foundation API's you can use is:
performSelectorOnMainThread:withObject:waitUntilDone:
To fix your problem, move your button-creating code into a separate method and call that new method via "performSelectorOnMainThread" from the old (being called on a separate thread via the CoreLocation delegate protocol) and you should be good to go.
NSData *distanceURLResult = [NSData dataWithContentsOfURL:[NSURL
URLWithString:distanceURL]];
is a synchronous call. It means your code will wait for the result of the network call to continue. You should make an Asynchronous request, with NSURLConnection (http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html) or AFNetworking.

Simulating sent SMS on iPhone

Since debugging is extremely slow with Xcode 4.3 on iOS 5.1 when starting/installing the app on the device I use the simulator which starts much faster. (see my question regarding this issue here https://stackoverflow.com/questions/11541288/xcode-4-3-with-ios5-1-pauses-about-10secs-when-debug-starts-simulator-starts-i)
So all I need to do is something like this:
MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
picker.messageComposeDelegate = delegate;
NSString *s = #"1234567";
picker.recipients =[NSArray arrayWithObject: s];
picker.body =smsTxt;
if (simulationMode) {
MessageComposeResult result = MessageComposeResultSent; <-----------
[delegate messageComposeViewController:picker didFinishWithResult: result];
} else
[delegate presentModalViewController:picker animated:YES];
Here the problem is now that when executing on iOS-Simulator the MFMessageComposeViewController can't be instantiated and always yields nil.
Is there a way to create another object MyOwnMFMessageComposeViewController on iOS simulator which is compatible to MFMessageComposeViewController and can be passed in the same method like MFMessageComposeViewController?
Something like this:
MFMessageComposeViewController *picker = [[MFMessageComposeViewController alloc] init];
picker.messageComposeDelegate = delegate;
NSString *s = #"1234567";
picker.recipients =[NSArray arrayWithObject: s];
picker.body =smsTxt;
if (simulationMode) {
MyOwnMFMessageComposeViewController *mypicker = [[MFMessageComposeViewController alloc] init];
mypicker.messageComposeDelegate = delegate;
NSString *s = #"1234567";
mypicker.recipients =[NSArray arrayWithObject: s];
mypicker.body =smsTxt;
MessageComposeResult result = MessageComposeResultSent;
picker = (MFMessageComposeViewController) mypicker;
[delegate messageComposeViewController:picker didFinishWithResult: result];
} else
[delegate presentModalViewController:picker animated:YES];
What you are looking for is called a 'mock object' and is often used in test driven development. Basically what you do is create a subclass of MFMessageComposeViewController. This subclass works exactly the same as mfmessagecomposeviewcontroller except you also create instance variables to show that something has happened.
So for example when your delegate calls messageComposeViewController:didFinishWithResult. The mock object would likely store the result and a flag that that method had been fired. Note that this won't actually send anything, but simply tells you that the delegate fired and on a real object will work.

how to reflect changes in the database without running the app again?

I am(new to iPhone) developing an iPhone App which tracks the data Usage(wifi,cellular). I am giving an option to the user to add new plans which will be put into a table in the database(sqlite).
I am displaying the list of plans in a picker view and I want to refresh the data in the picker view as soon as user enters a new plan. As of now pickerview is getting updated in the next run :(
thanks in advance.
code is here:
actionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:nil cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
[actionSheet setActionSheetStyle:UIActionSheetStyleBlackTranslucent];
CGRect pickerFrame = CGRectMake(0, 40, 0, 0);
pickerView = [[UIPickerView alloc] initWithFrame:pickerFrame];
pickerView.showsSelectionIndicator = YES;
pickerView.dataSource = self;
pickerView.delegate = self;
[actionSheet addSubview:pickerView];
plans= [[NSMutableArray alloc] init];
NSLog(#"Data base is entering");
NSString *path1 = [[NSBundle mainBundle] pathForResource:#"antara" ofType:#"sqlite"];
FMDatabase *db1 = [[FMDatabase alloc] initWithPath:path1];
[db1 open];
FMResultSet *fResult2= [db1 executeQuery:#"SELECT * FROM main"];
NSLog(#"fresult2 ready");
while ( [fResult2 next])
{
planData = [fResult2 stringForColumn:#"planName"];
[plans addObject:planData];
NSLog(#"The data is =%#",planData);
}
[db1 close];
[pickerView selectRow:1 inComponent:0 animated:NO];
//mlabel.text= [plans objectAtIndex:[pickerView selectedRowInComponent:0]];
//[self setCurrentPlan:[plans objectAtIndex:[pickerView selectedRowInComponent:0]]];
//[mlabel setText:[self getCurrentPlan]];
currentPlan= [plans objectAtIndex:[pickerView selectedRowInComponent:0]];
[plan setText:currentPlan];
[pickerView release];
I think you need some separation in your code. Try this and tell me if it works
1) Take out the logic of pulling data from DB into a separate function, lets call it loadPlans.
- (void) loadPlans {
plans= [[NSMutableArray alloc] init];
NSLog(#"Data base is entering");
NSString *path1 = [[NSBundle mainBundle] pathForResource:#"antara" ofType:#"sqlite"];
FMDatabase *db1 = [[FMDatabase alloc] initWithPath:path1];
[db1 open];
FMResultSet *fResult2= [db1 executeQuery:#"SELECT * FROM main"];
NSLog(#"fresult2 ready");
while ( [fResult2 next])
{
planData = [fResult2 stringForColumn:#"planName"];
[plans addObject:planData];
NSLog(#"The data is =%#",planData);
}
[db1 close];
}
2) Obviously this code is taken from the function you had pasted above and hence, remove these lines from that function.
3) Under normal flow: Call loadPlans before the call to the above mentioned function
4) Whenever you have something inserted in the database. Call these two functions again there
[self loadPlans];
[PickerView reloadComponent:n];
This all assumes that the db entry of the plan is happening on the same page and in the same thread so that you have control over it, where you can call these functions. If this assumption is not true then also explain how and when the plans are getting added to db, paste some code about db entry too here.
all you need to do is to trigger a rebuilding the components you are showing by
[customPickerView reloadComponent:n] // where n = the index of the component
put the trigger code somewhere after the loading has finished.
You may also use something like this to check if the pickerView is visible and if you really need to update the screen:
if ([screenUpdateDelegate respondsToSelector:#selector(whateverIneedToDo:)]) {
[screenUpdateDelegate whateverIneedToDo:withParameterIfYouLike];

iPhone application lags after launching and dismissing MFMailComposeViewController

I have an application that uses a table view controller to display some items, after clicking on one of those items you may select to email this item. Once that happens I use the code provided by apple "MailComposer", and send the mail. However after this the scrolling in the table view is not as smooth as before.
I checked with "Leaks" and there are no leaks in my code, however there is a great deal of object allocation when the modal view controller for the MFMailComposeViewController, and when i dismiss my controller, all that object allocation is still there. How can i get rid of all that object allocation?. Any help will be greatly appreciated. Thank you.
-Oscar
UPDATE:
I have realized the lag only happens once you click on the To: textfield on the MFMailComposeViewController and type something, once something has been typed there will be a memory leak and the application will be sluggish. This exact same thing also happens in Apple's Mail Composer. I am using the simulator maybe this is why?. Does anyone else have a simmilar experience?
The way I am pressenting my controller is:
-(void)displayComposerSheet
{
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
NSString *mailSubject = appDelegate.mailTitle;
NSString *mailBody = appDelegate.mailLink;
NSString *formattedString = [NSString stringWithFormat:#"<a href='%#'>%#</a>", mailBody, mailBody];
[picker setSubject:mailSubject];
// Set up recipients
//NSArray *toRecipients = [NSArray arrayWithObject:#"somemail#hotmail.com"];
//NSArray *ccRecipients = [NSArray arrayWithObjects:#"second#example.com", #"third#example.com", nil];
//NSArray *bccRecipients = [NSArray arrayWithObject:#"fourth#example.com"];
//[picker setToRecipients:toRecipients];
//[picker setCcRecipients:ccRecipients];
//[picker setBccRecipients:bccRecipients];
// Attach an image to the email (Warning this causes a memory leak aknowledged by Apple)
//NSString *path = [[NSBundle mainBundle] pathForResource:#"news_icon" ofType:#"png"];
//NSData *myData = [NSData dataWithContentsOfFile:path];
//[picker addAttachmentData:myData mimeType:#"image/png" fileName:#"rainy"];
// Fill out the email body text
[picker setMessageBody:formattedString isHTML:YES];
[self presentModalViewController:picker animated:YES];
[picker release];
}
and dimissing it here:
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
....
[self dismissModalViewControllerAnimated:YES];
}
It's a known memory leak in MFMailComposeViewController class (as of iOS 4.2 SDK). The leaks can be even seen in the MailComposer sample project by Apple. Try to run the app with Allocations instrument and notice the Overall Bytes growing up every time you click cancel and show the composer again.
See below for the similar discussion:
http://discussions.apple.com/thread.jspa?threadID=2158170
https://devforums.apple.com/thread/23510?tstart=15
https://devforums.apple.com/message/121093#121093
Make sure you use
controller.mailComposeDelegate = self;
and not
controller.delegate = self;