I am newbie working on objective C. I am facing one problem .
I have tab bar controller containing three view controllers out of which I am concerned about only two VCs named "Setting" and "BBVC" . "BBVC" has a UIButton and "Setting" has a UISwitch (Please see below image).
When button "B" is pressed, in tab bar view controller below code gets executed :
- (void)centerButtonTapped:(id __unused)sender {
BBVC *vc = [[BBVC alloc] init];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
[self presentViewController:nc animated:YES completion:nil];
}
BBVC gets loaded as pop UP
My aim is I want to change the value of "UISwitch" based on "UIButton" action event.
Case 1 : Not On setting View
In this case after pressing the UIButton, when I am on
"Setting" VC, the aim can be achieved by using viewWillappear and UserDefault as shown below :
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"viewWillAppear");
[super viewWillAppear:animated];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[Switch setOn:[defaults boolForKey:#"EnableLIVE"] animated:YES];
}
Case 2 :
In this case I am already on "Settings" VC (i.e. setting view is already loaded) and when button "B" from tab bar is pressed, it gets loaded as a pop up as shown in below image. I am trying to achieve my aim but its not working.
Attempt 1 :
In Setting VC, I updated the code in "viewDidAppear" method but while debugging I got to know after closing BBVC, method "viewDidAppear" is not getting called.
-(void)viewDidDisappear:(BOOL)animated
{
NSLog(#"viewDidDisappear");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[Switch setOn:[defaults boolForKey:#"EnableLIVE"] animated:YES];
}
Attempt 2 :
Use Delegate and Protocols :
I have used delegate and protocols which is working fine but in this case address of UISwitch is nil. Please see below image
Note : UISwitch is created programmatically.
I am clueless here. Any kind of help is appreciated.
Thank you.
If i'm interpreting your question correctly, it sounds like the main problem you're experiencing currently is updating the live switch on the settings VC when it's already displaying, but the BBVC is displaying modally overtop (and it's button is pressed).
You can listen for a notification of changes to user defaults within your settings controller when it loads up, and remove it as an observer once deallocated -- then adjust the switch to the appropriate value once the notification of user defaults changing comes in. Something along these lines:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(userDefaultsDidChange:) name:NSUserDefaultsDidChangeNotification object:nil];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.liveSwitch setOn:[[NSUserDefaults standardUserDefaults] boolForKey:#"EnableLIVE"]];
}
- (void)userDefaultsDidChange:(NSNotification *)notification {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.liveSwitch setOn:[[notification object] boolForKey:#"EnableLIVE"]];
}];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
https://developer.apple.com/documentation/foundation/nsuserdefaultsdidchangenotification?language=objc
Related
Scenario:
I need to show 3 or more popups one after the other on button click in each popup. I have created a different viewcontroller and xib files for each popup. So for displaying each popup I have used presentViewController instead of pushViewController.
That is, I have used this:
[self presentPopupViewController:searchPopUpView animationType:0];
instead of
[self.navigationController pushViewController:searchPopUpView animated:YES];
For dismissing a popup, the following code has been written:
[self dismissPopupViewControllerWithanimationType:0];
Issue:
The popups are displaying perfectly, but the background gets darker and darker whenever a popup shows up. After all popups have been dismissed I have to finally click on the blank screen to remove those darker parts. How to overcome this issue?
I think you are using MJPopupViewController to show pop-up.
If it is so, Then try this.
Suppose there is a controllerA from which you want to show a pop-up controller popupControllerB.
Then in your controllerA add Notifications Observer
Code to write in controllerA :
// Add Notification Observer when your view initialise.
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(dismissPopup) name:#"DISMISS_POPUP" object:nil];
In viewWillDisappear remove the notifications observer
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
This method will be called when you Post-notification from your popupControllerB
-(void)dismissPopup {
[self dismissPopupViewControllerWithanimationType:MJPopupViewAnimationFade];
}
And In popupControllerB, Where you want to dismiss the Pop-up, write this code.
[[NSNotificationCenter defaultCenter] postNotificationName:#"DISMISS_POPUP" object:nil];
Above line of code will call a method written in your controllerA and dismiss the pop-up properly.
If you want to dismiss presented UIViewControllers you can use this code. I have used this approach to dismiss presentedViewControllers. It will dismiss all your presentedViewControllers on your rootViewController.
UIViewController* presVC = self.window.rootViewController;
while (presVC) {
UIViewController* temp = vc.presentingViewController;
if (!temp.presentedViewController) {
[vc dismissViewControllerAnimated:NO completion:^{}];
break;
}
vc = temp;
}
I have A and B UIViewController. A controller has button and textview and click this button it goes to B controller. Then I click the B controller I come back to A controller. But when I come back from B controller I need to hide textview from A controller.
B controller:
-(void)A{
[self.navigationController popToRootViewController animated:YES];
}
You are using poptoviewcontroller method so after going back to previous controller the data still persists. So, before navigating to B controller from A controller hide the textview, so that when navigation view pops to main view, the textview will be hidden
Try to use like this...
There are two solution
1.
- (void)viewDidLoad
{
[super viewDidLoad];
textview.hideen = NO;
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
textview.hidden = YES;
}
2 . Use a key in NSUserDefaults for status . I mean check it is coming from B Controller or not.
There are many options for this:
Before navigating to the next view hide the textview.
Hide the textview in the viewwilldisappear method.
Use a key with NSUserDefaults and check whether it is coming from B controller.
Declare a variable in appdelegate and change its value in B controller check the value in a controller hide the textview based on result.
You can navigate to another page by declaring a view controller and setting it to naviagation controller before that you can set the properties of that controller.
->write code in controller A
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(hideTextview) name:#“hidetextview” object:nil];
}
- (void) hideTextview{
textview.hidden = YES;
}
->in controller B
(void)viewWillDisappear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] postNotificationName:#"hidetextview" object:nil userInfo:nil];
}
The simplest thing you could do is hide the text view in viewcontroller A before you navigate to view controller B so the code needs to be added in the
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
textview.hidden = YES;
}
OR
- (void)navigateToBController{
VCB *b = [[VCB alloc]init];
[self.navigationController pushviewController:b];
textview.hidden = YES;
}
If you are navigating from A -> B -> C and then in viewcontroller C you navigate to root view controller i.e A then in this case what i would suggest is to maintain a variable in NSUSerDefault which would inform you from which viewcontroller it has popped so that you could show / hide your textview.
I have two view controllers, FirstViewController and FourthViewController. FirstViewController is my initial view controller. I present FourthViewController with
UIViewController *fourthController = [self.storyboard instantiateViewControllerWithID:#"Fourth"];
[self presentViewController:fourthController animated:YES completion:nil];
Then, in FourthViewController's .m I'd like to change the text of a UILabel in FirstViewController. So I use
UIViewController *firstController = [self.storyboard instantiateViewControllerWithID:#"First"];
firstController.mainLab.text = [NSMutableString stringWithFormat:#"New Text"];
However, after I use
[self dismissViewControllerAnimated:YES completion:nil];
I find that my mainLab's text has not updated. Does anyone know why?
When you are calling this line from FourthViewController.m you are actually creating a new instance of FirstViewController, rather than using the already created one.
UIViewController *firstController = [self.storyboard
instantiateViewControllerWithID:#"First"];
You can tackle this in two ways.
1) Using notification
post a notification from FourthViewController when label text need to be changed.
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateLabel"
object:self];
In your FirstViewController viewDidLoad methodcreate an observer that waits for this notification to get fired.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateLabelCalled:)
name:#"updateLabel"
object:nil];
Implement updateLabelCalled: and update label.
- (void) updateLabelCalled:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"updateLabel"]){
//write code to update label
}
}
2) Implementing delegate
It is already explained here in stackoverflow. The basic idea is you create a FourthViewController delegate, and create a delegate method to updateLabel. FirstViewController should implement this method.
If you want to update the label on first screen and nothing else then go for notifications. It's better rather you write the delegate. Because you want to update only label text thats it.
I want to present a modal view controller (for a login screen) when my app launches, and also when it becomes active again after a user has hit the home button and then relaunched the app.
I first tried to present the modal view in the root view controller's viewDidAppear: method. That works great when the app first launches, but this method isn't called when the app becomes active again.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self presentModalView];
}
- (void)presentModalView {
if(![AuthenticationService sharedInstance].isAuthenticated) {
_modalVC = [self.storyboard instantiateViewControllerWithIdentifier:self.modalViewControllerIdentifier];
_modalVC.delegate = self;
[self presentViewController:_modalVC animated:YES completion:nil];
}
}
Next I tried to call this from my app delegate in the applicationDidBecomeActive: method.
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// 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.
ModalPresentingUISplitViewController *splitViewController = (ModalPresentingUISplitViewController *)self.window.rootViewController;
[splitViewController presentModalView];
}
This appears to work fine on the surface, but I get a Unbalanced calls to begin/end appearance transitions for <ModalPresentingUISplitViewController: 0x7251590> warning in my log. I get the sense that I'm somehow presenting the modal view before the UISplitView is finished presenting itself, but I don't know how to get around this.
How can I "automatically" present a modal view from my root view controller when the app becomes active, and do it at the "right" moment as not to unbalance my split view controller?
Forgot this question was here. Yes, I have a solution. I can't help but feel that there is a more elegant or right way to do this, but this worked for me...
This assumes you are using ARC and storyboards; You've created a UIViewController for your login view with a modal segue from the UISplitViewController (or whatever your root view controller is).
UISplitViewController (or whatever your root view controller is)
- (id)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(presentModalView) name:UIApplicationDidBecomeActiveNotification object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.viewHasAppeared = YES;
[self presentModalView];
}
- (void) presentModalView {
if(self.viewHasAppeared && !self.userAuthenticated) {
[self performSegueWithIdentifier:#"ShowLoginView" sender:self];
}
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([[segue identifier] isEqualToString:#"ShowLoginView"]) {
JDPLoginViewController *dest = [segue destinationViewController];
dest.delegate = self;
}
}
- (void) dismissLogin {
self.userAuthenticated = YES;
[self dismissViewControllerAnimated:YES completion:nil];
}
Here are the important parts of the code to note...
We're calling presentModalView two places - in viewDidAppear which will take care of presenting our login view when the app first starts and
We are registering the presentModalView as an observer to the UIApplicationDidBecomeActiveNotification event so the method gets called when the app becomes active after being in the background.
Finally, we're creating a BOOL property viewHasAppeared on the UISplitViewController to keep track of whether the UISplitViewController's view has appeared or not so we don't try to present the modal login before the UISplitViewController's view has appeared.
Here are the different scenarios...
App First Starts:
presentModalView is called by the UIApplicationDidBecomeActiveNotification event, but since the UISplitViewController's view isn't loaded (and the viewHasAppeared BOOL is NO, nothing happens. Win. We don't present the view when we shouldn't.
Then eventually viewDidAppear is called, it sets viewHasAppeared to YES and then calls presentModalView. The login screen is presented. Everything works as expected - Yay!
App Becomes Active After Being in Background
presentModalView is called by the UIApplicationDidBecomeActiveNotification event again as in the first scenario, but this time viewHasAppeared is YES, so login view is presented as expected. Yay again!
Like I said, this feels kind of ugly, but it gets the job done until I find a better solution. Hope it works for you.
Have you tried UIView's viewWillAppear?
Chaining off #jpolete's answer I did things a little differently. In addition I wanted the login screen to only appear after the app has been in the background for more the 15 seconds (it's painful for the user to always have to log back in).
The source code for this demo can be found on github
Like #jpolete, I encapsulated most of the logic in the root view controller, which is a navigation controller in my case (iPhone example). The userLoggedIn flags whether or not the user has been authenticated. The presentingLoginController flag lets me know if the login screen is currently presented. backgroundTime holds a time stamp of when the user entered the background. Here is the class extension:
#interface RootNavigationController () <LoginDelegate>
#property (assign, nonatomic) BOOL userLoggedIn;
#property (strong, nonatomic) NSDate *backgroundTime;
#property (assign, nonatomic) BOOL presentingLoginController;
-(void)applicationDidBecomeActive:(NSNotification*) notification;
-(void)applicationDidEnterBackground:(NSNotification*) notification;
#end
When the view is loaded I add the appropriate notification hooks:
#implementation RootNavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Here we trigger the login segue if the user is not authenticated and we are not currently presenting the login controller.
-(void)loginIfNecessary {
if (!self.userLoggedIn && !self.presentingLoginController) {
self.presentingLoginController = YES;
[self performSegueWithIdentifier:#"RootLoginSegue" sender:self];
}
}
Here we set the root view controller to be the loginDelegate of the login controller.
This delegate is informed when a successful login occurs (the login controller is embedded in another nav controller):
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"RootLoginSegue"]) {
UINavigationController *navController = segue.destinationViewController;
LoginTableViewController *loginController = (LoginTableViewController *) navController.topViewController;
loginController.loginDelegate = self;
}
}
When a successful login occurs we do the following:
-(void)didLogin { // LoginDelegate method called to login controller after successsful login
self.presentingLoginController = NO;
self.userLoggedIn = YES;
}
When the view appears for the first time, when it appears after being in the background, or after being "covered up" we login (if needed):
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self loginIfNecessary];
}
When we enter the background we record the time:
-(void)applicationDidEnterBackground:(NSNotification*) notification {
self.backgroundTime = [NSDate date];
}
When we enter the foreground and its the first time or sufficient time has passed then we
force the user to log in again (if necessary):
-(void) applicationDidBecomeActive:(NSNotification*) notification {
const NSTimeInterval maxBackgroundTime = 15.0;
if (!self.backgroundTime || [[NSDate date] timeIntervalSinceDate:self.backgroundTime] > maxBackgroundTime) {
self.userLoggedIn = NO;
}
[self loginIfNecessary];
}
#end
I have created a Tab Bar Application using Xcode that as two views.
The secound view is a UITableViewController.
What I am struggling to do is send data to this view, when the second tab is pressed. I have delegated the Tab Bar to my AppDelegate class and implemented this function:
-(void)tabBarController:(UITabBarController*)tabBarController didSelectViewController:(UIViewController*)viewController
{
// Override point for customization after application launch.
statisticsViewController* assignmentListcont = [statisticsViewController alloc];
NSManagedObjectContext* context = [self managedObjectContext];
assignmentListcont.managedObjectContext = context;
[assignmentListcont release];
}
The second view is displaying fine but the data hasn't been passed. I imagine its because I haven't programmed the second views transition but I'm unsure of how to do this if I already have a .xib file doing it for me? Is there some way to just pass the data without problems or even retrieve the data once inside the view?
You could use notifications.
In the view that you want to receive the data, put this in viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(yourSelector:)
name:#"addedData"
object:nil];
Then implement the method that receives that data:
- (void)yourSelector:(NSNotification *)notification {
Foo *foo;
foo = [notification object];
//do something else
}
Now in the class where the data is originated from, you post a notification that new data was created. Also, you pass along the data that you want to have the other method receive.
[[NSNotificationCenter defaultCenter] postNotificationName:#"addedData"
object:foo];
I've done this to send a value from one view controller to another, hope it helps
UITabBarViewController *var = [self.storyboard instantiateViewControllerWithIdentifier:#"name"]; //I select the UITabBarController
otherViewController *var2 = [var.childViewControllers objectAtIndex:0]; //I Select the first ViewController from that UITabBarController
var2.variable = #"value";
[self.navigationController pushViewController:var animated:YES];