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

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.

Related

iPhone App Crash with error [UIApplication _cachedSystemAnimationFenceCreatingIfNecessary:]

I have an iPhone App in the app store which is using Touch ID. If Touch ID is enabled, the user is authenticated with it, else user needs to enter his PIN to login to the application.
After IOS 10.1 release, when I checked the crash report, the crash count has increased. From the crash report, it is pointing on [UIApplication _cachedSystemAnimationFenceCreatingIfNecessary:] and when I opened the app in Xcode, it is focussing on [self dismissViewControllerAnimated:YES completion:nil];.
The code I have written is as below:
-(void) showTouchIDAuthentication{
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = #"Authenticate using your finger to access My Account Menu.";
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
if (success) {
NSLog(#"User is authenticated successfully");
[self dismissViewControllerAnimated:YES completion:nil];
} else {
}];
}
}
When I tested in iPhone 6, IOS 10, everything is working fine. Don't know how to simulate the issue.
Can anyone please figure out if I am missing something? Please help me out in resolving this crash issue.
Usually completion handlers are not running on main thread. All UI related stuff must be done on main thread (including dismissing a view controller).
I suggest to add the dismiss line on a main thread block like this:
-(void) showTouchIDAuthentication{
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = #"Authenticate using your finger to access My Account Menu.";
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
if (success) {
NSLog(#"User is authenticated successfully");
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
[self dismissViewControllerAnimated:YES completion:nil];
}];
} else {
}];
}
}

Show UIAlertView during UIActivity:activityViewController

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];
});
});
}

Programmatically tap on HTML href in order to update app

I'm planning updates for an enterprise app with ad-hoc distribution.
For updates, Apple recommends having the user visit an HTML page and tap on a link:
href="itms-services://?action=download-manifest&url=http://example.com/
manifest.plist"
See http://help.apple.com/iosdeployment-apps/#app43ad871e
I don't want to do this. I want the app to programmatically check for updates on launch and alert the user with a UIAlertView that an update is available.
Here's what I have so far in application didFinishLaunching. The complicated plist parsing comes from the structure of an example plist found here: http://help.apple.com/iosdeployment-apps/#app43ad78b3
NSLog(#"checking for update");
NSData *plistData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://example.com/MyApp.plist"]];
if (plistData) {
NSLog(#"finished checking for update");
NSError *error;
NSPropertyListFormat format;
NSDictionary *plist = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListImmutable format:&format error:&error];
if (plist) {
NSArray *items = [plist valueForKey:#"items"];
NSDictionary *dictionary;
if ([items count] > 0) {
dictionary = [items objectAtIndex:0];
}
NSDictionary *metaData = [dictionary objectForKey:#"metadata"];
float currentVersion = [[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"] floatValue];
float newVersion = [[metaData objectForKey:#"bundle-version"] floatValue];
NSLog(#"newVersion: %f, currentVersion: %f", newVersion, currentVersion);
if (newVersion > currentVersion) {
NSLog(#"A new update is available");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Update available" message:#"A new update is available." delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"UPDATE", nil];
[alert show];
}
}
}
Then I have my UIAlertView delegate method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
NSLog(#"downloading full update");
UIWebView *webView = [[UIWebView alloc] init];
[webView loadRequest:[[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"itms-services://?action=download-manifest&url=http://example.com/MyApp.plist"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0]];
}
}
A few things:
I know [alert show] shouldn't be called in application didFinish, but I'll change it later.
I don't know how quickly plistData will be downloaded and how this download affects the app.
More importantly, my alert view delegate method doesn't work, and the update doesn't download. Even when I introduce webView with #property (nonatomic, strong) UIWebView *webView, the method doesn't do anything.
I think Dropbox has the MIME configured properly because I can download the .ipa through google Chrome.
So what I really need is a way using NSURLConnection (NSURLRequest etc.) to replicate the act of a user tapping on an HTML href. After that I think the full update will occur.
You can open a URL automatically using
[[UIApplication sharedApplication] openURL:...];
I don't know if it works for itms-services: urls, but it works for other bespoke URL schemes like tel:, fb: etc. so it should do unless Apple have specifically blocked it.

Xcode: How would I do a one-off UIAlertView?

I have an app that I want to bring up an alert view to ask them if they can review the app?
Any help appreciated!
Don't. That is bad UX. Instead, try the following:
have an "About" section in your app containing your brand, and a link to the review page
have a "Review This App" button in a non-distracting location at a corner/side of the screen where it doesn't break the user experience
Something like this would do it:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"UIAlertView" message:#"<Alert message>" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK", nil];
[alert show];
[alert release];
* Important Update
Apple has added an extra rule for apps being submitted to the app store.
We can no longer store arbitrary data files in the Documents folder. Only content generated by the user like a text file they manually typed and saved or a photo they took with the camera can be saved in the Documents folder.
We are now expected to store our app generated files in the Library/Cache folder instead of the Documents folder. In addition, we should mark the files we don't want to be sync to iCloud with the skip backup attribute.
Failure to comply with this will result in an app being rejected by Apple.
Reason: The Documents folder is now used for syncing to iCloud. iCloud does a sync every few minutes and if we were to have megabytes of our app generated files stored in the Documents folder, it would get mixed up with the user's own iCloud synced files.
Update the MediaDirectory.m files with these new code to be safe:
+ (NSString *) mediaPathForFileName:(NSString *) fileName
{
NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDirectory = [directoryPaths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/%#", cachesDirectory, fileName];
return filePath;
}
+ (BOOL)addSkipBackupAttributeToFile:(NSString *) fileName
{
NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDirectory = [directoryPaths objectAtIndex:0];
NSString *filePathStr = [NSString stringWithFormat:#"%#/%#", cachesDirectory, fileName];
const char* filePath = [filePathStr fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
return result == 0;
}
Remember to mark that text file we generated with the skip backup attribute like so after generating the file:
[MediaDirectory addSkipBackupAttributeToFile:#"fileStatus.txt"];
I also had a typo earlier, I said to write to file "firstLaunch.txt" when it should have been "fileStatus.txt" (bad copy and paste skills :P)
Original Text
You could try writing a value (such as "no") to a text file and storing in the application's sandbox Document folder when the user taps on "Don't ask again" button.
Next time the app loads, your view controller would read the value from this text file and if it's "no", then don't display the alert view.
Hope that helps.
Writing a one off UI alert view
OK, let me first explain the process we're going to go through:
1) Your application delegate class didFinishLaunchingWithOption is the method that gets called when an app is launched.
2) This method only gets called once when the app launches so this is the spot where we create a text file and we write our status of the alert view whether to "show again" or "don't show again" for the UIAlertView
3) We're going to bring up the alert view in your UIViewController's viewDidLoad method. However, we only display this UIAlertView IF the value in our text file IS NOT "don't show again". If the value in the text file is "show again", we show the UIAlertView, if it's "don't show again", we don't show the UIAlertView, makes sense?
(These values are only string, you can set any value you want, I'm only using these random values to demonstrate).
Right, now we got the general process, let's implement it.
The code
In your AppDelegate.h header file:
#interface MyAppDelegate : NSObject
{
...
bool isFirstLaunch;
}
Now in your AppDelegate.m implementation file:
// -------------------------------------------------------------------------------------
// I wrote a helper class called "MediaDirectory" that returns me the path to a file
// in the Documents directory of the application sandbox.
// Each application has a copy of the Documents directory so you can safely
// assume this directory will always exist.
//
// This class has been very useful for me, hope you find it useful too.
//
// DOWNLOAD THE FILES AT:
//
// http://www.chewedon.com/classes/MediaDirectory.h
// http://www.chewedon.com/classes/MediaDirectory.m
// -------------------------------------------------------------------------------------
#import <Foundation/NSFileManager.h>
#import <Foundation/NSFileHandle.h>
#import "MediaDirectory.h"
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
NSString *fileStatus = nil;
// Here, the NSFileManager defaultManager fileExistsAtPath method needs a path to
// the file in the Document directory, to get this path, use the static method from
// my MediaDirectory class and pass it the name of the file.
// I called it "fileStatus.txt"
//
// We're checking if the file exist in the Documents directory or not.
// If it does not exist then we create the text file and pass it the
// value "show again", otherwise we do nothing.
// (We don't want to overwrite the value everytime the app starts)
if([[NSFileManager defaultManager] fileExistsAtPath:[MediaDirectory mediaPathForFileName:#"fileStatus.txt"]] == NO)
{
// prepare fileStatus.txt by setting "show again" into our string
// we will write this string into the text file later
fileStatus = [[NSString alloc] initWithString:#"show again"];
// now we write the value "show again" so that the UIAlertView will show
// when it checks for the value, until the user clicks on no and we set
// this value to "don't show again" later in another piece of code
[fileStatus writeToFile:[MediaDirectory mediaPathForFileName:#"fileStatus.txt"]
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
}
[fileStatus release];
...
// rest of your didFinishLaunchingWithOptions method
[window addSubview:[viewController view]];
[self.window makeKeyAndVisible];
[self initGlobals];
return YES;
}
Now in your UIViewController class, we need to make this class conform to the UIAlertView protocol. We are going to use one of the delegate method that tells us when a button on the UIAlertView is clicked.
#interface MyViewController : UIViewController <UIAlertViewDelegate>
{
...
}
In our implementation file (MyViewController.m file) we check the value stored in the text file before showing the UIAlertView.
#import "MediaDirectory.h"
#import <Foundation/NSFileManager.h>
#import <Foundation/NSFileHandle.h>
-(void)viewDidLoad
{
...
BOOL shouldShowAlert = false;
NSString *fileStatus = nil;
// check if the file exists in the Documents directory and read its value
// if it does. If the value read from file is "show again",
// we bring up a UIAlertView
if([[NSFileManager defaultManager] fileExistsAtPath:[MediaDirectory mediaPathForFileName:#"fileStatus.txt"]] == YES)
{
fileStatus = [[NSMutableString alloc] initWithContentsOfFile:[MediaDirectory mediaPathForFileName:#"fileStatus.txt"]
encoding:NSUTF8StringEncoding
error:nil];
if([fileStatus isEqualToString:#"show again"] == YES)
{
shouldShowAlert = true;
}
else if([fileStatus isEqualToString:#"don't show again"] == YES)
{
shouldShowAlert = false;
}
}
if(shouldShowAlert == true)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"My Alert View Title"
message:#"my message"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:#"Don't Show Again", nil];
// we set a tag for this UIAlertView to distinguish between this UIAlertView
// and other UIAlertView in case there are any.
// I use a value of 10, you can use any value but make sure it is unique
[alert setTag:10];
[alert show];
[alert release];
}
}
Now we come to the last part where we handle which button the user tapped on for the UIAlertView.
Somewhere in your MyViewController.m file, write this delegate method:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// making sure its the UIAlertView we want
if([alertView tag] == 10)
{
// By default, the "cancel" button has an index of 0
// and the next button would have a index of 1
// In our case, we set the first button is "OK"
// and "Don't Show Again" as second button
if(buttonIndex == 1)
{
NSString *fileStatus = [[NSString alloc] initWithString:#"don't show again"];
[fileStatus writeToFile:[MediaDirectory mediaPathForFileName:#"fileStatus.txt"]
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
[fileStatus release];
}
}
}
Hopefully I didn't miss anything, let me know if it works or not.

UIWebView loading

I want load the html data in webview. At the button click, it open the viewcontroller and load the html data in this viewcontroller (add web view in this view controller using Interface builder). When the html data not proper loading and i press the back button, at that time crash the app. i am not doing allocation & init webview in the coding. set IBOUTLET using Interface builder & bind it.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[connection release];
NSString *strResponce = [[NSString alloc] initWithData:jsonData_Info encoding:NSUTF8StringEncoding];
[jsonData_Info release];
NSError *error;
SBJSON *json = [[SBJSON new] autorelease];
self.jsonArray_Info=[json objectWithString:strResponce error:&error];
str_InfoDetail = [[self.jsonArray_Info objectAtIndex:0] valueForKey:#"Page"];
str_html = [NSString stringWithFormat:#"%#",str_InfoDetail];
NSString *temp;
temp = [NSString stringWithFormat:#"<html><head><style>body{background-color:transparent;}</style></head><body><span style='color:white'>%#</span></body></html>",str_html];
//web_Information = [[UIWebView alloc]init];
web_Information.backgroundColor=[UIColor clearColor];
web_Information.opaque= NO;
[web_Information loadHTMLString:temp baseURL:nil];
[act stopAnimating];
[strResponce release];
}
please give me any solution.
thanks.
Pls post some code and crash log if you need answers. By the look of it I think it may be because of implementation of UIWebView delegate in your class. I think when you navigate back you do not make the delegate nil which can cause the app to crash