Show waiting view when sending mail using smtp connection ios? - email

I am working on a app in which I am sending mail using mail-core engine. I have created my own viewController to send mail. I want to show a waiting view when mail sending is in process. My waiting view always displays after mail sending has been done. Is it some kind of threading issue?
Here is the code which I'm using to send mail.
- (IBAction) sendTapped:(id) sender {
[txtfSubject resignFirstResponder];
[txtfReceptient resignFirstResponder];
[txtvMessageBody resignFirstResponder];
[self setTo:txtfReceptient.text];
[self setFrom:username];
[self setSubject:txtfSubject.text];
[self setBody:txtvMessageBody.text];
[self performSelector:#selector(prepareAndSendMail) withObject:nil afterDelay:0.34];
}
- (void) prepareAndSendMail {
[WNAppDelegate performSelectorOnMainThread:#selector(showWaitingView) withObject:nil waitUntilDone:NO];
//TODO: send mail here
CTCoreMessage *msg = [[CTCoreMessage alloc] init];
[msg setTo:[myMessage to]];
[msg setFrom:[myMessage from]];
//Encode message here
NSString *encodedMessage = nil;
#try {
encodedMessage = [self encodeMessage:txtvMessageBody.text];
}
#catch (NSException * e) {
NSLog(#"An exception occurred while encoding message");
}
#finally {
if(encodedMessage){
[msg setBody:encodedMessage];
}
}
[msg setSubject:[myMessage subject]];
BOOL success = [self sendMailOnAnotherThread:msg];
[msg release];
[WNAppDelegate performSelectorOnMainThread:#selector(removeWaitingView) withObject:nil waitUntilDone:NO];
//[appDelegate removeWaitingView];
if(!success) {
UIAlertView * empty_alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Could not send."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[empty_alert show];
[empty_alert autorelease];
return;
}
else {
//Message sent successfully
if(self.target && [self.target respondsToSelector:#selector(messageSentSuccessfully)]){
[self.target messageSentSuccessfully];
}
WN_POST_NOTIFICATION(kMessageSentSuccessfully,nil);
}
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL) sendMailOnAnotherThread:(CTCoreMessage*)message {
BOOL success = YES;
BOOL auth = YES;
BOOL tls = YES;
#try {
[CTSMTPConnection sendMessage:message server:GMAIL_SERVER username:username
password:password port:GMAIL_PORT_Number useTLS:tls useAuth:auth];
}
#catch (NSException * e) {
//Msg failed to send;
success = FALSE;
}
return success;
}

Ok guys,Thanks for all the info you have provided. The problem is now solved.
I'M posting here my code in case some one need it.
- (IBAction) sendTapped:(id) sender {
[txtfSubject resignFirstResponder];
[txtfReceptient resignFirstResponder];
[txtvMessageBody resignFirstResponder];
[self setTo:txtfReceptient.text];
[self setFrom:username];
[self setSubject:txtfSubject.text];
[self setBody:txtvMessageBody.text];
[self performSelector:#selector(prepareAndSendMail) withObject:nil afterDelay:0.34];
}
- (void) prepareAndSendMail {
//[((WalnutAppDelegate*)WNAppDelegate) performSelectorOnMainThread:#selector(showWaitingView) withObject:nil waitUntilDone:NO];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSThread *aNewThread = [[[NSThread alloc] initWithTarget:((WalnutAppDelegate*)WNAppDelegate) selector:#selector(showWaitingView) object:nil] autorelease];
[aNewThread start];
//[NSThread detachNewThreadSelector: toTarget:((WalnutAppDelegate*)WNAppDelegate) withObject:nil];
//TODO: send mail here
CTCoreMessage *msg = [[CTCoreMessage alloc] init];
[msg setTo:[myMessage to]];
[msg setFrom:[myMessage from]];
//Encode message here
NSString *encodedMessage = nil;
#try {
encodedMessage = [self encodeMessage:txtvMessageBody.text];
}
#catch (NSException * e) {
NSLog(#"An exception occurred while encoding message");
}
#finally {
if(encodedMessage){
[msg setBody:encodedMessage];
}
}
[msg setSubject:[myMessage subject]];
BOOL success = [self sendMailOnAnotherThread:msg];
[msg release];
//[NSThread detachNewThreadSelector:#selector(removeWaitingView) toTarget:((WalnutAppDelegate*)WNAppDelegate) withObject:nil];
[((WalnutAppDelegate*)WNAppDelegate) performSelectorOnMainThread:#selector(removeWaitingView) withObject:nil waitUntilDone:NO];
[pool drain];
if(!success) {
UIAlertView * empty_alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Could not send."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[empty_alert show];
[empty_alert autorelease];
return;
}
else {
//Message sent successfully
if(self.target && [self.target respondsToSelector:#selector(messageSentSuccessfully)]){
[self.target messageSentSuccessfully];
}
WN_POST_NOTIFICATION(kMessageSentSuccessfully,nil);
}
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL) sendMailOnAnotherThread:(CTCoreMessage*)message {
BOOL success = YES;
BOOL auth = YES;
BOOL tls = YES;
#try {
[CTSMTPConnection sendMessage:message server:GMAIL_SERVER username:username
password:password port:GMAIL_PORT_Number useTLS:tls useAuth:auth];
}
#catch (NSException * e) {
//Msg failed to send;
success = FALSE;
}
return success;
}
- (void)showWaitingView {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CGRect frame = CGRectMake(90, 190, 32, 32);
UIActivityIndicatorView* progressInd = [[UIActivityIndicatorView alloc] initWithFrame:frame];
[progressInd startAnimating];
progressInd.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
frame = CGRectMake(130, 193, 140, 30);
UILabel *waitingLable = [[UILabel alloc] initWithFrame:frame];
waitingLable.text = #"Processing...";
waitingLable.textColor = [UIColor whiteColor];
waitingLable.font = [UIFont systemFontOfSize:20];;
waitingLable.backgroundColor = [UIColor clearColor];
frame = [[UIScreen mainScreen] applicationFrame];
UIView *theView = [[UIView alloc] initWithFrame:frame];
theView.backgroundColor = [UIColor blackColor];
theView.alpha = 0.7;
theView.tag = 999;
[theView addSubview:progressInd];
[theView addSubview:waitingLable];
[progressInd release];
[waitingLable release];
[window addSubview:[theView autorelease]];
[window bringSubviewToFront:theView];
[pool drain];
}
- (void)removeWaitingView {
UIView *v = [window viewWithTag:999];
if(v) [v removeFromSuperview];
}

Yes it is. You need to return to run loop in order to get UI updated. So it is best to display the waiting view in main thread, send mail in background thread and then again hide and remove waiting view in main thred. You should only update UI from main thread. You can use performSelectorInBackground and performSelectorOnMainThread to do it the easy way without creating threads manually. You can also use dispatch_async like this:
//show waiting view
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//prepare mail here
dispatch_async(dispatch_get_main_queue(), ^{
//send mail
//hide waiting view
});
});

Since you are executing prepareAndSendMail on the main thread, WNAppDelegate performSelectorOnMainThread:#selector(showWaitingView) withObject:nil waitUntilDone:NO]; will call showWaitingView after the current run loop has ended by which time you will have sent the mail. Setting waitUntilDone: to YES will show the waiting view at the time you intend it to.

Related

Webview content is not readable after stop animating loading image

I am building one iPhone application where I have two views one for login view and another for web view. When user enters the login credentials, then it will redirect to web view. while loading the webview from login view too much time is taking to load. hence I used alertview to show loading image.
#interface FocusViewController ()
#end
#implementation FocusViewController
#synthesize txtsecurecode;
#synthesize webView;
#synthesize OrganizationCode;
UIActivityIndicatorView *indicator;
UIAlertView *alert;
- (void)viewDidLoad
{
[super viewDidLoad];
appDelegate = (FocusAppDelegate *)[[UIApplication sharedApplication]delegate];
if(appDelegate.data.strOrganizationCode == nil || appDelegate.data.strAccessCode == nil)
{
NSLog(#"YOUR FIRST SCREEN");
_loginView.hidden = NO;
webView.hidden = YES;
}
else
{
NSLog(#"LOAD WEBVIEW DIRECTLY\n");
NSLog(#"Organization Code -> %# \n Secure Access Code -> %#",appDelegate.data.strOrganizationCode,appDelegate.data.strAccessCode);
OrganizationCode.text = appDelegate.data.strOrganizationCode ;
txtsecurecode.text = appDelegate.data.strAccessCode;
[self loginClicked:nil];
webView.hidden = NO;
_loginView.hidden = YES;
}
}
- (IBAction)loginClicked:(id)sender {
#try {
if([[txtsecurecode text] isEqualToString:#""] || [[OrganizationCode text] isEqualToString:#""] ) {
[self alertStatus:#"Please enter Access code" :#"Login Failed!":0];
}
else
{
NSString *post =[[NSString alloc] initWithFormat:#"txtsecurecode=%# #&password=%#",[txtsecurecode text],[OrganizationCode text]];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://myexample.com/AccountService/security/ValidateAccess?accesscode=%#&companycode=%#&type=1", txtsecurecode.text, OrganizationCode.text]];
NSString *responseData = [[NSString alloc]initWithData:[NSData dataWithContentsOfURL:url] encoding:NSUTF8StringEncoding];
if([responseData isEqualToString:#""]){
[self alertStatus:#"Please enter valid Access Code" :#"Login Failed !" :0];
}
else
{
appDelegate.data.strOrganizationCode = OrganizationCode.text;
appDelegate.data.strAccessCode = txtsecurecode.text;
[FocusAppDelegate addCustomObjectToUserDefaults:appDelegate.data key:kCredentails];
//Updated
_loginView.hidden = YES;
webView.hidden = NO;
responseData = [responseData stringByReplacingOccurrencesOfString:#" "" " withString:#""];
NSString* encodedString = [responseData stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"Response ==> %#" ,encodedString);
alert = [[[UIAlertView alloc] initWithTitle:#"Loading\nPlease Wait..." message:nil delegate:self cancelButtonTitle:nil otherButtonTitles: nil] autorelease];
[alert show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
indicator.center = CGPointMake(alert.bounds.size.width / 2, alert.bounds.size.height - 50);
[indicator startAnimating];
[alert addSubview:indicator];
[indicator release];
webView.backgroundColor = [UIColor clearColor];
webView.opaque = NO;
webView.delegate = self;
webView.frame = self.view.bounds;
NSString* urlTwo = [[encodedString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
stringByReplacingOccurrencesOfString:#"%22" withString:#""];
NSURL *url2;
if([urlTwo hasPrefix:#"http://"]){
url2 = [NSURL URLWithString:urlTwo];
}else{
url2 = [NSURL URLWithString:[NSString stringWithFormat:#"http://%#" , urlTwo]];
}
NSLog(#"url2:%#", url2);
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url2];
[webView loadRequest:requestObj];
[[self view] addSubview:webView];
}
}
}
#catch (NSException * e) {
NSLog(#"Exception: %#", e);
[self alertStatus:#"Login Failed." :#"Login Failed!" :0];
}
}
-(void)webViewDidStartLoad:(UIWebView *)webView{
NSLog(#"webViewDidStartLoad");
[self.view addSubview:self.indicator];
alert.hidden = NO;
}
- (void) webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"webViewDidFinishLoad");
[self.indicator removeFromSuperview];
[self.alert dismissWithClickedButtonIndex:1 animated:NO] ;
}
- (IBAction)backgroundClick:(id)sender
{
[txtsecurecode resignFirstResponder];
[OrganizationCode resignFirstResponder];
}
#end
Until content in webview displays, loading image is displaying and working good. but content of webview is not editable and remaining static as I think I used 'web view.hidden = YES'. when I comment the alert method and run then content of webview is also editable and working good as required. I need to load the content of url after loading image stops loading.
I would suggest show the activity indicator from the Viewcontroller having the WebView and implement the WebView Delegate methods
- (void)webViewDidStartLoad:(UIWebView *)webView
{
[actvityIndicator startAnimating];
}
and
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[actvityIndicator stopAnimating];
}
this will keep your webview Interactive and also show the indicator till the page loads completely
you can try something like this. The Delegate is important in .h file
FocusViewController.h
#interface FocusViewController : UIViewController <UIWebViewDelegate> {
UIActivityIndicatorView *indicator;
}
FocusViewController.m
- (void)viewDidLoad
{
// Loading Indicator
indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[indicator setColor:[UIColor colorWithRed:50.0/255.0 green:120.0/255.0 blue:200.0/255.0 alpha:1.0]];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
[indicator startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[indicator stopAnimating];
}

Crash On MFMailComposeViewController For iPad

- (void) mailshareClick:(UIButton *)sender
{
NSString *_message = #"wait for set up Mail";
[self waitForWhile:_message];
if ([MFMailComposeViewController canSendMail]){
[NSThread detachNewThreadSelector:#selector(mailFunction) toTarget:self withObject:nil];
}
else {
_message = #"Please set Mail account";
[self remove:_message];
}
}
- (void) mailFunction
{
NSData *data = nil;
if ([self.files.imageArr count]>0)
{
XXImage *single = [self.files.imageArr objectAtIndex:0];
UIImage *image = [[SDImageCache sharedImageCache] imageFromKey:[[single.imagearray objectAtIndex:0] columnImage]];
data = [UIImageJPEGRepresentation(image, 1.0f) retain];
}
[self performSelectorOnMainThread:#selector(mailFinished:) withObject:data waitUntilDone:YES];
[data release];
}
- (void) mailFinished:(NSData *)_data
{
if ([MFMailComposeViewController canSendMail]){
NSData *data = [_data retain];
MFMailComposeViewController *message = [[MFMailComposeViewController alloc] init];
//Title
[message setSubject:self.files.title];
//Body
[message setMessageBody:#"111" isHTML:YES];
[message setToRecipients:[NSArray arrayWithObject:#"mail"]];
//Content
if (data != nil) {
NSString *picStr = [[NSString alloc] initWithFormat:#"%#%#",OutsideWebsite_Normal,self.files.middlePicPath];
[message addAttachmentData: data mimeType: #"" fileName:picStr];
[picStr release];
[data release];
}
message.mailComposeDelegate = self;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
message.modalPresentationStyle = UIModalPresentationFormSheet;
}
[self presentModalViewController:message animated:YES];
[self remove:#"Set up Ok"];
[message release];
}
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
switch (result) {
case MFMailComposeResultSent:
{
NSLog(#"MFMailComposeResultSent");
break;
}
case MFMailComposeResultSaved:
{
NSLog(#"MFMailComposeResultSaved");
break;
}
case MFMailComposeResultFailed:
{
NSLog(#"MFMailComposeResultFailed");
break;
}
case MFMailComposeResultCancelled:
{
NSLog(#"MFMailComposeResultCancelled");
break;
}
default:
break;
}
[self performSelector:#selector(delayDismissModalView) withObject:nil afterDelay:1];
}
-(void)delayDismissModalView
{
[self dismissModalViewControllerAnimated:YES];
}
After invoke a few times the email method , there will be
[MFSearchResultsViewController hash]: message sent to deallocated instance
Or
[MFMailComposeViewController hash]: message sent to deallocated instance
crash.
As you think of that, what is the MFSearchResultsViewController function.
Whether it can solve the problem,please give me a hand.
You have to setDelegate for the mail controller. If you set the delegate to "self", make sure that the "self" has <MFMailComposeViewControllerDelegate>. If all these can't make it work, make sure you didn't call presentModalViewController twice.

Receive memory warning after 10 or 11 times capturing image using UIImagePickerControllerSourceTypeCamera in iphone

Hi I receive memory warning whenever I am using camera.
The error is like this:
"Receive memory warning..."
And the code is:
-(void) getPhoto{
GameAppdelegate *appDelegate = (GameAppdelegate *)[[UIApplication sharedApplication]delegate];
UIImagePickerController * picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = YES;
///////////////////////////////////photolibrary//////////////////////////////
if([appDelegate.photoselection isEqualToString:#"User Pressed Button 1\n"])
{
picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
if(appDelegate.sound == 1)
{
[classObj ButtonSound];
}
}
///////////////////////////////////Camera//////////////////////////////
else if([appDelegate.photoselection isEqualToString:#"User Pressed Button 2\n"])
{
#try
{
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
}
#catch (NSException * e)
{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"ALERT"
message:#"Please try again"
delegate:self
cancelButtonTitle:nil
otherButtonTitles:#"ok", nil];
[av show];
}
if(appDelegate.sound == 1)
{
[classObj ButtonSound];
}
}
///////////////////////////////////Cancel//////////////////////////////
else if([appDelegate.photoselection isEqualToString:#"User Pressed Button 3\n"])
{
if(appDelegate.sound == 1)
[classObj ButtonSound];
return;
}
[self presentModalViewController:picker animated:YES];
[picker release];
}
How can i handle this?please help after taking picture i crop the image and save in application like this:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
iRolegameAppDelegate *appDelegate = (iRolegameAppDelegate *)[[UIApplication sharedApplication]delegate];
if(appDelegate.sound == 1)
{
[classObj ButtonSound];
}
[picker dismissModalViewControllerAnimated:YES];
imageView.image = image;
CGSize size = [imageView.image size];
CGRect cropRect = CGRectMake(0.0, 0.0, size.width, size.height);
NSValue *cropRectValue = [editingInfo objectForKey:#"UIImagePickerControllerCropRect"];
cropRect = [cropRectValue CGRectValue];
appDelegate.slectedimage = image;
imageView.hidden = YES;
if( [appDelegate.Name length] != 0 && max_att == 15)
{
btnNotDone.hidden = YES;
btnDone.enabled = YES;
}
//IMAGE SAVE IN DOCUMENTS//////
[UIImagePNGRepresentation(image) writeToFile:[self findUniqueSavePath] atomically:YES];
// Show the current contents of the documents folder
CFShow([[NSFileManager defaultManager] directoryContentsAtPath:[NSHomeDirectory() stringByAppendingString:#"/Documents"]]);
}
Please help me. I want to remove all warnings.
You're leaking the UIImagePickerController. Autorelease it on creation or release it after dismissModalViewControllerAnimated.
You may still get memory warnings, photos can be enormous, especially on the iPhone 4 and at a certain point you have two of them in memory: the UIImage and the autoreleased PNG.
P.S. you don't seem to be using size and cropRect so you could delete them.
Release your alert view. In general release any object you alloc. In case you have a retain property then assign a auto release object to it.
When you get a memory warning your view controller method - (void)didReceiveMemoryWarning is called. Here you will have to release any unwanted objects which you have cached. Typically that would be some images, views in stack etc.
Also check if you are having appropriate dealloc for objects in your modal view controller.
Are you implementing -imagePickerController:didFinishPickingMediaWithInfo:? The method you've implemented has been deprecated. You should use the other method even for images. What are you doing with the recorded videos?
On a side note, the following code –
#try
{
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
}
#catch (NSException * e)
{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"ALERT"
message:#"Please try again"
delegate:self
cancelButtonTitle:nil
otherButtonTitles:#"ok", nil];
[av show];
}
should be
if ( [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] ) {
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
} else {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"ALERT"
message:#"Camera isn't available"
delegate:self
cancelButtonTitle:nil
otherButtonTitles:#"ok", nil];
[av show];
[av release]
}
Now smarter thing would be to disable button 2 which would be
if ( ![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] ) {
button2.enabled = N0;
}

mail application are not launching!

i am trying to implement the functionality of email in my application. i have added MessageUI-framework, along with header and MFMailComposeViewControllerDelegate protocol but i am facing problem. here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
if(isViewPushed == NO) {
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCompose
target:self action:#selector(email)] autorelease];
}
}
-(void) email
{
NSMutableString *emailBody = [[[NSMutableString alloc] initWithString:#"<html><body>"] retain];
[emailBody appendString:#"<p>type text here</p>"];
UIImage *emailImage = [UIImage imageNamed:#"20-gear2.png"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(emailImage)];
[emailBody appendString:[NSString stringWithFormat:#"<p><b><img src='data:image/png;base64.....,.......%#'></b></p>",imageData]];
[emailBody appendString:#"</body></html>"];
MFMailComposeViewController *emailDialog = [[MFMailComposeViewController alloc] init];
emailDialog.mailComposeDelegate = self;
[emailDialog setSubject:#"My Inline Image Document"];
[self presentModalViewController:emailDialog animated:YES];
[emailDialog release];
if(! [MFMailComposeViewController canSendMail])
{
UIAlertView *cantMailAlert = [[UIAlertView alloc]
initWithTitle:#"cant email"
message:#"nt able to send email"
delegate:NULL
cancelButtonTitle:#"ok"
otherButtonTitles:NULL];
[cantMailAlert show];
[cantMailAlert release];
return;
}
MFMailComposeViewController *mailController = [[[MFMailComposeViewController alloc] init] autorelease];
[mailController setMessageBody:#"can send my mail" isHTML:NO];
mailController.mailComposeDelegate = self;
[self presentModalViewController:mailController animated:YES];
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
if (error)
{
UIAlertView *cantMailAlert = [[UIAlertView alloc]
initWithTitle:#"mail error"
message: [error localizedDescription]
delegate:NULL
cancelButtonTitle:#"ok"
otherButtonTitles:NULL];
[cantMailAlert show];
[cantMailAlert release];
return;
}
NSString *resultString;
switch (result)
{
case MFMailComposeResultSent:
resultString = #"sent mail";
break;
case MFMailComposeResultSaved:
resultString = #"saved";
break;
case MFMailComposeResultCancelled:
resultString = #"cancel";
break;
case MFMailComposeResultFailed:
resultString = #"failed";
break;
}
if (resultString = #"saved")
{
NSString *msg = [NSString stringWithFormat:#"%# at %#\n", resultString, [NSDate date]];
UIAlertView *MailAlert = [[UIAlertView alloc]
initWithTitle:#"status"
message: msg
delegate:NULL
cancelButtonTitle:#"ok"
otherButtonTitles:NULL];
[MailAlert show];
[MailAlert release];
return;
}
[controller dismissModalViewControllerAnimated:YES];
[controller release];
//[self email];
}
but when i click on mail button then applictaion terminates and starts loading . it says can't able to store privious value!!
Why are you presenting two instances of MFMailcomposeviewcontroller in viewDidLoad? Why are you creating two objects namely emailDialog and mailController and presenting them?

Threading / UIActivityIndicatorView with In App Purchase

Let me first say, my In App Purchase works.
I have been struggling with the Activity Indicator / Threading for more than a week now. I am having real problems getting my spinner (UIActivityIndicatorView) to play nice within my InAppPurchase.m
I am using the same threading code in many other places and it works fine.
Is there something about how the IAP process works that causes problems with basic threading?
Right now, the spinner spins between the time you tap the buyButton and the first alert appears ("Do you want to buy? ..."), but no spinner after that.
Here is the .m File:
// InAppPurchaseManager.m
#import "InAppPurchaseManager.h"
#import "GANTracker.h"
#implementation InAppPurchaseManager
#synthesize productID;
#synthesize productsRequest;
#synthesize closeButton;
#synthesize buyButton;
#synthesize testLabel;
#synthesize pView;
#synthesize spinner;
#synthesize spinnerLabel;
- (void)dealloc {
[productID release];
//[productsRequest release];
[closeButton release];
[buyButton release];
[testLabel release];
[pView release];
[spinner release];
[spinnerLabel release];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[super dealloc];
}
- (void)viewDidLoad {
[super viewDidLoad];
NSError *error;
if (![[GANTracker sharedTracker] trackPageview:#"/in_app_purchase" withError:&error]) {
//NSLog(#"No GAN Tracking");
}
UIColor *backgroundColor = [UIColor colorWithRed:.6745 green:.1333 blue:.1333 alpha:1];
pView.backgroundColor = backgroundColor;
[closeButton release];
closeButton = [[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStyleBordered target:self action:#selector(closeButtonAction:)];
self.navigationItem.leftBarButtonItem = closeButton;
// create the "Loading..." label
[spinnerLabel release];
//CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
spinnerLabel = [[UILabel alloc] initWithFrame:CGRectMake(35, 145, 250, 35)];
[spinnerLabel setText:#"Connecting to App Store... "];
[spinnerLabel setTextColor:[UIColor whiteColor]];
[spinnerLabel setBackgroundColor:[UIColor blackColor]];
[spinnerLabel setTextAlignment:UITextAlignmentRight];
[self.view addSubview:spinnerLabel];
spinnerLabel.hidden = YES;
// create the spinner
[spinner release];
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[spinner setCenter:CGPointMake(55,162)];
[self.view addSubview:spinner];
spinner.backgroundColor = [UIColor blackColor];
spinner.hidesWhenStopped = YES;
[spinner stopAnimating];
self.navigationItem.title = #"Credits";
//[self spinTheSpinner];
//[self loadStore];
[NSThread detachNewThreadSelector:#selector(loadStore) toTarget:self withObject:nil];
}
-(void)viewDidAppear:(BOOL)animated {
[self doneSpinning];
[self updateButtonStatus:#"ON"];
}
-(void)spinTheSpinner {
NSLog(#"In App Purchase.m == SpinTheSpiner");
//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[spinner startAnimating];
spinnerLabel.hidden=NO;
//[self performSelectorOnMainThread:#selector(doneSpinning) withObject:nil waitUntilDone:NO];
//[pool release];
}
-(void)doneSpinning {
NSLog(#"In App Purchase.m == DoneSpinning");
spinnerLabel.hidden = YES;
[spinner stopAnimating];
}
-(void)closeButtonAction:(id)sender {
[self dismissModalViewControllerAnimated:YES];
}
-(void)buyButtonAction:(id)sender {
if([self canMakePurchases]) {
[self updateButtonStatus:#"OFF"];
[self spinTheSpinner];
//[self performSelectorOnMainThread:#selector(requestInAppPurchaseData) withObject:nil waitUntilDone:NO];
[NSThread detachNewThreadSelector:#selector(requestInAppPurchaseData) toTarget:self withObject:nil];
} else {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:[NSString stringWithString:#"Your account settings do not allow for In App Purchases."] delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
}
}
-(void)updateButtonStatus:(NSString *)status {
if ([status isEqual:#"OFF"]) {
closeButton.enabled = NO;
buyButton.enabled = NO;
buyButton.titleLabel.textColor = [UIColor grayColor];
} else {
closeButton.enabled = YES;
buyButton.enabled = YES;
buyButton.titleLabel.textColor = [UIColor blueColor];
}
}
#pragma mark -
#pragma mark SKProductsRequestDelegate methods
//
// call this method once on startup
//
- (void)loadStore
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"Load Store");
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
// restarts any purchases if they were interrupted last time the app was open
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[self doneSpinning];
[pool release];
}
- (void)requestInAppPurchaseData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"Request In App Purchase Data");
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
NSSet *productIdentifiers = [NSSet setWithObject:kInAppPurchaseCreditProductId];
productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
productsRequest.delegate = self;
[productsRequest start];
//[self doneSpinning];
[pool release];
// we will release the request object in the delegate callback
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSLog(#"did Receive Response");
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
NSArray *products = response.products;
productID = [products count] == 1 ? [[products objectAtIndex:0] retain] : nil;
if (productID)
{
/*
NSLog(#"Product title: %#" , productID.localizedTitle);
NSLog(#"Product description: %#" , productID.localizedDescription);
NSLog(#"Product price: %#" , productID.price);
NSLog(#"Product id: %#" , productID.productIdentifier);
*/
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString *currentCredits = ([standardUserDefaults objectForKey:#"currentCredits"]) ? [standardUserDefaults objectForKey:#"currentCredits"] : #"0";
testLabel.text = [NSString stringWithFormat:#"%#", currentCredits];
}
for (NSString *invalidProductId in response.invalidProductIdentifiers)
{
//NSLog(#"Invalid product id: %#" , invalidProductId);
testLabel.text = #"Try Again Later.";
}
// finally release the reqest we alloc/init’ed in requestProUpgradeProductData
[productsRequest release];
[[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerProductsFetchedNotification object:self userInfo:nil];
//[self performSelectorOnMainThread:#selector(purchaseCredit) withObject:nil waitUntilDone:NO];
[self purchaseCredit];
}
//
// call this before making a purchase
//
- (BOOL)canMakePurchases
{
NSLog(#"Can Make Payments");
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
return [SKPaymentQueue canMakePayments];
}
//
// kick off the upgrade transaction
//
- (void)purchaseCredit
{
// REMOVED FOR PRIVACY
}
#pragma -
#pragma Purchase helpers
//
// saves a record of the transaction by storing the receipt to disk
//
- (void)recordTransaction:(SKPaymentTransaction *)transaction
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
if ([transaction.payment.productIdentifier isEqualToString:kInAppPurchaseCreditProductId])
{
// save the transaction receipt to disk
[[NSUserDefaults standardUserDefaults] setValue:transaction.transactionReceipt forKey:#"InAppPurchaseTransactionReceipt" ];
[[NSUserDefaults standardUserDefaults] synchronize];
}
[pool release];
}
//
// enable pro features
//
- (void)provideContent:(NSString *)productId
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
if ([productId isEqualToString:kInAppPurchaseCreditProductId])
{
// Increment currentCredits
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString *currentCredits = [standardUserDefaults objectForKey:#"currentCredits"];
int newCreditCount = [currentCredits intValue] + 1;
[standardUserDefaults setObject:[NSString stringWithFormat:#"%d", newCreditCount] forKey:#"currentCredits"];
testLabel.text = [NSString stringWithFormat:#"%d", newCreditCount];
}
[pool release];
}
//
// removes the transaction from the queue and posts a notification with the transaction result
//
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful
{
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
// remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:transaction, #"transaction" , nil];
if (wasSuccessful)
{
// send out a notification that we’ve finished the transaction
[[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionSucceededNotification object:self userInfo:userInfo];
}
else
{
// send out a notification for the failed transaction
[[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionFailedNotification object:self userInfo:userInfo];
}
[self updateButtonStatus:#"ON"];
}
//
// called when the transaction was successful
//
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
[self updateButtonStatus:#"OFF"];
[self spinTheSpinner];
[NSThread detachNewThreadSelector:#selector(recordTransaction:) toTarget:self withObject:transaction];
[NSThread detachNewThreadSelector:#selector(provideContent:) toTarget:self withObject:transaction.payment.productIdentifier];
//[self recordTransaction:transaction];
//[self provideContent:transaction.payment.productIdentifier];
[NSThread detachNewThreadSelector:#selector(threadFinishTransaction:) toTarget:self withObject:transaction];
//[self finishTransaction:transaction wasSuccessful:YES];
NSError *error;
if (![[GANTracker sharedTracker] trackPageview:#"/in_app_purchase_done" withError:&error]) {
//NSLog(#"No GAN Tracking");
}
[self doneSpinning];
}
-(void)threadFinishTransaction:(SKPaymentTransaction *)transaction {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self finishTransaction:transaction wasSuccessful:YES];
[pool release];
}
//
// called when a transaction has been restored and and successfully completed
//
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
[self recordTransaction:transaction.originalTransaction];
[self provideContent:transaction.originalTransaction.payment.productIdentifier];
[self finishTransaction:transaction wasSuccessful:YES];
}
//
// called when a transaction has failed
//
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
if (transaction.error.code != SKErrorPaymentCancelled)
{
// error!
NSError *error;
if (![[GANTracker sharedTracker] trackPageview:#"/in_app_purchase_error" withError:&error]) {
//NSLog(#"No GAN Tracking");
}
[self finishTransaction:transaction wasSuccessful:NO];
}
else
{
// this is fine, the user just cancelled, so don’t notify
NSError *error;
if (![[GANTracker sharedTracker] trackPageview:#"/in_app_purchase_cancel" withError:&error]) {
//NSLog(#"No GAN Tracking");
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
[self updateButtonStatus:#"ON"];
}
#pragma mark -
#pragma mark SKPaymentTransactionObserver methods
//
// called when the transaction status is updated
//
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
//[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
break;
default:
break;
}
}
}
#end
Chris, 2 things:-
Firstly, why have you opted for a multi-threaded approach?
Nothing here requires you spawn a new thread. The StoreKit api is asynchronous, as you know, you are afterall using the callbacks and delegates. This is specifically so it won't block the main thread and so you don't have to spawn a new thread. It almost certainly does it's work on a background thread - but you don't need to know that, it is handled for you. Infact, not only does this code not require a background thread, you are almost certainly experiencing a substantial performance cost spawning new threads to do so little work. ie. it will (probably) take longer for the thread to start up than it will to do the work you have scheduled on it.
So, if your motivation was performance, you are going to be disapointed.
Secondly, your threading code, or lack of it, is a mess. On the plus side, just to reiterate, none of it is needed, so no big problem.
You say that you are
using the same threading code in many
other places and it works fine
You have been unlucky. This has given you the impression that this should work when infact it is completely unsafe. Threading is really tough, and if you want to do it you could do worse than reading some the related apple docs
Threading
Concurrency
I'm reluctant to just spew stuff straight from these guides, translated thru my foggy brain, and try to pass it of as my own advice, but to try to motivate you to read the guides i've adding some comments to a couple of lines of your code:-
// you start a new background thread to call -loadStore
[NSThread detachNewThreadSelector:#selector(loadStore) toTarget:self withObject:nil];
// you initialize SKPaymentQueue singleton on the background thread - is this allowed? i dont know. I can't see it documented.
// then you add transactionObserver observer on the background thread - which thread do you want to receive notifications on? 1)Main thread, 2)this (background) thread, 3)unsure. If it's not the background thread this probably isnt a good idea
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
// Update the GUI from the background thread. No joking here - you absolutely can never do this. It's well documented.
[self doneSpinning];
// end of method, background thread exits or not? You tell me. hope we get lucky with those notifications
[pool release];
So, i'd like to add that i am definitely no expert on how In App Purchase works but i would bet there is nothing special about it. Your activity spinner will probably be fine if you get rid of the background thread or re-implement it in a thread safe manner (which in my opinion doesn't look worth the trouble here).
I only skimmed your code, but I saw at least two places where it looked like you are attempting to update UI (stopping a spinner, updating label text) from a method which is called from a thread that is not the primary (main) thread. This is not allowed - all UI must be updated from the main thread.
If you need to update UI from a background thread you need to marshall the call to the main thread, perhaps using peformSelectorOnMainThread:withObject:
So, for example, it looks like loadStore is called from a non-main thread, and it calls doneSpinning, which updates UI. I'd make the following change:
- (void)loadStore
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"Load Store");
// restarts any purchases if they were interrupted last time the app was open
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
// instead of calling doneSpinning directly, ensure it runs on the main thread
[self performSelectorOnMainThread: #selector( doneSpinning) withObject: nil];
[pool release];
}