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

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

Related

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

Memory management creating an NSDictionary with NSMutableArrays

I'm having problem understanding memory management when creating a dictionary with mutable arrays. I'm using the ios6 SDK with deployment target 5.1.
In the implementation of the class "Group" the method "namesAndEmails" builds an array "emails" that contains the emails addresses for Person objects with an email. If the Person object does not have an email the Person name is added to another array "namesWithNoEmail". The arrays are returned in a dictionary.
#import "Group.h"
#implementation Group
-(NSDictionary*) namesAndEmails {
NSMutableArray *emails = [[NSMutableArray alloc] initWithCapacity:0] ;
NSMutableArray *namesWithNoEmail = [[NSMutableArray alloc] initWithCapacity:0];
NSString *email;
NSString *name;
for (Person *p in allPersons) {
email = p.email;
name = p.name;
if ([email length]==0) {
[namesWithNoEmail addObject:name];
} else {
[emails addObject:email];
}
}
NSArray *keys = [NSArray arrayWithObjects:#"emails",#"names", nil];
NSArray *objects = [NSArray arrayWithObjects:emails, namesWithNoEmail, nil];
//[emails release];
//[namesWithNoEmail release];
return [NSDictionary dictionaryWithObjects:objects forKeys:keys];
}
Somewhere else in the code I wish to send an email to a group of people so I call the emailGroup method which gets a dictionary out by calling "namesAndEmails" on the group.
-(void) emailGroup:(Group*) g {
NSDictionary *emailInfo = [g namesAndEmails];
guestsWithNoEmail = [emailInfo objectForKey:#"names"];
guestEmails = [emailInfo objectForKey:#"emails"];
int nGuestsWithNoEmail = [guestsWithNoEmail count];
if (nGuestsWithNoEmail > 0) {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:#"No emails" message:#"" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] autorelease];
[alert show];
}
// some more code here
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
[picker setSubject:subject];
[picker setMessageBody:#"" isHTML:NO];
[picker setToRecipients:guestEmails];
[[self delegate ] presentModalViewController:picker animated:YES];
[picker release];
}
As far as I understand [NSDictionary dictionaryWithObjects:objects forKeys:keys] in "namesAndEmails" returns an autoreleased dictionary. But why does my code crash if I release the "emails" and "namesWithNoEmail" arrays? I thought that the dictionary would have ownership of the array after they are added and therefore it would be safe to release the arrays in the method. I guess that's not correct, but why?
Is the a more clean way of doing this? Thank you for any advice!
My first suggestion would be to use the "Product->Analyze" feature. If you leaking or over releasing somewhere, it will probably give you the exact chain of events.
Secondly, I can't see the linking between your methods nameAndEmails and emailGroup:. Because I can't see the connection, I can't tell you if the autorelease is causing the problem.
Autoreleased objects get released when the the main run loop cycles. So it's very possible your NSDictionary is getting released. You could test this by doing anything from setting the memory location as a "watch" in the debugger to putting printing something in the console lines each time the runloop your in cycles (I made the assumption your in the main run loop, so correct me if that's not true).
Other things you can do to track the problem would be to use "Zombies" in instruments or NSZombieEnable=YES in your configuration

fetch JSON data asynchronously

I want to fetch JSON data asynchronously. The data is set up in a way that one request will bring only 8 records. I need to send the requests repeatedly until the response becomes empty or returns less than 8 records.
Currently, I have these methods in myviewcontroller.m class:
(void)myCallback:(id)sender {
MyDataRequest *objMyDataRequest = [[[MyDataRequest alloc] init] autorelease];
objMyDataRequest.myRequiredVariableToGetAuthTokenDataResponse = classOfMyCallBack.someVariable;
// Initiate getAuthToken request
[objWishListRequest initiateGetAuthTokenRequest:self requestSelector:#selector(getAuthTokenDataResponse:)];
}
Now here is the definition of getAuthTokenDataResponse:
(void) getAuthTokenDataResponse:(NSData *)data {
NSString *stringResponse = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
SBJsonParser *parser=[[SBJsonParser alloc]init];
NSDictionary *objDictionaryForStringResponse = [parser objectWithString:stringResponse];
[stringResponse release];
[parser release];
MyListRequest *objMyListRequest = [[[MyListRequest alloc] init] autorelease];
objMyListRequest.myRequiredValueToGetMyDataResponse = [objDictionaryForStringResponse objectForKey:#"Data"];
// Initiate GetMyDataResponse request
[objMyListRequest initiateGetMyDataRequest:self requestSelector:#selector(getMyDataResponse:)];
}
(void) getMyDataResponse:(NSData *)data {
NSString *stringResponse = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
SBJsonParser *parser=[[SBJsonParser alloc]init];
NSDictionary *objGetMyDataRootDictionary = [parser objectWithString:stringResponse];
[stringResponse release];
[parser release];
NSDictionary *dataElements=[objGetMyDataRootDictionary objectForKey:#"Data"];
Wish *objMyData;
for (NSDictionary* objGetMyDataRootDictionary in dataElements) {
objMyData = [[Wish alloc]init];
//add different elements from dataElements into member variables of object objWish
[self.myDataArray addObject:objMyData];
[objMyData release];
}
[self.myDataTableView reloadData];
}
This method lies in MyDataRequest class:
(void)initiateGetMyDataRequest:(id)requestDelegate requestSelector:(SEL)requestSelector{
// Set the delegate and selector
self.delegate = requestDelegate;
self.callback = requestSelector;
NSString* unescapedUrlString = [NSString stringWithFormat:#"http://test.mytesturl.com/core.svc/alldata/My/get/All/?token=%#&search=&page=1",myRequiredtokenparameter];
[self request:url];
}
I need to send multiple requests to the same url (with different parameter value i.e. value of page number) to fetch the results. How may I achieve it given the above scenario? The calls must be asynchronous.
How should I make the actual flow between all these calls? How may I get the data of "all the pages" asynchronously?
I think you are looking for a operation queue. I use ASIHTTPRequests in my apps and they work.
If you want to use this library, here's the link how to use it: Show UIActivityIndicatorView when loading NSString from Web

NSURLRequest converting NSData to an array

I need to convert data received from the web via an array in a PHP script into an array that I can pull values out of. Here's my code!
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
//NSString *payloadAsString = [NSString stringWithUTF8String:[receivedData bytes]];
NSArray *payloadAsString = [NSKeyedUnarchiver unarchiveObjectWithData:receivedData];
[payloadAsString finishEncoding];
verified = [payloadAsString objectAtIndex:0];
NSLog(#"logging");
//NSString *no = [[NSString alloc] init stringWithCString:verified];
NSLog(#"%#", verified);
if([verified isEqualToString:#"admin"]){
NSLog(#"test admin");
[self performSelector:#selector(changeViewAdmin) withObject:nil afterDelay:0.05];
}
if([verified isEqualToString:#"user"]){
NSLog(#"test user");
[self performSelector:#selector(changeView) withObject:nil afterDelay:0.05];
}
if([verified isEqualToString:#"No"]){
NSLog(#"test no");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Invalid UserName/Password combination!"
delegate:self
cancelButtonTitle:#"Okay"
otherButtonTitles:nil];
[alert show];
[alert release];
}
[payloadAsString release];
//NSLog(#"%#", verified);
// INSERT GOOGLE MAPS URL REQUEST HERE
/*if(requestType == 1){
NSString* addressText = payloadAsString;
// URL encode the spaces
addressText = [addressText stringByAddingPercentEscapesUsingEncoding: NSASCIIStringEncoding];
NSString* urlText = [NSString stringWithFormat:#"http://maps.google.com/maps?q=%#", addressText];
// lets throw this text on the log so we can view the url in the event we have an issue
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlText]];
// */
//
//}
[connection release];
self.receivedData = nil;
}
Unfortunately, my console returns null and asks if I've put the -finishencoding method in. Question is, if that's correct, where would I do so?
PS: Another question, is if I'm retrieving an array of data from a database, is a PHP script the best way to go? Thank you.
1) Of all this code the only string relevant to your question is
NSArray *payloadAsString = [NSKeyedUnarchiver unarchiveObjectWithData:receivedData];
I really doubt that PHP script returns you data in NSKeyedUnarchiver-compatible format. I believe the only reason you don't get NSInvalidArgumentException exception from this method is that receivedData is nil (did you initialize it anywhere?). Try to make a string from what you receive like this
[[[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding] autorelease]
and log it. From this I hope it will be clear how to parse response.
2) Do not name NSArray instances like 'blahBlahString'. Strings and arrays are completely different.
NSKeyedUnarchiver can only unarchive instances which are produced by instances of the NSKeyedArchiver class.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSKeyedUnarchiver_Class/index.html

save and restore state of a tab bar controller

I have an application that has a UITabBarController with two tabs, each having its own navigation controller. Now I want to store the state of the application when the user closes it, so that when the user relauches the application will show the same place as the last time before it was closed.
So, in applicationWillTerminate: I have
[NSKeyedArchiver archiveRootObject:tabBarController toFile:#"lastVisitedTab"];
Then, in applicationDidFinishLaunching: I have
UITabBarController *last= (UITabBarController *)[NSKeyedUnarchiver unarchiveObjectWithFile:#"lastVisitedTab"];
if (last)
tabBarController = [last retain];
I also have an extension to UIImage to make it compliant to NSCoding. However, this doesn't work, as the state is not preserved. The first tab gets selected all the time, and no navigation is preserved either.
Can someone tell me what's wrong, or show me how to do it correctly?
I think it's overkill to persist the actual objects. Instead, just save the selectedIndex property (use [NSNumber numberWithInt: tabBar.selectedIndex]) and then read it back and set the property on launch. Maybe this doesn't properly answer your question, but it might be sufficient for what you are trying to achieve.
I figured out how to do it finally, thanks to Felixyz's idea. Below is what I have to do to store tabs, regardless of their data. If, says, a view is loaded with data downloaded from an URL, store the URL instead of the whole view. You would have to override
- (void)encodeWithCoder:(NSCoder *)encoder
- (id)initWithCoder:(NSCoder *)decoder
in your UIViewController subclass to tell the view controller to save appropriate data before the application stops.
Now in your application delegate save the data before quiting
- (void)applicationWillTerminate:(UIApplication *)application
// data buffer for archiving
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
// the index of selected tab
[archiver encodeInt:tabBarController.selectedIndex forKey:#"TAB_INDEX"];
// array of keys for each navigation controller, here I have 3 navigation controllers
NSArray *keys = [NSArray arrayWithObjects:
#"NAVIGATION_CONTROLLER_1",
#"NAVIGATION_CONTROLLER_2",
#"NAVIGATION_CONTROLLER_3", nil];
for (int i = 0; i < keys.count; i++) {
UINavigationController *controller = [tabBarController.viewControllers objectAtIndex:i];
NSMutableArray *subControllers = [NSMutableArray arrayWithArray:controller.viewControllers];
// the first view controller would already be on the view controller stack and should be removed
[subControllers removeObjectAtIndex:0];
// for each of the navigation controllers save its view controllers, except for the first one (root)
[archiver encodeObject:subControllers forKey:[keys objectAtIndex:i]];
}
[archiver finishEncoding];
// write that out to file
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
[data writeToFile:[documentsDirectory stringByAppendingPathComponent:#"ARCHIVE_PATH"] atomically:YES];
}
And then, when relaunching
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// set up the tabs
tabBarController = [[UITabBarController alloc] init];
tabBarController.viewControllers = [NSArray arrayWithObjects:
[[[UINavigationController alloc] initWithRootViewController:rootViewController1] autorelease],
[[[UINavigationController alloc] initWithRootViewController:rootViewController2] autorelease],
[[[UINavigationController alloc] initWithRootViewController:rootViewController3] autorelease], nil];
// look for saved data, if any
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSData *archive = [NSData dataWithContentsOfFile:[documentsDirectory stringByAppendingPathComponent:#"ARCHIVE_PATH"]];
// if no data found, skip this step
if (archive) {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:archive];
// set the tab
tabBarController.selectedIndex = [unarchiver decodeIntForKey:#"TAB_INDEX"];
NSArray *keys = [NSArray arrayWithObjects:
#"NAVIGATION_CONTROLLER_1",
#"NAVIGATION_CONTROLLER_2",
#"NAVIGATION_CONTROLLER_3", nil];
// push view controllers up the stack
for (int i = 0; i < keys.count; i++) {
NSArray *controllers = [unarchiver decodeObjectForKey:[keys objectAtIndex:i]];
for (UIViewController *controller in controllers) {
[((UINavigationController *)[tabBarController.viewControllers objectAtIndex:i]) pushViewController:controller animated:NO];
}
}
}
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
}