The App I'm trying to do has a tabbar controller.
When the App starts, I'm getting the user location in the AppDelegate and when I've got the accuracy I need the AppDelegate sends an NSNotification to my App's starting page (index 0 of the tab bar controller).
Upon receiving the notification, this view tries to send an email with the user coordinates and other data, but as soon as the MFMailComposeViewController is presented I get the following error:
Warning: Attempt to present <MFMailComposeViewController: 0x98a0270> on <UITabBarController: 0x988c630> whose view is not in the window hierarchy!
What am I missing?
Thanks.
EDIT: adding some code...
This is what I've got in my AppDelegate.m:
- (void) locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSUserDefaults *phoneNumbers = [NSUserDefaults standardUserDefaults];
NSDate *eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if (abs(howRecent) < 10.0) {
[self locationUpdate:newLocation];
smsLoc = newLocation;
if ([[phoneNumbers objectForKey:#"sendSMS"] isEqualToString:#"yes"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"sendSMS" object:nil];
} else if ([[phoneNumbers objectForKey:#"sendEmail"] isEqualToString:#"yes"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"sendEmail" object:nil];
}
}
}
Then, in my first view controller I have:
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(sendSMS:) name:#"sendSMS" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(sendEmail:) name:#"sendEmail" object:nil];
}
And at the end, the selector for "sendSMS" (the other is pretty similar):
- (void)sendSMS: (NSNotification *)notification {
NSUserDefaults *phoneNumbers = [NSUserDefaults standardUserDefaults];
if ([phoneNumbers objectForKey:#"first"] || [phoneNumbers objectForKey:#"second"]) {
MFMessageComposeViewController *controller = [[MFMessageComposeViewController alloc] init];
if ([MFMessageComposeViewController canSendText]) {
AppDelegate *deleg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
controller.body = [NSString stringWithFormat:#"some message with coordinates %.4f - %.4f", [deleg currentLocation].coordinate.latitude, [deleg currentLocation].coordinate.longitude];
controller.recipients = [NSArray arrayWithObjects:[phoneNumbers objectForKey:#"first"], [phoneNumbers objectForKey:#"second"], nil];
controller.messageComposeDelegate = self;
[self presentModalViewController:controller animated:YES];
}
}
}
}
Second edit: adding some more code.
UITabBarController *tabBarController = [[UITabBarController alloc] init];
tabBarController.delegate = self;
tabBarController.selectedIndex = 0;
[[tabBarController.tabBar.items objectAtIndex:0] setTitle:NSLocalizedString(#"Home", nil)];
[[tabBarController.tabBar.items objectAtIndex:1] setTitle:NSLocalizedString(#"Requests", nil)];
[[tabBarController.tabBar.items objectAtIndex:2] setTitle:NSLocalizedString(#"Account", nil)];
[[tabBarController.tabBar.items objectAtIndex:3] setTitle:NSLocalizedString(#"Settings", nil)];
//some other controls from DB
[[tabBarController.tabBar.items objectAtIndex:1] setBadgeValue:[NSString stringWithFormat:#"%d",number]];
The tabbarController has been made via IB, but I've added the code above in my AppDelegate because I need to localize the tab bar items and to add a badge to one of them.
Am I doing something wrong here?
I'm not sure if you have solve this issue. The error message means the viewcontroller you use to present another modal viewcontroller is not visible on the window. This can happen for e.g:
[VC1 presentModalViewController:VC2];
// Error here, since VC1 is no longer visible on the window
[VC1 presentModalViewController:VC3];
If your issue is like above, you can fix it like:
if (self.modalViewController != nil) {
[self.modalViewController presentModalViewController:VC3 animated:YES];
} else {
[self.tabBarController presentModalViewController:VC3 animated:YES];
}
If that doesn't fix your issue, maybe you can try to present using self.tabBarController instead of self. Again just suggestion, not sure if it works though.
Using this may help someone: [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:picker animated:NO completion:nil];
Since modalViewController and presentModalViewController are deprecated, the following is what works for me:
presentingVC = [[UIApplication sharedApplication] keyWindow].rootViewController;
if (presentingVC.presentedViewController) {
[presentingVC.presentedViewController presentViewController:VC3 animated:YES completion:nil];
} else {
[presentingVC presentViewController:VC3 animated:YES completion:nil];
}
You can follow this pattern
[VC1 presentModalViewController:VC2];
//
[**VC2** presentModalViewController:VC3];
Related
I have some functions in my SecondViewController.m file and I want to call these functions when the application is launched. I have tried to do it like this, but it’s not working.
I have put a print statement in the function to see if it is getting called or not and it appears that the print statement is executed correctly.
Here is the following code:
In SecondViewController.h:
#interface AlertsManagementController : UIViewController {
PushNotificationSettings *pushNotificationSettings;
IBOutlet UISwitch *switch1;
IBOutlet UISwitch *switch2;
}
#property (nonatomic, retain) PushNotificationSettings *pushNotificationSettings;
In SecondViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
appDelegate = (projectAppDelegate *)[[UIApplication sharedApplication] delegate];
pushNotificationSettings = [[PushNotificationSettings alloc] init];
NSDictionary * settings = [pushNotificationSettings getPushSettings];
}
-(void)modificationPush
{
if (switch1.on && switch2.on)
[switch2 setOn:NO];
printf("Function 1 executed!");
}
-(void)sendTokenFunc{
NSMutableDictionary *preferences1 = [[NSMutableDictionary alloc] init];
if (switch1.on)
[preferences1 setObject:#"1" forKey:#“switch1”];
else
[preferences1 setObject:#"0" forKey:#“switch1”];
if (switch2.on)
[preferences1 setObject:#"1" forKey:#“switch2”];
else
[preferences1 setObject:#"0" forKey:#“switch2”];
[pushNotificationSettings savePushSettingsWithDictionary:preferences1];
[preferences1 release];
[appDelegate uploadToken:[appDelegate tokenValue]];
printf("Function 2 executed!");
}
In appDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
SecondViewController * vc = [[SecondViewController alloc]init];
[vc modificationPuch];
[vc sendTokenFunc];
}
This can be done by using NSNotificationCenter.
First go to your SecondViewController.m file and in viewDidLoad write the following code.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myNotification:) name:#"myNotification" object:nil];
Then outside viewDidLoad make a function myNotification. I will show you the eg below
-(void)myNotification:(NSNotification*)notification
{
//write the code you wanna excecute when the app opens here
}
Go to appDeligate.m and write this code in application didFinishLaunchingWithOptions
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:self userInfo:nil];
Thank you,
Happy coding;)
But if SecondViewController didn't inited, you must get EXC_BAD_ACCESS :)
I need to open existing viewcontroller from AppDelegate while receiving push notification. Currently i am opening new one every time so issue is that it is called viewDidLoad every time and all variable are reinitialized again and again.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[[NSUserDefaults standardUserDefaults] setObject:#"Yes" forKey:#"Got Message"];
[[NSUserDefaults standardUserDefaults] setObject:userInfo forKey:#"message"];
[[NSUserDefaults standardUserDefaults]synchronize];
HomeViewController* room = [[HomeViewController alloc] init];
[self.window.rootViewController presentViewController:room
animated:NO
completion:nil];
}
Try to do so:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
[[NSUserDefaults standardUserDefaults] setObject:#"Yes" forKey:#"Got Message"];
[[NSUserDefaults standardUserDefaults] setObject:userInfo forKey:#"message"];
[[NSUserDefaults standardUserDefaults]synchronize];
[self.window.rootViewController presentViewController:self.room
animated:NO
completion:nil];
}
- (UIViewController *)room {
if (_room == NULL) {
_room = [[HomeViewController alloc] init];
}
return _room;
}
Then you can reuse your view controller (However, it will exposure the view controller in your AppDelegate, which may be a taste in clean code).
Since you want to use a existing view controller, why do you use the code HomeViewController* room = [[HomeViewController alloc] init];?
Follow your goal, my suggestion is use a property to retain the existing view controller, just like:
#property (strong, nonatomic) UIViewController *existingViewController;
and you
[self.window.rootViewController presentViewController:existingViewController
animated:NO
completion:nil];
You can get UINavigationController in AppDelegate meethod
UIViewController *yourViewController = //your view controller to show after push
UINavigationController *navController = self.window.rootViewController;
[navController popToViewController:yourViewController animated:YES]
I am pretty stuck with this and hoping someone can show me some light about this.
I need help to figure out how to refresh the table list on the main view from the slide-in menu.
IN MAIN VIEW
So basically, I have a main view that has a list of events. I use this function to retrieve data from the database:
[self loadEvents:eventCat sortEvent:nil searchDate:dateSelected];
Inside that function, it has the script that gets data, determines if there is content and then reloads the table:
- (void)loadEvents:(NSString*)searchType sortEvent:(NSString*)sortType searchDate:(NSString *)dateSelect{
NSString *url = [NSString stringWithFormat: #"http://www.url.com/eventList.php];
NSURL *eventURL = [NSURL URLWithString: url];
NSLog(#"%#", eventURL);
NSData *jsonData = [NSData dataWithContentsOfURL: eventURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error: &error];
self.eventList = [NSMutableArray array];
NSArray *eventArray = [dataDictionary objectForKey:#"fitpass"];
//NSString *eventFound = [dataDictionary objectForKey:#"notfound"];
//if nothing is found
if ([dataDictionary objectForKey:#"notfound"]){
tableView.hidden = YES;
noEvent.hidden = NO;
NSLog(#"123");
}else{
noEvent.hidden = YES;
tableView.hidden = NO;
NSLog(#"RELOADDATA");
for(NSDictionary *eventDictionary in eventArray){
Event *event = [Event blogPostWithTitle:[eventDictionary objectForKey:#"name"]];
event.eventTime = [eventDictionary objectForKey:#"event_time"];
[self.eventList addObject:event];
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
}
I tried using the NSNotificationCenter method to refresh the data. So I have this in the viewDidLoad Method:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshTableWithNotification:) name:#"RefreshTable" object:nil];
Then this function right after ViewDidLoad Method:
- (void)refreshTableWithNotification:(NSNotification *)notification
{
[self.tableView reloadData];
}
IN THE SLIDE MENU VIEW
In the side menu, I have a table list to populate the different categories, and when use click a category, it should refresh the event list in the main view:
//on direct press, trigger segue
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//get current value and set the eventCategory ID in userDefault
NSDictionary *catPicked = [self.catList objectAtIndex:indexPath.row];
NSString *eventCat =[catPicked objectForKey:#"ID"];
NSLog(#"%#Picked:", eventCat);
[userDefaults setObject:eventCat forKey:kEventCat];
[userDefaults synchronize];
NSLog(#"%#entered", [userDefaults objectForKey:kEventCat]);
//redirect back to eventList;
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
UIViewController *vc = [mainStoryboard instantiateViewControllerWithIdentifier: #"eventListViewController"];
//refresh data
EventLists *destinationController = [[EventLists alloc] init];
[destinationController loadEvents:eventCat sortEvent:nil searchDate:nil];
//send notofication over
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefreshTable" object:nil userInfo:nil];
[destinationController.view setNeedsDisplay];
[[SlideNavigationController sharedInstance] popToRootAndSwitchToViewController:vc
withSlideOutAnimation:self.slideOutAnimationEnabled
andCompletion:nil];
return;
}
What I am stuck is that I can see the data (eventList) is updated, but the view does not refresh...
Any help would be very much appreciated!!!
Rory!!!
Thank you so much for your help!! I finally figured it out with your hint.
So basically I thought it was passing he data because of my log. But it was just merely running the function. No data was passed.
And the solution was actually rather simple. Since the data was not really being passed over, refreshing the table in the notification function will not work. The data needs to be reloaded. In other word, I just needed to change the notification function in the Main View to:
- (void)refreshTableWithNotification:(NSNotification *)notification
{
[self loadEvents:[userDefaults objectForKey:kEventCat] sortEvent:nil searchDate:dateSelected];
}
Then my Side Menu View is now much simpler since all I needed was the Notification call and view switch:
//redirect back to eventList;
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
UIViewController *vc = [mainStoryboard instantiateViewControllerWithIdentifier: #"eventListViewController"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefreshTable" object:nil userInfo:nil];
And yes! I needed to move the notification in the Main view to ViewDidAppear:
- (void)viewDidAppear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshTableWithNotification:) name:#"RefreshTable" object:nil];
}
Thank you so much for your help!!
I am creating a Chat kind of iPhone application using Apple push notification services. APN's is working fine and i am getting notification when user is receiving new message. So, i have set one Toast pop up in didReceiveRemoteNotification of my App Delegate class. The problem is, i am getting Toast pop up in every View Controller screen because i have added Toast on my main window itself. but can you please help me that how can i hide this Toast pop up from one of my Chat List View Controller screen. How can i check which View Controller is loaded currently on my window view, when application is in foreground?
here is my code :
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo
{
if ( application.applicationState == UIApplicationStateActive ){
NSString *personName = [[userInfo valueForKey:#"aps"] valueForKey:#"user_name"];
NSString *meassge = [NSString stringWithFormat:#"New message from %#.", personName];
[[self window] makeToast:meassge duration:1.0 position:#"center"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadTheTable" object:nil];
}
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
[[UIApplication sharedApplication] cancelAllLocalNotifications];
}
Thanks!
Declare below methods in AppDelegate.m
- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)rootViewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)rootViewController;
return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
} else if (rootViewController.presentedViewController) {
UIViewController* presentedViewController = rootViewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
} else {
return rootViewController;
}
}
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo
{
if ( application.applicationState == UIApplicationStateActive ){
NSString *personName = [[userInfo valueForKey:#"aps"] valueForKey:#"user_name"];
NSString *meassge = [NSString stringWithFormat:#"New message from %#.", personName];
if(![[self topViewController] isKindOfClass:[ChatListViewController class]])//ChatListViewController is your viewcontroller
[[self window] makeToast:meassge duration:1.0 position:#"center"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadTheTable" object:nil];
}
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
[[UIApplication sharedApplication] cancelAllLocalNotifications];
}
My First View is a UIViewController i have a UITable View in it and it works just fine.
From the first view i push another view, in that view i select a group name then the view dismiss, what i want is when i select the group name from the second view the first view loads the values of that group name to the table view, this is the code i use and its not working
the second view code
- (IBAction)sendNames:(id)sender {
if (!keyValue) {
NSLog(#"Didnt Select Any Group To Send");
return;
}
ViewController *theInstance = [[ViewController alloc] init];
[theInstance setGroupName:keyValue];
[theInstance ReceivedGroup:nil];
[self dismissViewControllerAnimated:YES completion:nil];
//tried that option and didnt work too, i added the rest of the code in the viewDidLoad
//[[NSNotificationCenter defaultCenter] postNotificationName:#"RG" object:nil];
}
keyValue is NSString that been set on tableView didSelectRowAtIndexPath
the first view
- (IBAction)ReceivedGroup:(id)sender {
NSString *path = [self dataFilePath];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *tempArray = [dict objectForKey:groupName];
nameArray = [[NSMutableArray alloc] init];
[nameArray addObjectsFromArray:tempArray];
[nameTable reloadData];
NSLog(#"Group Name : %#",groupName);
NSLog(#"Array : %#",nameArray);
}
groupName is NSString
In the log i get the groupName and the nameArray printed
but the tableView is empty.
EDIT: I fixed the problem and posted my answer
Your viewcontroller "theInstance" will be deallocated immediately after calling sendNames: so nothing's gonna happen. You should rather pass a pointer of your first viewcontroller to your second viewcontroller (e.g. set it as a property) and perform all operations on this viewcontroller.
subclass UITableViewController
#interface SecondViewController : UITableViewController
#property (nonatomic, strong) ViewController *firstViewController;
#end
if using storyboard segues implement in your first viewcontroller :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
((SecondViewController *) segue.destinationViewController).firstViewController = self;
}
sendNames: should then look like this:
- (IBAction)sendNames:(id)sender {
if (!keyValue) {
NSLog(#"Didnt Select Any Group To Send");
return;
}
ViewController *theInstance = self.firstViewController;
[theInstance setGroupName:keyValue];
[theInstance ReceivedGroup:nil];
[self dismissViewControllerAnimated:YES completion:nil];
//tried that option and didnt work too, i added the rest of the code in the viewDidLoad
//[[NSNotificationCenter defaultCenter] postNotificationName:#"RG" object:nil];
}
I Fixed it by sending the keyValue using NSNotificationCenter
Here is my code now
For The Second View
- (IBAction)sendNames:(id)sender {
if (!keyValue) {
NSLog(#"Didnt Select Any Group To Send");
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"RG" object:keyValue];
[self dismissViewControllerAnimated:YES completion:nil];
}
Then On The First View
- (void)receiveNotification:(NSNotification *)notification {
NSString *Key = [notification object];
nameArray = [[NSMutableArray alloc] init];
[nameArray removeAllObjects];
NSString *path = [self dataFilePath];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *tempArray = [dict objectForKey:Key];
[nameArray addObjectsFromArray:tempArray];
[self.nameTable reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[nameTable numberOfRowsInSection:0] - 1 inSection:0];
[nameTable scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"RG" object:nil];
}