I have 2 viewcontrollers with segue "page curl"
viewcontrollerA => pagecurl => viewcontrollerB
and Now I want to update viewcontrollerA since user make some change at viewcontrollerB.
I tryed:
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"mystoryboard"
bundle:nil];
UIViewController* vc = [sb instantiateViewControllerWithIdentifier:#"ExampleViewController"];
[vc ViewDidLoad]; // or ViewWillAppear or ViewDidApear
it works only for the NSLog I put in those functions.
but none of them works with the function which check out Coredata and update the interface.
please help
try this code:
you add parent class
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(actionremovecalender:)
name:#"subMitReport"
object:nil];
-(void)actionremovecalender:(NSNotification *)notification
{
[self ViewDidLoad]
}
call child class
[[NSNotificationCenter defaultCenter]postNotificationName:#"subMitReport" object:nil]
You can send a NSNotification that the parent will receive, or you can set a delegate with a method implemented by the parent view.
In both cases, just reload the view.
In a one-to-one relation, you should prefer the delegation pattern. Just add a weak reference of viewcontrollerA to your viewcontrollerB. You can just call a method (in this case viewDidLoad method) of viewcontrollerA using the reference so you can refresh the views. But I'd prefer declaring a protocol for delegation to prevent tight coupling of two view controllers.
ViewWillApear or ViewDidApear will be called since there is any object changes in the viewcontroller, but if you want to change your viewcontrollerA from another viewcontrollerB, that require NSNotificationCenter to call the function from viewcontrollerA
you can always use NSNotificationCenter to update your parent viewcontroller
At your parent viewcontroller put this:
//since child view controller calls turnItOff, notification center calls function "turnButtonCountinuesOff"
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(turnButtonCountinuesOff) name:#"turnItOff" object:nil];
turnButtonCountinuesOff is your function at parent viewcontroller
At your child viewcontroller put this:
//connect to parent UI view controller calls notification turnItOff.
[[NSNotificationCenter defaultCenter] postNotificationName:#"turnItOff" object:nil];
hope it helps.
Related
I have three View Controllers. Let's call them BaseVC, firstVC and secondVC. FirstVC is presented modally by BaseVC. SecondVC is pushed by firstVC. There is a one button on each firstVC and secondVC. By clicking them, I want to dismiss the current VC and let BaseVC do something. So I created a protocol, let BaseVC obey it, and set BaseVC as firstVC's delegate. When I set secondVC's delegate from firstVC, breakpoint show it succeeding. However when I call delegate from secondVC, it shows _delegate is nil.
Is it because delegate is always a weak property? So how could I pass delegate between View Controllers or is there any other way to solve this problem?
You can use postNotification while dismissing the VC and add the observer on baseVC to do some operation.
You could use [self.navigationController dismissViewControllerAnimated:YES completion:nil]; in button action to dismiss the view controller.
Before this you need to post the notification [[NSNotificationCenter defaultCenter] postNotificationName:#"NotificaitonBaseVC" object:nil]; and add the observer in baseVC's viewDidLoad method as follows
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomeOperation:) name:#"NotificaitonBaseVC" object:nil];
I have a UINavigationGroup with a root view controller called MainViewController. Inside this MainViewController I'm calling another UINavigationController as a modal as following:
- (IBAction)didTapButton:(id)sender {
UINavigationController * someViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"someNavigationController"];
[self.navigationController presentViewController:someViewController animated:YES completion:nil];
}
Inside this someNavigationController, the user is going through some process so the nav controller is being pushed with some UIViewControllers. After the user completes the process, in the last UIViewController called finalStepViewController, I'm closing the modal as follow:
[self dismissViewControllerAnimated:YES completion:nil];
The modal is indeed dismissed and the user is back to the initial MainViewController.
However, I'd like to push another UIViewController to MainViewController's NavigationController (for example: a view saying that the user completed the process successfully). Preferably before the modal is dismissed.
I have tried the following things:
1. Using presentingViewController
UIViewController * successViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"successViewController"];
[self.presentingViewController.navigationController successViewController animated:YES];
Result: no error, but nothing happening either.
2. Delegate/protocol
Imported finalStepViewController.h inside MainViewController.h and appended <finalStepViewControllerDelegate>
Inside MainViewController.m added a method called parentMethodThatChildCanCall to be called from finalStepViewController.m
Added the following to finalStepViewController.h:
#protocol finalStepViewControllerDelegate <NSObject>
-(void)parentMethodThatChildCanCall;
#end
#property (assign) id <finalStepViewControllerDelegate> delegate;
and #synthesize delegate; in the model
Set the delegate property to someViewController in the above mentioned didTapButton IBAction to self. This showed a notice error saying: Assigning to id<UINavigationControllerDelegate>' from incompatible type UIViewController *const __strong'
Finally called [self.delegate parentMethodThatChildCanCall] just before closing the modal.
Result: except for the notice error, no fail but nothing happens as parentMethodThatChildCanCall is not called.
Any idea what I'm doing wrong/what I should be doing? It's my second week doing Objective-C, and most of the time I don't know what I'm doing so any help/code would be appreciated!
Thanks.
You can achieve this a lot easier using NSNotificationCenter.
In your MainViewController's -viewDidLoad add the following code
typeof(self) __weak wself = self;
[[NSNotificationCenter defaultCenter] addObserverForName:#"successfullActionName"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
SuccessViewController *viewController; // instantiate it properly
[wself.navigationController pushViewController:viewController animated:NO];
}];
Remove your controller from NSNotificationCenter upon dealloc
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
In FinalStepViewController on action that dismisses the view controller before dismiss post the notification
- (IBAction)buttonTapped:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"successfullActionName" object:nil];
[self dismissViewControllerAnimated:YES completion:nil];
}
This example is very crude and is not ideal, you should use constants for your notification names and in some cases store the observers returned by NSNotificationCenter to remove specific ones.
-- EDIT
I'd like to also mention that the method addObserverForName:object:queue:usingBlock: actually returns the observer as an id type object. You need to store a reference to it as an iVar in your class and remove it from the NSNotificationCenter when dealloc method is called otherwise that observer will never get deallocated.
I have a UITabBarController, which has 4 tabs. Each one of those tabs is a separate UIViewController. I have objects on each one of those 4 VC's that use NSNotification's to perform actions upon the press of a certain object. The 4 VC's all respond to the notification in the same way because it is a similar object on each page. When this object is pressed it presents a view onto the current view controller. The problem is that if I move to any of the other 3 tabs now that view is also on their VC. That is because the notification is being responded to on all 4 tabs when it is pressed on any of the VC's. I am needing it to only respond to the VC that the user is currently on and not any of the others that are in the tab bar.
Is there a way to get this to work properly? Maybe a threshold where you can set how many times the notification can perform its selector after being called? That way I could set it to 1 and at any given time if that notification is called the selector can only be called 1 time.
The type of object implementation that I'm using requires me to use NSNotification's so there is no way to change how I interact.
edit:
This viewDidLoad method is on the top level VC for the 4 VC's in my tab bar. All 4 of them either use this directly or inherit from it.
- (void) viewDidLoad
{
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didSelectItemFromCollectionView:) name:#"didSelectItemFromCollectionView" object:nil];
}
Action Handler:
- (void) didSelectItemFromCollectionView:(NSNotification *)notification
{
NSDictionary *cellData = [notification object];
if (cellData)
{
NewVC *pushToVC = [self.storyboard instantiateViewControllerWithIdentifier:#"PushToVC"];
[self.navigationController pushViewController:pushToVC animated:YES];
}
}
Each of the 4 VC's is a UITableViewController and have cells with an object that can be pressed. This NSNotificationCenter action is what allows the operation to work.
You must have implemented the NSNotificationCenter's -addObserver:selector:name:object: method in the -viewDidLoad of every viewController
Example:
- (void)viewDidLoad
{
//...
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"TestNotification"
object:nil];
}
Instead of having this in -viewDidLoad, move it within -viewWillAppear and implement removeObserver:name:object: in -viewWillDisappear.
This way, only the viewController that is currently on will respond to the notification.
Example:
- (void)viewWillAppear:(BOOL)animated
{
//...
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"TestNotification"
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
//...
[NSNotificationCenter defaultCenter] removeObserver:self
name:#"TestNotification"
object:nil];
}
- (void)doSomething:(NSNotification *)userInfo
{
//...
//if you push a viewController then the following is all you need
[self.navigationController pushViewController:vcSomething
animated:YES];
//however.... if you're instead presenting a viewController modally then
//you should implement "-removeObserver:name:object: in this method as well
//[NSNotificationCenter defaultCenter] removeObserver:self
// name:#"TestNotification"
// object:nil];
//[self presentViewController:vcSomething
// animated:YES
// completion:nil];
//OR... in the completion parameter as:
//[self presentViewController:vcSomething
// animated:YES
// completion:^{
// [NSNotificationCenter defaultCenter] removeObserver:self
// name:#"TestNotification"
// object:nil];
// }];
}
EDIT:
You (#Jonathan) commented:
I really appreciate your answer and it has helped me out a lot! I
actually ran into one more scenario where this issue occur's and I'm
not sure how to figure it out. Right now I have a VC that presents
another VC modally. Each one has observers for the same
NSNotification. Everything performs perfectly well when I'm in the
modally presented VC, but once I dismiss that VC and return to the
underlying one I have the same issue where the notification is being
called multiple times. Do you have an idea for a solution in this
case?
Now... regarding this...
FIRSTLY... NOTE:
Multiple -addObserver:selector:name:object: will register the specified notification multiple times (means... same notification being registered for N times will call invoke the target selector N times)
Presenting a ViewController (call it Child) from, say, Parent viewController will NOT invoke the -viewWillDisappear: of the Parent
where as...
Dismissing the Child viewController will still invoke -viewWillAppear: of the Parent
This creates an imbalance in the logic and if not handled (as per the commented lines in the code example of the doSomething method above), it results in the Parent registering for the notification multiple times (as it's -viewWillAppear: method is called more often than not -viewWillDisappear:)
also see: similar question
it should be called only once so that it never gets called again
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(methodName:) name:#"name" object:nil];
});
My question is that I have one main UIViewController that allows three other UIViewControllers to be presented through it, but I am wondering if there is a way that once I dismiss one of those other three controllers, can the main UIViewController be notified or tell that it is now appearing due to the dismissal of said controller?
Thank you in advanced!
If your main view controller implements:
(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
and the presented view controllers send it that message, you will know since at that time it can query to see what the "presentedViewController" was.
When you dismiss one of the three ViewControllers, you could signal to the main ViewController that they have been dismissed via a NSNotification:
NSDictionary *viewControllerInfo = #{#"ViewControllerClass" : NSStringFromClass([self class])}
[[NSNotificationCenter defaultCenter] postNotificationName:#"ViewControllerDismissed" object:nil userInfo:viewControllerInfo];
And in your main viewController:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(viewControllerDismissed:) name:#"ViewControllerDismissed" object:nil];
And respond with this method:
- (void)viewControllerDismissed:(NSNotification *)notification {
NSDictionary *viewControllerInfo = [notification userInfo];
// Dictionary should be same as the one passed through the noticiation.
}
Additional note: If you are using a UIStoryboard, then you can use an unwind segue.
EDIT: Updated Dictionary to use NSStringFromClass()
I have a viewController I've built in storyboard. I also have a NSObject Subclass which acts as my model, which sends and listens for API requests and responses. When a method fires in my model, I want to present a modal View of my viewController from whatever view happens to be visible at the time.
An example would be if my API hears "show this view" I want to show viewController regardless of what view is being shown.
Conceptually, how does one do this?
EDIT: I don't know which view controller will be showing when I want to present my modal viewController. Also, I need to pass params from my model to the modalVC when it's presented.
I would send a notification from the model telling "someone" that some view needs be displayed.
NSDictionary *userInfo = #{ #"TheViewKey": viewToDisplay];
[[NSNoticationCenter defaultCenter] postNotificationName:#"NotificationThatThisViewNeedsToBeDisplayed" object:self userInfo:userInfo];
And then on the delegate (or the active view controller) would register to this notification and handle the display.
// self is the delegate and/or the view controller that will receive the notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleViewToDisplay:) name:#"NotificationThatThisViewNeedsToBeDisplayed" object:nil];
If you put in the view controller remember to remove self from the observers when the view is not visible:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"NotificationThatThisViewNeedsToBeDisplayed"];
This way your model is decoupled from the presentation.
You have the current viewController (any viewController subclass) present the new view using:
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
EDIT: To find the top view controller, you ask the UITabBarController for the selectedViewController (if you use a tabBarController) to get the 'seed', or start with the window.rootViewController.
Once you are past any tabBarControllers, then you should only have UIViewController subclasses and UINavigationControllers. You can use a loop like this:
- (UIViewController *)frontmostController:(UIViewController *)seed
{
UIViewController *ret;
if([seed isKindOfClass:[UINavigationController class]]) {
ret = [(UINavigationController *)seed topViewController];
} else
if([seed isKindOfClass:[UIViewController class]]) {
ret = seed.presentedViewController;
}
return ret ? [self frontmostController:ret] : seed;
}