I have switches and I want to detect if any of switch was changed position, if changes was made I need to start my action.
Switches stores position in NSUserDefaults
- (IBAction)saveSwitch:(id)sender
{
NSUserDefaults *defs1 = [NSUserDefaults standardUserDefaults];
[defs1 setBool: blackSwitch.on forKey: #"blackKey"];
NSUserDefaults *defs2 = [NSUserDefaults standardUserDefaults];
[defs2 setBool: greenSwitch.on forKey: #"greenKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
You can post a notification whenever you call synchronize
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyAppSettingsChanged" object:self userInfo:nil];
Then in your other class listen to the notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onAppSettingsChanged:) name:#"MyAppSettingsChanged" object:nil];
-(void) onAppSettingsChanged:(NSNotification)notification
{
// settings changed
}
If you want, you can pass an NSDictionary into userInfo when calling postNotificationName that contains information like which settings have changed.
If you're using NSUserDefaults the easiest is to subscribe NSUserDefaultsDidChangeNotification. It is automatically sent when something changes.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(appSettingsDidChange:)
name:NSUserDefaultsDidChangeNotification
object:nil];
The best way to track changes to NSUserDefaults is to add an observer using KVO. This way you do not need to perform any custom notification code or track changes manually.
In the class that wants to be informed about the changes just register it as a listener to the specified keys:
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:#"blackKey" options:NSKeyValueObservingOptionNew context:nil];
[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:#"greenKey" options:NSKeyValueObservingOptionNew context:nil];
Then just respond to the notification:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (object == defaults) {
// Here you can grab the values or just respond to it with an action.
}
}
Now whenever one of those keys changes you will be notified automatically.
This is a super clean solution and allows for some heavy reuse. For example, if you add the NSKeyValueObservingOptionInitial key to the options parameter above (NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) then it will also notify your observer method with the initial value, allowing you to reuse that method even for initial states.
Swift Version
Setting up the defaults:
NSUserDefaults.standardUserDefaults().addObserver(self, forKeyPath: "blackKey", options: .New, context: nil)
NSUserDefaults.standardUserDefaults().addObserver(self, forKeyPath: "greenKey", options: .New, context: nil)
The observer:
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if object is NSUserDefaults {
// Here you can grab the values or just respond to it with an action.
}
}
You cannot detect changes in NSUserDefaults. Instead, track when the switch itself is changed, and handle that event. Example:
[blackSwitch addTarget:self
action:#selector(blackSwitchChanged:)
forControlEvents:UIControlEventValueChanged];
Handle the switch position changing:
- (IBAction)blackSwitchChanged:(id)sender {
NSLog(#"Black switch changed");
..
// check if blackSwitch is on or off.
}
Related
Trying to get one ViewController to communicate with another through standard Cocoa Notifications.
Wrote a simple test case. In my initial VC I add the following to viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector: #selector(mesageReceived:)
name:#"test.channel.pw" object:nil];
I add the following method:
- (void)mesageReceived:(NSNotification *)notif
{
NSString *text = (NSString *)notif;
}
I then segue to a VC which has the following added to its viewDidLoad:
NSString *test = #"someText";
[[NSNotificationCenter defaultCenter]
postNotificationName:#"test.channel.pw" object:test];
When I run it, the notification works - the messageReceived method is called - but the value of text is "test.channel.pw", not "someText" as I expected.
What is going on here, and what is the correct syntax for passing a reference to a class instance using notifications?
You need to pass your data using userInfo dictionary.
Change the notification passing code like:
[[NSNotificationCenter defaultCenter] postNotificationName:#"test.channel.pw" object:nil userInfo:[NSDictionary dictionaryWithObject:#"someText" forKey:#"dataKey"]];
And change the receiver method like:
- (void)mesageReceived:(NSNotification *)notif
{
NSString *text = [[notif userInfo] objectForKey:#"dataKey"];
}
Try NSString *text = (NSString *)[notif object];.
By this
-(void)someMethod{
NSDictionary *postDict = #{#"Key":[NSNumber numberWithFloat:17.0]};
[[NSNotificationCenter defaultCenter]postNotificationName:#"myNotification" object:postDict];
}
Some other class
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter]postNotificationName:#"myNotification" object:nil userInfo:postDict];
}
-(void)valueForNotification:(NSNotification *)notification{
NSDictionary *dict = [notification userInfo];
NSLog(#"%#",[dict objectForKey:#"Key"]); //prints out 17.0
}
You need to pass only NSDictionary object in NSnotificationCenter. Through NSDictionary you can pass any data and then you need to parse once received.
I have a ServiceFacade class with class methods for communicating with backend services.
On that ServiceFacade class I have a static method that returns NSMutableDictionary in which I keep current ServiceFacade's downloading operations.
I want to observer changes on this NSMutableDictionary either in AppDelegate or any other place. The app delegate seems not to respond to
- (void)addObserver:(NSObject *)anObserver
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context{
}
Method for returning NSMutableDictionary:
+(NSMutableDictionary *)downloadOperations
{
if (_downloadOperations)
{
return _downloadOperations;
}
[_downloadOperations addObserver:[AppDelegate sharedAppDelegate] forKeyPath:#"downloadOperationsDict" options:0 context:NULL];
_downloadOperations = [NSMutableDictionary dictionary];
return _downloadOperations;
}
Any idea ?
There is no way to observe NSMutableDictionary changes. But there are 2 workarounds
1) Subclass NSMutableDictionary and trigger Notifications on setObject:forKey and removeObjectForKey.
2) Wrap your _downloadOperations write/remove operations and trigger notifications there.
I suggest you to use 2) variant, as subclassing NSMutableDictionary is not so easy.
So 2) variant will be like this.
Add these 2 methods to class ServiceFacade
- (void)setDownloadObject:(id)aObj forKey:(id<NSCopying>)aKey
{
[self.downloadOperations setObject:aObj forKey:aKey];
[[NSNotificationCenter defaultCenter] postNotificationName:#"DownloadOperationsChanged" object:self
userInfo:self.downloadOperations];
}
- (id)removeDownloadObjectForKey:(id<NSCopying>)aKey
{
[[self.downloadOperations] removeObjectForKey:aKey];
[[NSNotificationCenter defaultCenter] postNotificationName:#"DownloadOperationsChanged" object:self
userInfo:self.downloadOperations];
}
After this, you need to add and remove objects from that dictionary via this 2 methods. And you can also subscribe for changes
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(downloadOperationsChanged:)
name:#"DownloadOperationsChanged"
object:nil];
return YES;
}
- (void)downloadOperationsChanged:(NSNotification *)aNotification
{
NSLog(#"Operations : %#", aNotification);
}
My app has a setting that allows me to turn off ui sounds, or turn it on. I use this for the value:
+ (void)setUISoundsEnabled:(BOOL)UISoundsEnabled
{
__UISoundsEnabled = UISoundsEnabled;
}
here is a notification in my view controller that listens for preference changes and then updates the sound settings.
- (void)preferencesDidChange:(NSNotification *)note
{
NSMutableArray *changedPreferences = note.object;
if ([changedPreferences containsObject:#"localPlayUISounds"]) {
[FHSSound setUISoundsEnabled:PREFS.localPlayUISounds];
}
2 questions:
First question: How would go about getting the settings saved in PREFS to match the settings saved in the BOOL at the top.
Second question:
How would I implement NSUserDefaults to save and load this data. To be specific where exactly am I implementing NSUserDefaults to save, and load this data. I'm not familiar with NSUserDefaults so examples would be very helpful
Please let me know if you need any more code or have any more questions
It's very simple. You use NSUserDefaults as-is; there's no need to subclass.
// read the setting when you start the app
flag = [[NSUserDefaults standardUserDefaults] boolForKey:"someKey"]
// set the setting if user can change it inside your app
[[NSUserDefaults standardUserDefaults] setBool:flag forKey:"someKey"]
It's probably best to create a settings plist file so that these settings can be then changed in the Settings app. If you do that, you should also listen to NSUserDefaultsDidChangeNotification in case user changes the settings in the Settings app while yours is in the background.
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(defaultsChanged:)
name:NSUserDefaultsDidChangeNotification object:nil];
...
- (void)defaultsChanged:(id)sender {
[[NSUserDefaults standardUserDefaults] synchronize];
// and notify your observers as necessary based on new settings...
}
Apps with advanced settings also often contain a pre-populated plist with "initial" default values, because your standardUserDefaults will contain no values until the user enters something in Settings app:
NSURL *defaultSettingsURL = [[NSBundle mainBundle] URLForResource:#"DefaultSettings" withExtension:#"plist"];
self.bundledDefaults = [NSDictionary dictionaryWithContentsOfURL:defaultSettingsURL];
You can dump bundledDefaults into settings on first run or simply use them as a back-up any time you read from the user defaults.
Here is how I solved it without using NSUserDefaults.
- (void)viewDidLoad
{
[super viewDidLoad];
[self setSoundPreferences];
}
The notification is set to listen for the change in prefs
- (void)preferencesDidChange:(NSNotification *)note
{
NSMutableArray *changedPreferences = note.object;
if ([changedPreferences containsObject:#"localPlayUISounds"]) {
[FHSSound setUISoundsEnabled:PREFS.localPlayUISounds];
}
else if ([changedPreferences containsObject:#"localPlayAlertSounds"]) {
[FHSSound setAlertSoundsEnabled:PREFS.localPlayAlertSounds];
}
and lastly setting the preferences at launch
#pragma mark (launch)
- (void)setSoundPreferences
{
[FHSSound setUISoundsEnabled:PREFS.localPlayUISounds];
[FHSSound setAlertSoundsEnabled:PREFS.localPlayAlertSounds];
}
I've encountered a weird bug and would like to check if I'm using my Key value observing of changes to NSUserDefaults correctly.
I have used this code in two places in my app without issues, then I added 3rd controller that observes values for "goldCount" and "energyCount". Now when I set the initial value, the app crashes with exc_bad_access. I'm adding this controller to the view 2 seconds after it's parent view appears using performSelectorAfterDelay.
Just before displaying the game screen, I set these properties:
//crash on this line
[[NSUserDefaults standardUserDefaults] setInteger:200 forKey: goldCount];
[[NSUserDefaults standardUserDefaults] setInteger:150 forKey: energyCount];
Within 3 different view controllers, I have this code in viewDidLoad:
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults addObserver:self
forKeyPath:#"goldCount"
options:NSKeyValueObservingOptionNew
context:NULL];
[defaults addObserver:self
forKeyPath:#"energyCount"
options:NSKeyValueObservingOptionNew
context:NULL];
self.goldLabel.text = [NSString stringWithFormat:#"%i",[[GameDataManager sharedInstance] currentGoldCount]];
self.energyLabel.text = [NSString stringWithFormat:#"%i",[[GameDataManager sharedInstance] currentEnergyCount]];
Here's how the class updates it's labels:
// KVO handler
-(void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)anObject
change:(NSDictionary *)aChange context:(void *)aContext
{
//aKeyPath gives us the name of a user default that has changed
if([aKeyPath isEqualToString:#"goldCount"])
{
//we are interested in the new value
self.goldLabel.text = [NSString stringWithFormat:#"%i",[[aChange objectForKey:#"new"] intValue]];
}else if([aKeyPath isEqualToString:#"energyCount"])
{
self.energyLabel.text = [NSString stringWithFormat:#"%i",[[aChange objectForKey:#"new"] intValue]];
}
}
After adding a call to [[NSUserDefaults standardUserDefaults] synchronize]; I get this exception the second time around:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '(
): An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: goldCount
Observed object:
Change: {
kind = 1;
new = 205;
}
Context: 0x0'
NSUserDefaults is not documented to be KVO compliant so it's not possible to observe defaults by their key. This might be the reason for the crash but without a stack trace it's not possible to tell.
There is a notification you can register for that announces changes to the defaults system: NSUserDefaultsDidChangeNotification.
I have a table view controller that is supposed to be populated with data from an array that is encapsulated within a store class. The table needs to know how many rows are in each section via the method table:numberOfRowsInSection:. In this method, I need to return the size of the array that is inside my instance of store. I initially did this by making store a singleton, but was told this was inefficient and that using NSNotificationCenter would be better.
As far as I know, all NSNotificationCenter does is trigger methods in certain objects when another object posts a specific notification. How can I use NSNotificationCenter to send the size of the array to my table view controller?
You can do it like this:
...
// Send
[[NSNotificationCenter defaultCenter] postNotificationName: SizeOfRrrayNotification
object: [NSNumber numberWithInteger: [array count]]];
...
// Subscribe
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(sizeOfArray:)
name: SizeOfRrrayNotification
object: nil];
// Get size
- (void) sizeOfArray: (NSNotification*) notification
{
NSNumber* sizeOfArray = (NSNumber*) notification.object;
NSLog(#"size of array=%i", [sizeOfArray integerValue]);
}
Post the Notification :
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyArraySize" object: [NSNumber numberWithInteger: [myArray count]]] userInfo:nil];
Get the Notification :
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(getSizeOfArray:) name:#"MyArraySize" object:nil];
Add this method in the viewController where you are getting notification:
- (void) getSizeOfArray: (NSNotification*) notification
{
NSNumber* myArraySize = (NSNumber*) notification.object;
}
you can even send more data through "userInfo" and get that data in selector method using notification.userInfo, but remember its type is "NSDictionary"
Hope this will help you.
The method in which you are calculating the size of the array:
<------Notification sending side---------->
-(void)sizeOfArray{
int size = [myArray count];
NSMutableString *myString = [NSMutable string];
NSString *str = [NSString stringwithFormat:#"%d",size];
[myString apprndString:str];
//It is to be noted that NSNotification always uses NSobject hence the creation of mystring,instead of just passing size
[[NSNotificationCenter defaultCenter] postNotificationName:#"GetSizeOfArray" object:myString];
}
Now once you have posted a notification,add this to the viewDidLoad method of the controller where you are sending the data
<------Notification receiving side---------->
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(get_notified:) name:#"GetSizeOfArray" object:nil];
//The selector method should always have a notification object as its argument
[super viewDidLoad];
}
- (void)get_notified:(NSNotification *)notif {
//This method has notification object in its argument
if([[notif name] isEqualToString:#"GetSizeOfArray"]){
NSString *temp = [notif object];
int size = [temp int value];
//Data is passed.
[self.tableView reloadData]; //If its necessary
}
}
Hope this helps.