How to detect if VoiceOver is on in MacOS? - objective-c

I am working on a MacOS app, and I cannot figure out how to listen to accessibility status changes (for example, when VoiceOver is turned on or off).
In iOS, there is a notification I can listen to, UIAccessibilityVoiceOverStatusDidChangeNotification.
Is there an equivalent in MacOS?

It turns out there is a hidden api to listen to the accessibility notification. You can listen to #"NSApplicationDidChangeAccessibilityEnhancedUserInterfaceNotification"
[center addObserver:self
selector:#selector(onAccessibilityStatusChanged:)
name:#"NSApplicationDidChangeAccessibilityEnhancedUserInterfaceNotification"
object:nil];
and then in the method, you can check voiceOverEnabled which is introduced in 10.13
if(#available(macOS 10.13, *)) {
NSWorkspace * ws = [NSWorkspace sharedWorkspace];
NSLog(#"got notification voiceover enabled ? %d",ws.voiceOverEnabled);
}

On macOS, the correct way to do this is via KVO:
[[NSWorkspace sharedWorkspace] addObserver:self forKeyPath:#"voiceOverEnabled" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
with the accompanying handler method:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:#"voiceOverEnabled"]) {
// handle the event in your way.
//
} else {
// Any unrecognized context must belong to super
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
On iOS and tvOS, use:
[[NSNotificationCenter defaultCenter] addObserverForName:UIAccessibilityVoiceOverStatusDidChangeNotification
object:nil
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
// handle the event in your way.
}];

Related

How to receive 2 notifications via NSNotificationCenter

I have three methods:
- (void)viewDidAppear:(BOOL)animated
{
[self updateViews];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:#"itemQuantityChanged" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotification:) name:[NSString stringWithFormat:#"Item %# deleted", itemUUID] object:nil];
}
- (void)viewDidDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void) receiveNotification: (NSNotification *)notification
{
if ([[notification name] isEqualToString:#"itemQuantityChanged"])
[self updateViews];
if ([[notification name] isEqualToString:[NSString stringWithFormat:#"Item %# deleted", itemUUID]])
NSLog(#"FAIL!");
}
The main idea is that for this class I need to receive 2 different notifications and in case of receiving them need to perform different actions. Is everything ok with the realization? I believe that it is possible to simplify this code. How to removeObserver correctly? I don't use ARC.
You should use a different selector for each notification. That way, you don't need any logic in the method to determine which notification was sent.
Removing the observers as you are doing is fine.

AVPlayer Skips the Beginning of a Video

I'm having an issue with AVPlayer skipping the first 0.5 seconds of a video I'm playing when I attempt to play it immediately after pre-rolling.
First, I create the player:
self.videoPlayer = [[AVPlayer alloc] init];
Then, I add the player item to the player and add an observer to check and see when the item is ready:
[self.videoPlayer addObserver:self forKeyPath:#"currentItem" options:0 context:kBLCurrentPlayerItemStatusContext];
[self.videoPlayer replaceCurrentItemWithPlayerItem:self.currentVideoPlayerItem];
When the player item is observed, I will check to see if the player is ready to play. If it's not ready to play, I will add an observer to make sure it's ready to play:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
...
} else if (context == kBLCurrentPlayerItemStatusContext) {
[self.videoPlayer removeObserver:self forKeyPath:#"currentItem" context:&kBLCurrentPlayerItemStatusContext];
if ([self.videoPlayer status]==AVPlayerStatusReadyToPlay) {
[self prerollVideoPlayer];
} else {
[self.videoPlayer addObserver:self forKeyPath:#"status" options:0 context:&kBLVideoPlayerStatusContext];
}
}
...
When the player status is observed as ready to play, I then preroll the player:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
...
if (context == kBLVideoPlayerStatusContext) {
if ([self.videoPlayer status] == AVPlayerStatusReadyToPlay) {
[self.videoPlayer removeObserver:self forKeyPath:#"status" context:&kBLVideoPlayerStatusContext];
[self prerollVideoPlayer];
}
}
...
After the pre-roll is complete, I play the player:
-(void)prerollVideoPlayer{
[self.videoPlayer prerollAtRate:1.0 completionHandler:^(BOOL finished){
if (finished) {
[self.videoPlayer play];
}
}];
}
When the player plays in this way, it skips the first ~0.5 seconds of the video. If I don't have the player play immediately when it's finished pre-rolling, and I call the play method later it plays fine. I've checked to make sure all the observers are correctly observed and they occur in the order I've noted above. Is there another observer I need to add or a notification I should check for to avoid this skipping behavior?

NSNotificationCenter is not sending out notifications

I am developing a system to keep track of achievements and for that I use NSNotificationCenter. When an achievement is unlocked in a object in the app a notification is sent to JMAchievementHandler which sets a string to YES and saves the progress in NSUserDefaults. My problem is that the notifications are not working probably. Here is my code in JMAchievementHandler:
- (id)init {
self = [super init];
if (self) {
//Set strings to NO
achievementOne = #"NO";
achievementTwo = #"NO";
achievementThree = #"NO";
achievementFour = #"NO";
//Add observers
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName:) name:#"achievement1" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName) name:#"achievement2" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName) name:#"achievement3" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationWithName) name:#"achievement4" object:nil];
//Add observer to observe delegate methods
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationFromDelegate:) name:#"notificationLaunch" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receiveNotificationFromDelegate:) name:#"notificationEnterBackground" object:nil];
}
return self;
}
- (void)receiveNotificationWithName:(NSNotification *)notification {
if ([[notification name] isEqualToString:#"achievement1"] || [achievementOne isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementOne = #"YES";
}
else if ([[notification name] isEqualToString:#"achievement2"] || [achievementTwo isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementTwo = #"YES";
}
else if ([[notification name] isEqualToString:#"achievement3"] || [achievementThree isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementThree = #"YES";
}
else if ([[notification name] isEqualToString:#"achievement4"] || [achievementFour isEqualToString:#"NO"]) {
//unlock achievement
NSLog(#"%# is unlocked", [notification name]);
achievementFour = #"YES";
}
}
- (void)receiveNotificationFromDelegate:(NSNotification *)notificationDelegate
{
if ([[notificationDelegate name] isEqualToString:#"notificationLaunch"]) {
[self loadDataOnLaunch];
}
else if ([[notificationDelegate name] isEqualToString:#"notificationEnterBackground"]) {
[self saveDataOnExit];
}
}
- (void)loadDataOnLaunch
{
NSLog(#"loadDataOnLaunch");
}
- (void)saveDataOnExit
{
NSLog(#"saveDataOnExit");
}
When I try to post a notification the NSLogs are not called. I use the following code to send notifications from my AppDelegate and ViewController.
- (void)achievement1ButtonPressed:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"achievement1" object:self userInfo:nil];
}
I hope you guys can help me out. Thanks a lot
Jonas
Do you even have a method named receiveNotificationWithName? When you entered the code, the autocomplete should have barfed on that, offering receiveNotificationWithName: instead (unless you wrote it first, without NSNotification*, then added it later...
Anyway, there is a big difference between the two. Also, your handler is buggy. You should revisit that code.
There are lots of reasons to prefer blocks, and this points to one of them. You can just put your code right in the registration, and not worry about giving the wrong selector.
[[NSNotificationCenter defaultCenter] addObserverForName:#"achievement1"
object:nil
queue:nil
usingBlock:^{
// Handle achievement1 right in this little block.
}];
These look like one-shot notifications, so if you want to unregister the notification handler after it runs one time, do this (note the __block).
__block id achievement1 = [[NSNotificationCenter defaultCenter]
addObserverForName:#"achievement1 ":^{
object:nil
queue:nil
usingBlock:^{
// Handle achievement1 right in this little block.
// Remove observer from notification center
[[NSNotificationCenter defaultCenter] removeObserver:achievement1];
}];
All of this looks like it should work (besides the colon, like Andrea mentioned). Is it possible that your button callback isn't being called for a similar reason? I would add an NSLog in your achievement1ButtonPressed: method to make sure.
What I can see is that only one of your addObesrver method call the right selector with the ":", thus the right method signature.

AVQueuePlayer/AVPlayer loading notification?

I have an AVQueuePlayer (which obviously extends AVPlayer) that loads a playlist of streaming audio. Streaming is all working fine, but I'd like to have an activity indicator to show the user audio is loading. Trouble is, I can't seem to find any such Notification in AVQueuePlayer (or AVPlayer) that would indicate when the audio buffer has finished loading/is ready to play (nor does there appear to be a delegate method). Any thoughts?
You will have to use KVO to get this done.
For each item you are adding to the queue, you may setup observers like this:
item_ = [[AVPlayerItem playerItemWithURL:[NSURL URLWithString:#"http://somefunkyurl"]] retain];
[item_ addObserver:self forKeyPath:#"status" options:0 context:nil];
[item_ addObserver:self forKeyPath:#"playbackBufferEmpty" options:0 context:nil];
Now you can evaluate the status of that item within the observer method;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([object isKindOfClass:[AVPlayerItem class]])
{
AVPlayerItem *item = (AVPlayerItem *)object;
//playerItem status value changed?
if ([keyPath isEqualToString:#"status"])
{ //yes->check it...
switch(item.status)
{
case AVPlayerItemStatusFailed:
NSLog(#"player item status failed");
break;
case AVPlayerItemStatusReadyToPlay:
NSLog(#"player item status is ready to play");
break;
case AVPlayerItemStatusUnknown:
NSLog(#"player item status is unknown");
break;
}
}
else if ([keyPath isEqualToString:#"playbackBufferEmpty"])
{
if (item.playbackBufferEmpty)
{
NSLog(#"player item playback buffer is empty");
}
}
}
}

KVO nsmutablearray remove object notification

i have a singleton class with a nsmutablearray
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSLog(#"Received notification: keyPath:%# ofObject:%# change:%#",
keyPath, object, change);
//[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
//array accessors
- (void)insertNewObject:(id)object
{
[self willChangeValueForKey:#"heldJoins"];
[heldJoins addObject:object];
[self didChangeValueForKey:#"heldJoins"];
}
- (void)removeObject:(id)object
{
[self willChangeValueForKey:#"heldJoins"];
[heldJoins removeObject:object];
[self didChangeValueForKey:#"heldJoins"];
}
i "observe" the changes made to this array from another class
my rootviewcontroller
[CCV addObserver:self forKeyPath:#"heldJoins" options:NSKeyValueChangeOldKey||NSKeyValueChangeNewKey context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"Received notification: keyPath:%# ofObject:%# change:%#",keyPath, object, [change allKeys]);
}
this works and i am notified when there is an object removed or added
however i cannot figure out (and maybe its not possible) how to see the object that was removed or added (mainly need the object when its removed not added though)
i know the NSDictionary variable Change should have my object but all it has is one key "kind" which always equals 1 since there is always 1 change made to the array.
the nsmutablearray is filled with numbers 500,100,300
so when one of those numbers is removed i would like to know which number was removed in my observer class
how can i do that?
answer code:
[CCV addObserver:self forKeyPath:#"heldJoins" options: NSKeyValueObservingOptionOld ||NSKeyValueChangeNewKey context:NULL];
NSLog(#"Received notification: keyPath:%# ofObject:%# change:%#",keyPath, object, [change valueForKey:#"new"]);
It sounds like you didn't specify NSKeyValueObservingOptionOld when you added the observer.