I am trying to create Singleton that it would be initialised from AppDelegate. The purpose is to monitor all the UIViewControllers (the active one) and print on console the kind of class (as proof of concept). So my basic idea is to initialise the singleton in AppDelegate and pass as parameter a reference of AppDelegate. Then somehow I must monitor which one is the active view.
For example: View A B C
A is the first view in Navigation Controller. My Singleton knows that the current view is A. Then we push view B and Singleton is notified that view B is now the current view. Same with C. Now we pop C and Singleton knows that the current view is B.
Is any kind KVO or NSNotification for notifying my singleton that a new UIView is appeard/removed? Any alternatives for this problem?
After registering for all notification I found out about UINavigationControllerDidShowViewControllerNotification.
With this observer:
[notifyCenter addObserver:self selector:#selector(viewAppeared:) name:#"UINavigationControllerDidShowViewControllerNotification" object:nil]; I am able to monitor the activity of the UINavigationController.
You can get current View controller by just making a view controller object in appdelegate like
#property (strong, nonatomic) UIViewController *currentViewController;
and then on the view will Appear of your current view controller give the current reference to the app delegate object like
AppDelegate *myAppd = (AppDelegate*)[[UIApplication sharedApplication]delegate];
myAppd.currentViewController = self;
This way you get your current active view.
One approach is to pick a particular method you want to know about and intercept it.
Here, I create a category on UIViewController and provide a method I want called whenever the controller's viewWillAppear: would normally be called:
#include <objc/runtime.h>
#implementation UIViewController (Swap)
+ (void)load
{
NSLog(#"Exchange implementations");
method_exchangeImplementations(
class_getInstanceMethod(self, #selector(viewWillAppear:)),
class_getInstanceMethod(self, #selector(customViewWillAppear:)));
}
- (void)customViewWillAppear:(BOOL)animated
{
// Call the original method, using its new name
[self customViewWillAppear:animated];
NSLog(#"Firing %# for %#", VIEW_CONTROLLER_APPEARED, self);
[[NSNotificationCenter defaultCenter] postNotificationName:VIEW_CONTROLLER_APPEARED
object:self];
}
#end
After that, it's just a case of listening for the notification in whatever object needs to know (e.g. your Singleton).
Related
This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 7 years ago.
I am struggling on calling method from viewController B to viewController A. Need to call viewController B close button click to Dismissviewcontroller then immediately need to call one method and want to pass two string values on viewController A. Its like reverse process.
FYI : I am using Storyboard and present viewController for B. A is the mainviewcontroller.
use viewWillAppear in controller A.
Post a notification from controller B and add
observer on controller A.
Post notification on controller B close button
[[NSNotificationCenter defaultCenter] postNotificationName:"NAME" object:nil userInfo:nil];
Add observer on controller A:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(perform:) name:#"NAME" object:nil];
Implement delegates on
controller B and implement it on controller A so once you click
on close button controller B just call the delegate and perform what ever you want.
Implement KVO
well the easiest way but not the most efficient is to make a global object of the viewController A and viewController A view did load method
call that global variable and make it equal to self and
in then the dismiss from viewController B
will use
[self dismissViewControllerAnimated:YES completion:^{
// here you can create a code for calling the global **viewController A** object to call the function you need
}];
conclusion :
in viewController A header file :
extern A* AGlobalInstance;
and in A.m file just below the #import "A.h"
A* AGlobalInstance;
and in the viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
AGlobalInstance = self;
}
then in B.m button just use
[self dismissViewControllerAnimated:YES completion:^{
[AGlobalInstance function];
}];
but you must go to A viewController first before going to B to make it work
As the above answers suggested you to use Delegates and Notification so I am not gonna suggest you those. Apart from them I would like to ask you to go for Block.
typedef void(^valueHandler)(id anyObject1, id anyObject2);
#viewController A
B *instanceOfB;
[instanceOfB setValueHandlerBlock^(id anyObject1, id anyObject2) {
// Here you can receive the values.
}];
#viewController B
#property (nonatomic, copy) valueHandler valueHandlerBlock;
//To pass the value
if (valueHandlerBlock) {
valueHandlerBlock(#"a String value", anArray);
// When ever the above line will execute it will pass the values to view controller A.
}
There are many option for passing values
Use protocol and delegates
Use unwind segues
Use Notifications
there are many other option to do the same google on above points you will get tons of demos for this.
I am very new to iOS and overwhelmed with resources I find online. My use case is simple
a.) ViewController parent has label called categoryLabel. A tap on label category opens a new view View 1
b.) View 1, shows all groups. Lets says A, B, C. This will be shown on table
c.) when user click on any group A, B or C, a new view View 2 appears with all categories in that group. For example, user clicks on A and on View 2 user sees categories A1, A2, A3.
d.) Now when user clicks on any specific category, how does that goes back to ViewController parent and assigns to categoryLabel?
I do not know what is the best way to approach this design.
Any guidance is very much appreciated
hope this will help
let take an example , your are going from A -> B and want send some data from B to A , there are many technique to do that but using delegate method and block are nicer way.
delegate way :-
in your B.h file
#protocol yourDelegate <NSObject>
-(void)whichCategoryClicked:(NSString *)categoryName;
#end
#interface B : UIView
#property(nonatomic, assign)id<yourDelegate> delegate;
in your B.m
just call this delegate method after Clicking particular category.
[self.delegate whichCategoryClicked :#"Category_name"];
in your A.h
assign it as delegate and import the above class
#interface A.h : UIViewController<yourDelegate>
and in Implement this method in A.m
first in your viewdidload
{
B *objB = [[B alloc]init];
objB.delegate = self;
}
-(void)whichCategoryClicked:(NSString *)categoryName
{
categoryLabel.text = categoryName;
}
You can use Local notification for this purpose names as NSNotificationCenter in iOS. Which works as follows:
To send a notification that is from the view on which you are and want to send some value from that view, use below code:
NSDictionary *dict;
[[NSNotificationCenter defaultCenter] postNotificationName:#"NotificationKey" object:nil userInfo:dict];
and now on any of the view controller, you can add observer on viewDidLoad of that class as:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodToCall:)
name:#"NotificationKey"
object:nil];
Now call method written in above line:
- (void)updateImageFromArray:(NSNotification *)notification {
// your dict
NSDictionary *dictUserInfo = [notification userInfo];
}
Trying to get a better understand of iOS delegation. I'm using UIImagePickerController as a reference but what's a good code example to use a delegate to dismiss my view controller?
I have a TabBarViewController that calls AViewController and want to use delegation to dismiss AViewController.
In this case, the idea is that the class that presented the new viewcontroller, should also be the one that dismisses it. The AViewController may have no clue how it was presented, so it wants to let the presenter, the TabBarViewController, handle the dismissal in whatever form needed. So, we need to define a protocol, say AViewControllerProtocol, which allows there to be a standard definition of the dismissal call:
This goes in AViewControllerProtocol.h:
#protocol AViewControllerProtocol <NSObject>
#required
- (void) dismissWithDone:(UIViewController *)viewController;
- (void) dismissWithCancel:(UIViewController *) viewController;
#optional
- (void)dismissWithDone:(UIViewController *)viewController andPlaySoundFile:(NSString *)soundPath;
#end
This goes in a file called AViewControllerProtocol.h. Both TabBarViewController.m and AViewController.m will import it. Think of this as a pact that any user of AViewController must agree to before it can utilize it. Similarly, you can't use UITableView without making a pact to observe the UITableViewDelegate and UITableViewDataSource protocols (those are two separate protocols).
Any class which wants to present AViewController, can see from the protocol definition that AViewController expects two required methods in order to be properly dismissed. It also has an optional third method that requests the presenter to play a sound after dismissing.
In order for AViewController to do its piece of this pact, it needs to get and store one piece of information about who the presenter is. That is called the delegate. The delegate is a property defined in the #interface of AViewController, in AViewController.h:
This goes in AViewController.h:
#property (nonatomic, weak) id<AViewControllerProtocol> delegate;
Now, the presenter, TabBarViewController, needs to do its bit. It needs to define the two required methods, plus maybe the optimal one, and it also needs to set the delegate value:
In TabBarViewController.m, in the #implementation:
This goes in TabBarViewController.m:
- (void) dismissWithDone:(UIViewController *)viewController
{
[self saveData:viewController.dataToSave]; // this could be the results that need to be saved
[viewController dismissViewControllerAnimated:YES completion:^{
;
}];
}
- (void) dismissWithCancel:(UIViewController *)viewController
{
// don't save data
[viewController dismissViewControllerAnimated:YES completion:^{
;
}];
}
The delegate value is set where AViewController is first created and/or before it is presented:
This also goes in TabBarViewController.m:
AViewController * aVC = [AViewController.alloc init];
aVC.delegate = self;
aVC.data = ...; // this may be the data you want changed by the VC
[self presentViewController:aVC animated:YES completion:^{
}];
Setting the delegate here is the only connection that the AViewController class has with its presenting viewController - which is the whole point here: child classes really shouldn't have to know a whole lot about the classes that utilize them.
Lastly, the AViewController class needs to add the following in order to call, via the delegate, back to the presenting class - so in AViewController.m:
This goes in AViewController.m:
-(IBAction)userHitButton:(id)sender
{
if (sender == doneButton) {
if ([_delegate respondsToSelector:#selector(dismissWithDone:)]) {
[_delegate dismissWithDone:self];
}
} else {
if ([_delegate respondsToSelector:#selector(dismissWithCancel:)]) {
[_delegate dismissWithCancel:self];
}
}
}
If you are wondering why the calling class has to:
aVC.delegate = self;
It is because there are situations where the class that actually creates a child class, isn't the one that will handle the delegate calls. In that case, instead of self, you put an instance of a class that will handle the delegate callbacks. For example, lets say that you have AViewController, BViewController and CViewController. They all get data from the user that needs to be saved. A class by the imaginary name of ABCDataHandler could be the one that can handle the dismissWithDone: and dismissWithCancel: callbacks and saves the data as needed, and TabBarViewController can stay out of any data handling activity.
That's to put it as simply as I can. I hope I haven't made any typos here :-)
Hi I am working on a iPad app and got a requirement to dismiss all popovers (if any) when app goes in background.
I did some study online and didn't find a simple way to do it. I'd like to share some my idea here and see if there are a better way to do it.
1, Dismiss popovers in didEnterBakcground in delegate. Seems not practical since we have to add all popovers reference in.
2, Go through all views recursively in current window to find popover view by (class = _UIPopoverView). It is seems a bit hacky and dangerous.
3, Set up UIApplicationDidEnterBackgroundNotificationgroundNotification in each object who own popovers and dismiss them. This seems reasonable, but really troublesome if there are hundreds of popovers in your app.
4, How about add a category method say -(void)dismissWhenAppWillEnterBackground; and register notification.
Or there is easier way to do it?
Here is a drop-in category on UIPopoverController that does what you're asking.
Basically the category swizzles initWithContentViewController: so that it can track live UIPopoverController instances in a NSHashTable (which doesn't itself hold the contained UIPopoverControllers alive since it keeps weak references to them.) It also monitors for UIApplicationDidEnterBackgroundNotification, and when this arrives it iterates the collection of live UIPopoverControllers and dismisses any that are showing.
It might be nice to extend this to implement the "never allow two popovers to show at once" rule that Apple has.
I'm not a huge fan of method swizzling in production apps but this seems pretty safe.
No special instructions for use. Just include the category in your project and use your UIPopoverControllers normally.
#import <objc/runtime.h>
#interface UIPopoverController (autodismiss)
#end
#implementation UIPopoverController (autodismiss)
static NSHashTable* ts_popoverHashTable;
+ (void) load
{
SEL originalSelector = #selector(initWithContentViewController:);
SEL replacementSelector = #selector(ts_initWithContentViewController:);
Method originalMethod = class_getInstanceMethod( [UIPopoverController class], originalSelector);
Method replacementMethod = class_getInstanceMethod( [UIPopoverController class], replacementSelector);
method_exchangeImplementations(originalMethod, replacementMethod);
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector( applicationDidEnterBackgroundNotification: )
name: UIApplicationDidEnterBackgroundNotification
object: nil];
}
- (id) ts_initWithContentViewController: (UIViewController*) contentViewController
{
UIPopoverController* pc = [self ts_initWithContentViewController: contentViewController];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ts_popoverHashTable = [NSHashTable weakObjectsHashTable];
});
[ts_popoverHashTable addObject: pc];
return pc;
}
+ (void) applicationDidEnterBackgroundNotification: (NSNotification*) n
{
for ( UIPopoverController* pc in ts_popoverHashTable )
{
if ( pc.isPopoverVisible )
{
[pc dismissPopoverAnimated: NO];
}
}
}
#end
I may have a better answer, which is add a category method -(void)dismissWhenAppWillEnterBackground to UIPopoverController and register UIApplicationWillEnterBackgroundNotificationgroundNotification.
Write a protocol with a couple of optional methods:
- (void)appWillEnterBackground;
- (void)appWillBecomeActive;
Make your view controllers to implement it and then in your app delegate, access your root view controller, check if it responds to those methods and invoke them when the app is going to background and becoming active.
You should be able to obtain the root view controller easily. If you have a hierarchy of view controllers you may need to forward the call.
Add your popover dismissal code in appWillEnterBackground, for instance.
Create a uiviewcontroller base class for all the view controllers in the application.
Add an array which contains the references to the popover views in the particular viewcontroller
Maintain a reference to the current viewcontroller iin app delegate .
When app enters in background, get the current viewcontroller and travers the popover array and dismiss all the popovers.
My goal is to notify a UITableView to refresh itself every time some configurations have changed. The problem is that the configuration view is "not" on the same view that produces the signal. (Yes, I used Tabbed Application.)
Currently I use a sort of global variable in AppDelegate for detecting the change in one view, and do the check in another view. This is fine but the code is not readable as it is so tightly coupling. Is there an elegant method for doing this? Do I miss something in this programming framework?
If there were such an elegant way, I suppose the refreshing process of UITableView should happen as soon as the notification occurs. In this case, I would like to know whether it's possible to delay UITableView from refreshing itself until viewDidAppear occurs.
I would use KVO (Key Value Observing) to keep track of when it changes:
- (void)viewDidLoad {
[super viewDidLoad];
// Note that you can use the options to get the new value passed when it
// changes if you want to update immediately.
[configurationObject addObserver:self forKeyPath:#"configurationItem" options:0 context:nil];
}
- (void)viewDidUnload {
[super viewDidUnload];
[configurationObject removeObserver:self forKeyPath:#"configurationItem"];
}
// Note that I would refresh in viewWillAppear instead of viewDidAppear
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.needToRefreshData == YES) {
[self.tableView refreshData];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (keyPath isEqualToString:#"configurationItem") {
[self.needToRefreshData = YES];
}
}
Use Delegation Design Pattern to pass data from one View Controller to the Other.
For example, let's say one Tab shows a list of cars in a UITableViewController and you have another view that let's a user add a new car to the list. You can let the UITableViewController
Adopt AddCarViewController's protocol
Set itself as a Delegate for AddCarViewController's protocol
Implement its protocol method
Execute the protocol method when informed
You can then let the AddCarViewController
Create a Protocol
Declare object reference Delegate with getter and setter methods
Define a method under that protocol
Inform the Delegate when the Save action is performed
Take a look at the following sample code for your UITableViewController
#interface ViewController : UITableViewController <AddCarViewControllerDelegate>
:
:
// The addCar: method is invoked when the user taps the Add button created at run time.
- (void)addCar:(id)sender
{
// Perform the segue named ShowAddCar
[self performSegueWithIdentifier:#"ShowAddCar" sender:self];
}
:
:
// This method is called by the system whenever you invoke the method performSegueWithIdentifier:sender:
// You never call this method. It is invoked by the system.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSString *segueIdentifier = [segue identifier];
if ([segueIdentifier isEqualToString:#"ShowAddCar"]) {
// Obtain the object reference of the destination view controller
AddCarViewController *addCarViewController = [segue destinationViewController];
// Under the Delegation Design Pattern, set the addCarViewController's delegate to be self
addCarViewController.delegate = self;
// Instantiate a Save button to invoke the save: method when tapped
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:addCarViewController action:#selector(save:)];
// Set up the Save custom button on the right of the navigation bar
addCarViewController.navigationItem.rightBarButtonItem = saveButton;
}
}
:
:
- (void)addCarViewController:(AddCarViewController *)controller didFinishWithSave: (BOOL)save {
:
:
}
Sample code for the AddCarViewController is here
#protocol AddCarViewControllerDelegate;
#interface AddCarViewController : UIViewController
#property (nonatomic, strong) IBOutlet UITextField *carMake;
#property (nonatomic, strong) IBOutlet UITextField *CarName;
#property (nonatomic, assign) id <AddCarViewControllerDelegate> delegate;
// The keyboardDone: method is invoked when the user taps Done on the keyboard
- (IBAction)keyboardDone:(id)sender;
// The save: method is invoked when the user taps the Save button created at run time.
- (void)save:(id)sender;
#end
/*
The Protocol must be specified after the Interface specification is ended.
Guidelines:
- Create a protocol name as ClassNameDelegate as we did above.
- Create a protocol method name starting with the name of the class defining the protocol.
- Make the first method parameter to be the object reference of the caller as we did below.
*/
#protocol AddCarViewControllerDelegate
- (void)addCarViewController:(AddCarViewController *)controller didFinishWithSave:(BOOL)save;
#end
Well, one approach would be to have some common class (singleton perhaps which app delegate kind of is) that keeps track of your model, when the settings viewController detects a change it can mark the model as changed, then when the view in question comes in to view, ie, viewDidAppear gets called, it can query the model to see if the changed flag has been set, if it has then you know to reload the table view, otherwise you dont...
Another way could be to use notification center for it, if your view is loaded it can sign up for the notifications of the model change, in which at point it sets a flag that it needs to reload the table view next time it comes on screen..
hope this helps
You could store the configuration in core data and use an NSFetchedResultsController with the dependant view controller set as a delegate. This way your view controller will get a callback whenever the data is changed.
Apple has some boilerplate code to handle the updates as well