In my HomeViewController's viewDidAppear method, I have the following code:
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL didRunBefore = [defaults boolForKey:#"didRunBefore"];
if (!didRunBefore) {
// check to see if children already exist (previous user)
NSArray *children = [CoreDataHelper getObjectsForEntity:NSStringFromClass([Child class]) withSortKey:#"name" andSortAscending:YES andContext:self.managedObjectContext];
if (children.count == 0) {
// send user to create fist child
UIStoryboard *storyboard = self.storyboard;
ChildEditTableViewController *editController = [storyboard instantiateViewControllerWithIdentifier:#"ChildEditControllerID"];
NSManagedObjectContext *newContext = [[NSManagedObjectContext alloc] init];
newContext.parentContext = self.managedObjectContext;
editController.managedObjectContext = newContext;
[self.navigationController pushViewController:editController animated:NO];
}
}
}
Here's the code from ViewDidLoad in ChildEditTableViewController:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"Child Edit controller loaded");
self.availablePicker.delegate = self;
self.bankedPicker.delegate = self;
self.carryOverCellIsShowing = NO;
self.isNewChild = self.child == nil;
self.imageButton.layer.cornerRadius = self.imageButton.frame.size.width/2;
self.imageButton.layer.masksToBounds = YES;
[[self.imageButton imageView] setContentMode: UIViewContentModeScaleAspectFill];
if (self.isNewChild) {
// check to see if it's user's first time running app
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL didRunBefore = [defaults boolForKey:#"didRunBefore"];
if (!didRunBefore) {
// hide Home back button
[self.navigationItem setHidesBackButton:YES];
// update didRunBefore to yes
[defaults setBool:YES forKey:#"didRunBefore"];
[defaults synchronize];
}
self.child = [NSEntityDescription insertNewObjectForEntityForName:#"Child" inManagedObjectContext:self.managedObjectContext];
self.title = NSLocalizedString(#"Add New", #"Add New Title");
}
else {
if (self.child.profileImage != nil) {
[self.imageButton setImage:[UIImage squaredImageFromImage:[UIImage imageWithData:self.child.profileImage] scaledToSize:self.imageButton.frame.size.height] forState:UIControlStateNormal];
}
self.name.text = self.child.name;
self.autoBankSwitch.on = [self.child.autoBank boolValue];
self.carryOverSwitch.on = ![self.child.resetDailyTotal boolValue];
[self setCarryOverSwitchVisibility:self.autoBankSwitch];
}
// This will remove extra separators from tableview
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
}
That code works fine as far as I can see the ChildEditTableViewController load on the screen, but then it automatically pops back to the home controller. I've checked the code in the child controller and the only time I pop the controller is when the user hits a button.
Here's the Save IBAction where I pop the controller:
- (IBAction)save:(UIBarButtonItem *)sender {
[self saveToDB:sender];
[self.navigationController popViewControllerAnimated:YES];
}
If I use self.navigationController setViewControllers instead, this does not happen and ChildEditTableViewController stays loaded on the screen, but clicking the Save button (which pops the view controller) doesn't do anything.
Any ideas? (Thanks!)
**** EDIT *****
I noticed it was working fine in iOS 7.1 and 7.03. The only difference from an UI perspective was this piece of code below:
// enable handling of push notifications
if ([application respondsToSelector:#selector(registerUserNotificationSettings:)]) {
// use registerUserNotificationSettings
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIRemoteNotificationTypeBadge
|UIRemoteNotificationTypeSound
|UIRemoteNotificationTypeAlert) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
} else {
// use registerForRemoteNotifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
}
In iOS 8 I was getting a prompt to allow notifications on the simulator (something that doesn't work on the sim in prior versions). After I clicked ok is when the EditChild controller would get popped. So I commented out that code in the app delegate and the controller stays loaded just like in iOS 7.
****** EDIT ******
Below is the ApplicationDidBecomeActive code
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(#"%s", __PRETTY_FUNCTION__);
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
// move user to home screen so app is locked each time they open it (but not on first use)
SWRevealViewController* revealController = (SWRevealViewController*)self.window.rootViewController;
UINavigationController *nav = (UINavigationController *)revealController.frontViewController;
[nav popToRootViewControllerAnimated:YES];
}
So this is the culprit. This code is getting called again right after the user clicks Accept on the notifications registration alert, for some crazy reason.
I am thinking that the the callbacks in your application delegate are doing something to your view/controller hierarchy. I would add some break points in your application delegate methods applicationWillResignActive:, applicationDidBecomeActive: and see if they are doing anything.
Related
I know similar questions are asked but none of them helped.
My Observation
There are two cases: 1) When app is running and in the foreground, visible to the user 2) User presses home button and app moves to the background, not visible to the user. In case 1) when a phone call comes and end I get all the events with no problem. In case 2) I get no events, but when user opens the app, then I get all the events that has happened when app was in the background.
My Question
How can I get my code working even when app is in the background? Or, how can I move my app from background to foreground when those events happen?
What I've tried
I've tried enabling background fetch at Background Modes at Info.plist with an implementation of -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler. It had no effect.
Below is my code:
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// some other code above
self.callCenter = [[CTCallCenter alloc] init];
[self.callCenter setCallEventHandler:^(CTCall *call)
{
UINavigationController *nc = (UINavigationController *) [[(AppDelegate *) [[UIApplication sharedApplication] delegate] window] rootViewController];
MainViewController *vc = nc.viewControllers[0];
[vc setCallState:call.callState];
}
}
MainViewController.m
-(void)setCallState:(NSString *)callStateString
{
if ([callStateString isEqualToString:CTCallStateConnected])
{
NSLog(#"call connected!");
} else if ([callStateString isEqualToString:CTCallStateDialing])
{
NSLog(#"call dialing!");
} else if ([callStateString isEqualToString:CTCallStateDisconnected])
{
NSLog(#"call disconnected!");
} else if ([callStateString isEqualToString:CTCallStateIncoming])
{
NSLog(#"call incoming!");
} else
{
NSLog(#"unknown call state %#", callStateString);
}
}
I've add the background task feature to my application. Here is my app delegate
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setWindow:[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]];
[self.window setBackgroundColor:[UIColor blackColor]];
if (application.applicationState != UIApplicationStateBackground) {
// Application is launch in because user tap the app icon or from springboard
if ([application respondsToSelector:#selector(setMinimumBackgroundFetchInterval:)]) {
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
}
} else {
// Application is launch in background mode
}
RootViewController *rootViewController = [[RootViewController alloc] initByDevice];
[self.window setRootViewController:rootViewController];
[self.window makeKeyAndVisible];
return YES;
}
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[[DataManager instance] updateDataWithMaxAttempt:5 block:^(BOOL success, NSArray *newData) {
if (success) {
if ([newData count] > 0) {
completionHandler(UIBackgroundFetchResultNewData);
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
}];
}
And this is my root view controller
// RootViewController.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(#"Did appear");
// Do something that I want it to happen only when the application is visible to user
}
When the user tap the app icon, application works like what I expected. I see "Did appear" in console and stuff is happening after that like I expected.
But when the application awake to perform background task (which not visible to user) the RootViewController's viewDidAppear still getting call because of this line
[self.window makeKeyAndVisible];
By calling makeKeyAndVisible, it makes RootViewController visible even though it's only awake for background task. To fix this, I have to move this line to applicationDidBecomeActive
- (void)applicationDidBecomeActive:(UIApplication *)application {
[self.window makeKeyAndVisible];
}
As a result, the RootViewController's viewDidAppear now being call only when the application is in foreground (visible to user). But, my concern is, when application is in device memory (either active or inactive) applicationDidBecomeActive will be call several times.
user launch app
application become active again from springboard
device unlock
finish call
Is there any issue if I call UIWindow's makeKeyAndVisible several times during the application life cycle?
You can easily ensure that makeKeyAndVisible only happens once: wrap it.
if (!self.window.keyWindow) {
[self.window makeKeyAndVisible;
} else if (self.window.hidden) {
self.window.hidden = NO;
}
All,
I just cannot find an answer to this question. The settings View Controller needs to shown once on startup ONLY. So when you download the app from the App Store / test flight.
I have it correct, so it runs it first, thats fine.
when you have finished with the settings page it goes to the main page and when you move the app to the background it carries on from where it left off. thats fine.. But... When you swipe the app away by double pressing the home button and pushing the app up to remove (ios7) it goes back to the settings screen again but it should carry on from where it left off.
So in my App Delegate, I have :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *savedValue = [[NSUserDefaults standardUserDefaults] stringForKey:#"SettingsShown"];
NSLog(#"%#", savedValue);
Reachability *reachability = [Reachability reachabilityWithHostname:#"www.outtonightapp.com"];
[reachability startNotifier];
NSUserDefaults *settingsscreen = [NSUserDefaults standardUserDefaults];
[settingsscreen registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],#"firstTime", nil]];
//BOOL firstTime = [settingsscreen boolForKey:#"firstTime"];
BOOL firstTime = [settingsscreen boolForKey:#"SettingsShown"];
if (!firstTime) {
//if ( firstTime==YES) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"SettingsShown"];
[[NSUserDefaults standardUserDefaults] synchronize];
self.window.rootViewController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:#"SetUpNav"];
}
else
{
return YES;
}
This did work until I had to recreate my settings VC.. any advice would be great.
In similar situation I used delegation pattern.
Assume you have your initial view controller initialVC. And special settings view controller (SetUpNav) which is meant to be run only first time when defaults is not set. Then you could do the following:
You define SetUpNavdelegate protocol in SetUpNavViewController.h and property "initiator" conforming to that protocol
#protocol SetUpNavDeleagte;
#interface SetUpNavViewController : UIViewController
#property (strong,nonatomic) id <SetUpNavdelegate> initiator;
// the rest
#end
#protocol SetUpNavdelegate <NSObject>
-(void)setupFinished;
#end
In your InitialVC' viewDidLoad you do:
Check Your defaults are set properly or not by determine "firstTime" BOOL value
and fire setUpNav controller in code
-(void)viewDidLoad
{
// Check your defaults for consistency
if (firstTime){
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main OR you SB name" bundle:nil];
UIViewController *setUpNavVC = [loginStoryboard instantiateViewControllerWithIdentifier:#"SetUpNav"];
setUpNavVC.initiator = self;
YouAppDelegateClass *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.window.rootViewController = setUpVC;
// Here you don't need animation as I assume it is the very first screen
}
// ... the rest
}
-(void)setupFinished
{
// Here You animately restoring your initial vc.
YouAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.window.rootViewController = self; // This line is for device orientation sync
[UIView transitionWithView:appDelegate.window
duration:FINISH_DURATION
options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionCurveEaseInOut
animations:^{ appDelegate.window.rootViewController = self; }
completion:nil];
// setUpNav controller will be dealloced by ARC
}
In your setUpNavViewController after you finished all defaults job you set notification for your delegation:
[self.initator setupFinished];
In may app this setting work is actually a separate storyboard with it's own workflow in it. By the way using this approach you are able not only show it first time but whenever your app's user defaults is not set properly (like if you using settings bundle). You can show it modally, in navigation stack or in pop over (iPad case). It is a more generic approach.
Summary: The one who starts, he finishes.
I have some trouble. So i have AvPlayer and UIButton with play/stop.
Also: I have three UiViewControllers. I need that when I click on the first button on the first UIVIewController on the second controller, the third controller button is pressed also, respectively, on the contrary, too. How its make? Any proposition?
It's simple code - push on button - play URL Stream and also when push again stop music.
-(IBAction)playRadioButton:(id)sender
{
if(clicked == 0) {
clicked = 1;
NSLog(#"Play");
NSString *urlAddress = #"http://URLRADIOSTREAM";
NSURL *urlStream = [NSURL URLWithString:urlAddress];
myplayer = [[AVPlayer alloc] initWithURL:urlStream];
[myplayer play];
[playRadioButton setTitle:#"Pause" forState:UIControlStateNormal];
}
else
{
NSLog(#"Stop");
[myplayer release];
clicked = 0;
[playRadioButton setTitle:#"Play" forState:UIControlStateNormal];
}
}
If you have several controllers that you need to notify about an event on another controller you can use NSNotificationCenter
Eg. in one controller in ViewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playBtnClicked:)
name:#"BTN_CLICKED"
object:nil];
Also in the same controller define the selector e.g.
-(void)playBtnClicked:(NSNotification *)pNotification
{
// do something
}
In the other controller trigger it by using
[[NSNotificationCenter defaultCenter]
postNotificationName:#"BTN_CLICKED" object:nil];
If you don't want to use nsnotifications then use protocol and notify other viewcontrollers by using delegates
First of all, are the 3 view controllers allocated and initialized at once? If not, I recommend you set a property on your AppDelegate class, like this:
#interface AppDelegate
#property (nonatomic, assign) BOOL commonButtonPressed;
// All your code here
#end
And you can set this property like this:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.commonButtonPressed = YES; // or NO;
Then, from your UIViewController classes:
- (void)viewWillAppear:(BOOL)animated {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if (appDelegate.commonButtonPressed) {
// Logic of what happens to the button goes here.
}
}
Another way of doing this, without touching your AppDelegate class is using NSUserDefaults, like this:
[[NSUserDefaults standardDefaults] setBool:(<YES or NO>) forKey:#"commonButtonPressed"];
[[NSUserDefaults standardDefaults] synchronize];
You can read back the value like this:
BOOL buttonPressed = [[NSUserDefaults standardDefaults] boolForKey:#"commonButtonPressed"];
I have a small app that attempts to do basic login with FB using the exact methodology used in the Facebook tutorial for logging in here: https://developers.facebook.com/docs/howtos/login-with-facebook-using-ios-sdk/
The desired behavior is that the user be able to login to FB and stay logged in until they have explicitly chosen to log out by clicking on the logout button. They should stay logged in across launches of the app, even if the app is forcibly killed (but not deleted).
What is happening is that after clicking on the Login button, the app correctly logs the user in, but once I navigate back to the root view controller, and then back to the FacebookViewController, the button shows Login again, instead of log out. Something somewhere is killing the session.
I am not sure what code will be asked for, but here is a bunch:
#import "FacebookViewController.h"
#interface FacebookViewController ()
#end
#implementation FacebookViewController
#synthesize authButton;
#synthesize postSwitch;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(sessionStateChanged:)
name:FBSessionStateChangedNotification
object:nil];
// Check the session for a cached token to show the proper authenticated
// UI. However, since this is not user intitiated, do not show the login UX.
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate openSessionWithAllowLoginUI:NO];
// Set toggle
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"] isEqualToString:#"Yes"]) {
//set to yes
[postSwitch setOn:YES animated:YES];
} else {
// set to no
[postSwitch setOn:NO animated:YES];
}
NSLog(#"postToFB is now %#",[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"]);
}
- (IBAction)authButtonAction:(id)sender
{
AppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];
// If the user is authenticated, log out when the button is clicked.
// If the user is not authenticated, log in when the button is clicked.
if (FBSession.activeSession.isOpen) {
[appDelegate closeSession];
} else {
// The user has initiated a login, so call the openSession method
// and show the login UX if necessary.
[appDelegate openSessionWithAllowLoginUI:YES];
}
}
- (IBAction)togglePostSwitch:(id)sender
{
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"] isEqualToString:#"Yes"]) {
//set to No
[[NSUserDefaults standardUserDefaults] setObject:#"No" forKey:#"postToFB"];
} else {
// set to yes
[[NSUserDefaults standardUserDefaults] setObject:#"Yes" forKey:#"postToFB"];
}
NSLog(#"postToFB is now %#",[[NSUserDefaults standardUserDefaults] objectForKey:#"postToFB"]);
}
- (void)sessionStateChanged:(NSNotification*)notification
{
if (FBSession.activeSession.isOpen) {
[self.authButton setTitle:#"Logout of Facebook" forState:UIControlStateNormal];
} else {
[self.authButton setTitle:#"Login to Facebook" forState:UIControlStateNormal];
}
}
#end
The MainViewController (the root VC) has the following in the ViewDidLoad, and the ViewWillAppear:
- (void)viewDidLoad
{
[[self navigationController] setNavigationBarHidden:YES animated:NO];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
BOOL open = [appDelegate openSessionWithAllowLoginUI:NO];
if (open) {
NSLog(#"User session found (MainVC viewDidLoad)");
} else {
NSLog(#"no session detected");
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:NO];
[[self navigationController] setNavigationBarHidden:YES animated:NO];
}
I am not real clear on why this behavior is occurring. Any help would be greatly accepted :)
I actually traced this back to an error in labeling the login button. The session was always open even when the button said 'log in'. I managed to correct this issue, but now I am discovering that indeed the app will not stay logged in across launches, but at least the login/logout button is always correct!