Cocoa: User Notifications never being delivered - cocoa

I'm trying to deliver a local notification, it looks something like this:
NSUserNotification *notification = [[NSUserNotification alloc] init];
//set title, subtitle, and sound
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
But the notification doesn't appear. I know sometimes notifications are not presented if the app is frontmost but at the time of delivery it's not. In system preferences I made sure that notifications from my app are allowed, and I even overrode the userNotificationCenter:shouldPresentNotification: method to always return YES but it still doesn't present the notification.
What is most confusing is that everything was working fine until I updated to Mavericks. I suppose something has changed in the update but I can't figure out what.
Thanks for the help.

my guess is that something is nil. make sure you are either assigning a (valid and non-nil) title, or informativeText.
I imagine that having invalid values for other properties such as otherButtonTitle might prevent the notification from being displayed as well.
Are there any error messages in your console?
Use the debugger or NSLog() statements to assess the values assigned to the notification. You could also see this problem if the NSUserNotification pointer is nil (not the case from the code you posted, but worth mentioning).
Here is a minimal test for an app delegate that works on Mac OS X 10.9 (Mavericks):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSUserNotificationCenter* unc = [NSUserNotificationCenter defaultUserNotificationCenter];
unc.delegate = self;
NSUserNotification* notice = [[NSUserNotification alloc] init];
notice.title = #"title"; // notifications need a title, or informativeText at minimum to appear on screen
//notice.informativeText = #"informativeText";
NSLog(#"notification: %#, notification center:%#", notice, unc);
[unc deliverNotification:notice];
}
// The notifications will always dispaly even if we are in the foreground
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification
{
return YES;
}

Related

Drag and drop files into NSOutlineView

I'm trying to implement simple drag and drop operation into NSOutlineView Based on Apple's example - https://developer.apple.com/library/mac/samplecode/SourceView/Introduction/Intro.html
All seems to be ok, but finally when I drop some files from Finder I get error:
[<ChildNode 0x60800005a280> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key description.') was raised during a dragging session
Here is my test project: https://www.dropbox.com/s/1mgcg2dysvs292u/SimpleDrag.zip?dl=0
What I exactly need in my app: allow user to drag and drop multiple files and folder into some tree list and then display them to user. Also save all this this into some file, so it can be loaded again with all user dragged files and folders.
A final result I want to have like this:
The description property of NSObject is read-only, and is generally set by providing a getter in the implementation file:
- (NSString *)description {
return [self urlString]; // Using urlString solely for demo purposes.
}
You can't set it, either via key-value coding or by direct assignment:
self.description = [self urlString]; // Xcode error: 'Assignment to readonly property'
[self setValue:[self urlString] forKey:#"description"];
In -[ChildNode copyWithZone:] an attempt is made to do the latter of the two, and that's what causes the warning to be logged to the console.
// -------------------------------------------------------------------------------
// copyWithZone:zone
// -------------------------------------------------------------------------------
- (id)copyWithZone:(NSZone *)zone
{
id newNode = [[[self class] allocWithZone:zone] init];
// One of the keys in mutableKeys is 'description'...
// ...but it's readonly! (it's defined in the NSObject protocol)
for (NSString *key in [self mutableKeys])
{
[newNode setValue:[self valueForKey:key] forKey:key];
}
return newNode;
}
This begs the question why do you get the warning in your app, and not in the sample app? From what I can tell no ChildNode instance is ever sent a copyWithZone: message in the sample app, whereas this does happen in your app, immediately after the drop. Of course there's a second question here as well: why do Apple explicitly include the description key-path when it can't be set this way? - unfortunately I can't help you with that.
A really handy way of trying to trap errors that don't actually cause exceptions is to add an All Exceptions breakpoint. If you do this in your sample app you'll see that the app freezes at the line that's causing the problem, giving you a better chance of figuring out the issue.

(OS X) Detecting when front app goes into fullscreen mode

I'm writing a "UIElement" app that shows a status window on the side of the screen, similar to the Dock.
Now, when a program takes over the entire screen, I need to hide my status window, just like the Dock does.
What are my options to detect this and the inverse event?
I like to avoid polling via a timed event and also cannot use undocumented tricks (such as suggested here)
What doesn't work:
Registering a Carbon Event Handler for the kEventAppSystemUIModeChanged event isn't sufficient - it works to detect VLC's full screen mode, but not for modern Cocoa apps that use the new fullscreen widget at the top right corner of their windows.
Similarly, following Apple's instructions about the NSApplication presentationOptions API by observing changes to the currentSystemPresentationOptions property does not help, either - again, it only informs about VLC's fullscreen mode, but not about apps using the window' top right fullscreen widget.
Monitoring changes to the screen configuration using CGDisplayRegisterReconfigurationCallback is not working because there aren't any callbacks for these fullscreen modes.
Based on #Chuck's suggestion, I've come up with a solution that works somewhat, but may not be foolproof.
The solution is based on the assumption that 10.7's new fullscreen mode for windows moves these windows to a new Screen Space. Therefore, we subscribe to notifications for changes to the active space. In that notification handler, we check the window list to detect whether the menubar is included. If it is not, it probably means that we're in a fullscreen space.
Checking for the presence of the "Menubar" window is the best test I could come up with based on Chuck's idea. I don't like it too much, though, because it makes assumptions on the naming and presence of internally managed windows.
Here's the test code that goes inside AppDelegate.m, which also includes the test for the other app-wide fullscreen mode:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSApplication *app = [NSApplication sharedApplication];
// Observe full screen mode from apps setting SystemUIMode
// or invoking 'setPresentationOptions'
[app addObserver:self
forKeyPath:#"currentSystemPresentationOptions"
options:NSKeyValueObservingOptionNew
context:NULL];
// Observe full screen mode from apps using a separate space
// (i.e. those providing the fullscreen widget at the right
// of their window title bar).
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserverForName:NSWorkspaceActiveSpaceDidChangeNotification
object:NULL queue:NULL
usingBlock:^(NSNotification *note)
{
// The active space changed.
// Now we need to detect if this is a fullscreen space.
// Let's look at the windows...
NSArray *windows = CFBridgingRelease(CGWindowListCopyWindowInfo
(kCGWindowListOptionOnScreenOnly, kCGNullWindowID));
//NSLog(#"active space change: %#", windows);
// We detect full screen spaces by checking if there's a menubar
// in the window list.
// If not, we assume it's in fullscreen mode.
BOOL hasMenubar = NO;
for (NSDictionary *d in windows) {
if ([d[#"kCGWindowOwnerName"] isEqualToString:#"Window Server"]
&& [d[#"kCGWindowName"] isEqualToString:#"Menubar"]) {
hasMenubar = YES;
break;
}
}
NSLog(#"fullscreen: %#", hasMenubar ? #"No" : #"Yes");
}
];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:#"currentSystemPresentationOptions"]) {
NSLog(#"currentSystemPresentationOptions: %#", [change objectForKey:NSKeyValueChangeNewKey]); // a value of 4 indicates fullscreen mode
}
}
Since my earlier answer doesn't work for detecting full screen mode between apps, I did some experimentation. Starting with the solution that Thomas Tempelmann came up with of checking the presence of menu bar, I found a variation that I think could be more reliable.
The problem with checking for the menu bar is that in full screen mode you can move the mouse cursor to the top of the screen to make the menu bar appear, but you're still in full screen mode. I did some crawling through the CGWindow info, and discovered that when I enter full screen, there is window named "Fullscreen Backdrop" owned by the "Dock", and it's not there when not in full screen mode.
This is on Catalina (10.15.6) in an Xcode playground, so it should be tested in a real app, and on Big Sur (or whatever the current OS is, when you're reading this).
Here's the code (in Swift... easier to quickly test something)
func isFullScreen() -> Bool
{
guard let windows = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) else {
return false
}
for window in windows as NSArray
{
guard let winInfo = window as? NSDictionary else { continue }
if winInfo["kCGWindowOwnerName"] as? String == "Dock",
winInfo["kCGWindowName"] as? String == "Fullscreen Backdrop"
{
return true
}
}
return false
}
EDIT NOTE: This answer unfortunately doesn't provide a solution for detecting full screen in a different app, which is what the OP was asking. I'm leaving it because it does answer the question for detecting in in the same app going full screen - for example in a generic library that needs to know to automatically update keyEquivalents and title for an explicitly added "Enter Full Screen" menu item rather than Apple's automatically added menu item.
Although this question is quite old now, I've had to detect full screen mode in Swift recently. While it's not as simple as querying some flag in NSWindow, as we would hope for, there is an easy and reliable solution that has been available since macOS 10.7.
Whenever a window is about to enter full screen mode NSWindow sends a willEnterFullScreenNotification notification, and when it is about to exit full screen mode, it sends willExitFullScreenNotification. So you add an observer for those notifications. In the following code, I use them to set a global boolean flag.
import Cocoa
/*
Since notification closures can be run concurrently, we need to guard against
races on the Boolean flag. We could use DispatchSemaphore, but it's kind
over-kill for guarding a simple read/write to a boolean variable.
os_unfair_lock is appropriate for nanosecond-level contention. If the wait
could be milliseconds or longer, DispatchSemaphore is the thing to use.
This extension is just to make using it easier and safer to use.
*/
extension os_unfair_lock
{
mutating func withLock<R>(block: () throws -> R) rethrows -> R
{
os_unfair_lock_lock(&self)
defer { os_unfair_lock_unlock(&self) }
return try block()
}
}
fileprivate var fullScreenLock = os_unfair_lock()
public fileprivate(set) var isFullScreen: Bool = false
// Call this function in the app delegate's applicationDidFinishLaunching method
func initializeFullScreenDetection()
{
_ = NotificationCenter.default.addObserver(
forName: NSWindow.willEnterFullScreenNotification,
object: nil,
queue: nil)
{ _ in
fullScreenLock.withLock { isFullScreen = true }
}
_ = NotificationCenter.default.addObserver(
forName: NSWindow.willExitFullScreenNotification,
object: nil,
queue: nil)
{ _ in
fullScreenLock.withLock { isFullScreen = false }
}
}
Since observer closures can be run concurrently, I use os_unfair_lock to guard races on the _isFullScreen property. You could use DispatchSemaphore, though it's a bit heavy weight for just guarding a Boolean flag. Back when the question was first asked, OSSpinLock would have been the equivalent, but it's been deprecated since 10.12.
Just make sure to call initializeFullScreenDetection() in your application delegate's applicationDidFinishLaunching() method.

UITextField - message sent to deallocated instance "UIKBStringWideAttributeValueForKey:"

After enabling Zombie Objects I am able to see the following error when I try to edit a UITextField (textLvl):
2013-01-13 13:27:10.509 testob[18418:907] *** -[NSConcreteMutableAttributedString
_UIKBStringWideAttributeValueForKey:]: message sent to deallocated instance 0x2066a1f0
I have posted the portion of code that is causing the issue below, it seems to specifically be the "textField.text = self.storeText;" part - as when I comment this out the problem goes away.
You may be able to tell I am not the most experienced iOS dev, why would my UITextView deallocate after I've set the text? Help please!
Also, I've never heard of "_UIKBStringWideAttributeValueForKey" before - any ideas?
Thanks all!
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
NSLog(#"Text began editing");
self.storeText = textField.text;
textField.text = #"";
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
if (textField == textLvl){
if ([textField.text isEqualToString:#""]){
textField.text = self.storeText;
NSLog(#"No Text");
}
self.conv = [textField.text intValue];
if (self.conv >= 101){
textField.text = #"100";
UIAlertView *successAlert = [[UIAlertView alloc] initWithTitle:#"Oh no!" message:#"Can't be higher than 100." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[successAlert show]; }
}}
This might be a bug internal to the framework. I suggest you add - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField and set the textfield text to nil and then reset the textfield text.
I think the real answer here is that you're dealing with a UITextField that has been set to handle NSAttributedString rather than NSString. You'll notice if you're dealing with UITextField defined in a .xib, it's top property option is now "Text" with options Plain or Attributed.
If your text field was switched to attributed, you'll find this error happening if you continue to deal with the text field as if it were Plain.
My textfield was in a xib file and it's delegate was hooked up to the files owner. This was causing the crash for me, because the file's owner was NSObject.
I actually wanted to hook up the delegate to the cell, and not files owner.
Use instruments so see the retains/releases:
If you need to see where retains, releases and autoreleases occur for an object use instruments:
Run in instruments, in Allocations set "Record reference counts" on on (you have to stop recording to set the option). Cause the picker to run, stop recording, search for there ivar (datePickerView), drill down and you will be able to see where all retains, releases and autoreleases occurred.

AV Foundation: Difference between currentItem being ready to play, and -[AVPlayer readyForDisplay] property?

I'm running into a weird situation with my video player, the core code of which hasn't changed much from what worked in an earlier app I made. Here's the problem: I'm inserting a "_loadingLayer" (a CATextLayer that says the video is loading), and then observing the AVPlayer's currentItem's status property to figure out when to remove the "_loadingLayer" and replace it with my actual "_playerLayer". Here's my KVO code for that:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ((object == _playerLayer) && (_playerLayer.player.currentItem.status == AVPlayerItemStatusReadyToPlay)) {
[CATransaction setAnimationDuration:1.8];
_loadingLayer.opaque = NO;
if (_playerLayer.readyForDisplay) {
NSLog(#"Should be ready now.");
}
[self addPlayerLayerToLayerTree];
}
}
My problem is that the video is starting, but only the audio is playing -- the layer stays black. When I inserted the NSLog statement above, I found out why: Apparently although the currentItem's status is "AVPlayerItemStatusReadyToPlay", the player layer isn't actually readyForDisplay. This makes no sense to me -- it seems counterintuitive. Can someone please give me some guidance on this?
I was able to verify that _playerLayer is being added to the layer tree by setting its background color to red.
One other weird thing that I think might be related.... I've been seeing these messages in the debugger console:
PSsetwindowlevel, error setting window level (1000)
CGSSetIgnoresCycle: error 1000 setting or clearing window tags
Thanks in advance. This is a crosspost from the Apple Dev Forums.
We had a similar problem and traced it to what I believe is a bug in iOS 5.1 (and maybe earlier versions). It is fixed in iOS 6.0. Since I couldn't find a solution to this anywhere, I'm writing a long writeup for future people that have this problem.
If the AVPlayerItem reports a status of AVPlayerStatusReadyToPlay before the AVPlayerLayer has been obtained then the AVPlayer will never report that it is readyForDisplay.
So when you do:
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
make sure that it's followed with:
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
and that you don't have much if any code in between the two.
I built a test rig to make it work 100% of the time or fail 100% of the time. Note that it can be tricky to see what's going on in your actual app since you will have different load times on the video and that will affect how quickly the playerItem reports AVPlayerStatusReadyToPlay.
If you want to test in your app, put this into a simple view. The below will not work (i.e. you'll hear audio but not see video) on iOS 5.1. If you switch loadPlayerLayer to instead be invoked at the end of loadPlayer, it will always work.
A follow on for future readers: A couple of player events can switch up this order and make you think it's working. They're red herrings though since they're inadvertently reversing the load order such that playerLayer is grabbed before AVStatusReadyToPlay. The events are: seeking the video, going to the home screen and then reactivating the app, the player switching to a different video/audio track inside an HLS video. These actions trigger AVStatusReadyToPlay again and thus make the playerLayer happen before AVStatusReadyToPlay.
Here's the test harness that uses Apple's test HLS video:
-(void)loadPlayer
{
NSLog(#"loadPlayer invoked");
NSURL *url = [NSURL URLWithString:#"https://devimages.apple.com.edgekey.net/resources/http-streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8"];
self.playerItem = [AVPlayerItem playerItemWithURL:url];
[self.playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:&kPlayerContext];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
}
-(void)loadPlayerLayer
{
NSLog(#"starting player layer");
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
[self.playerLayer addObserver:self forKeyPath:#"readyForDisplay" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:&kPlayerLayerContext];
[self.playerLayer setFrame:[[self view] bounds]];
[[[self view] layer] addSublayer:self.playerLayer];
}
-(void)observeValueForKeyPath:(NSString*)path ofObject:(id)object change:(NSDictionary*)change context:(void*) context
{
if(context == &kPlayerContext){
if([self.player status] == AVPlayerStatusReadyToPlay){
NSLog(#"Player is ready to play");
//Robert: Never works if after AVPlayerItem reports AVPlayerStatusReadyToPlay
if(!self.startedPlayerLayer){
self.startedPlayerLayer = YES;
[self loadPlayerLayer];
}
}
}
if(context == &kPlayerLayerContext){
if([self.playerLayer isReadyForDisplay] == YES){
NSLog(#"PlayerLayer says it's ready to display now");
[self playTheVideoIfReady];
}
}
}

NSButton subclass that responds to right clicks

I have an NSButton subclass that I would like to make work with right mouse button clicks. Just overloading -rightMouseDown: won't cut it, as I would like the same kind of behaviour as for regular clicks (e.g. the button is pushed down, the user can cancel by leaving the button, the action is sent when the mouse is released, etc.).
What I have tried so far is overloading -rightMouse{Down,Up,Dragged}, changing the events to indicate the left mouse button clicks and then sending it to -mouse{Down,Up,Dragged}. Now this would clearly be a hack at best, and as it turns out Mac OS X did not like it all. I can click the button, but upon release, the button remains pushed in.
I could mimic the behaviour myself, which shouldn't be too complicated. However, I don't know how to make the button look pushed in.
Before you say "Don't! It's an unconventional Mac OS X behaviour and should be avoided": I have considered this and a right click could vastly improve the workflow. Basically the button cycles through 4 states, and I would like a right click to make it cycle in reverse. It's not an essential feature, but it would be nice. If you still feel like saying "Don't!", then let me know your thoughts. I appreciate it!
Thanks!
EDIT: This was my attempt of changing the event (you can't change the type, so I made a new one, copying all information across. I mean, I know this is the framework clearly telling me Don't Do This, but I gave it a go, as you do):
// I've contracted all three for brevity
- (void)rightMouse{Down,Up,Dragging}:(NSEvent *)theEvent {
NSEvent *event = [NSEvent mouseEventWithType:NSLeftMouse{Down,Up,Dragging} location:[theEvent locationInWindow] modifierFlags:[theEvent modifierFlags] timestamp:[theEvent timestamp] windowNumber:[theEvent windowNumber] context:[theEvent context] eventNumber:[theEvent eventNumber] clickCount:[theEvent clickCount] pressure:[theEvent pressure]];
[self mouse{Down,Up,Dragging}:event];
}
UPDATE: I noticed that -mouseUp: was never sent to NSButton, and if I changed it to an NSControl, it was. I couldn't figure out why this was, until Francis McGrew pointed out that it contains its own event handling loop. Now, this also made sense to why before I could reroute the -rightMouseDown:, but the button wouldn't go up on release. This is because it was fetching new events on its own, that I couldn't intercept and convert from right to left mouse button events.
NSButton is entering a mouse tracking loop. To change this you will have to subclass NSButton and create your own custom tracking loop. Try this code:
- (void) rightMouseDown:(NSEvent *)theEvent
{
NSEvent *newEvent = theEvent;
BOOL mouseInBounds = NO;
while (YES)
{
mouseInBounds = NSPointInRect([newEvent locationInWindow], [self convertRect:[self frame] fromView:nil]);
[self highlight:mouseInBounds];
newEvent = [[self window] nextEventMatchingMask:NSRightMouseDraggedMask | NSRightMouseUpMask];
if (NSRightMouseUp == [newEvent type])
{
break;
}
}
if (mouseInBounds) [self performClick:nil];
}
This is how I do it; Hopefully it will work for you.
I've turned a left mouse click-and-hold into a fake right mouse down on a path control. I'm not sure this will solve all your problems, but I found that the key difference when I did this was changing the timestamp:
NSEvent *event = [NSEvent mouseEventWithType:NSLeftMouseDown
location:[theEvent locationInWindow]
modifierFlags:[theEvent modifierFlags]
timestamp:CFAbsoluteGetTimeCurrent()
windowNumber:[theEvent windowNumber]
context:[theEvent context]
// I was surprised to find eventNumber didn't seem to need to be faked
eventNumber:[theEvent eventNumber]
clickCount:[theEvent clickCount]
pressure:[theEvent pressure]];
The other thing is that depending on your button type, its state may be the value that is making it appear pushed or not, so you might trying poking at that.
UPDATE: I think I've figured out why rightMouseUp: never gets called. Per the -[NSControl mouseDown:] docs, the button starts tracking the mouse when it gets a mouseDown event, and it doesn't stop tracking until it gets mouseUp. While it's tracking, it can't do anything else. I just tried, for example, at the end of a custom mouseDown::
[self performSelector:#selector(mouseUp:) withObject:myFakeMouseUpEvent afterDelay:1.0];
but this gets put off until a normal mouseUp: gets triggered some other way. So, if you've clicked the right mouse button, you can't (with the mouse) send a leftMouseUp, thus the button is still tracking, and won't accept a rightMouseUp event. I still don't know what the solution is, but I figured that would be useful information.
Not much to add to the answers above, but for those working in Swift, you may have trouble finding the constants for the event mask, buried deep in the documentation, and still more trouble finding a way to combine (OR) them in a way that the compiler accepts, so this may save you some time. Is there a neater way? This goes in your subclass -
var rightAction: Selector = nil
// add a new property, by analogy with action property
override func rightMouseDown(var theEvent: NSEvent!) {
var newEvent: NSEvent!
let maskUp = NSEventMask.RightMouseUpMask.rawValue
let maskDragged = NSEventMask.RightMouseDraggedMask.rawValue
let mask = Int( maskUp | maskDragged ) // cast from UInt
do {
newEvent = window!.nextEventMatchingMask(mask)
}
while newEvent.type == .RightMouseDragged
My loop has become a do..while, as it always has to execute at least once, and I never liked writing while true, and I don't need to do anything with the dragging events.
I had endless trouble getting meaningful results from convertRect(), perhaps because my controls were embedded in a table view. Thanks to Gustav Larsson above for my ending up with this for the last part -
let whereUp = newEvent.locationInWindow
let p = convertPoint(whereUp, fromView: nil)
let mouseInBounds = NSMouseInRect(p, bounds, flipped) // bounds, not frame
if mouseInBounds {
sendAction(rightAction, to: target)
// assuming rightAction and target have been allocated
}
}

Resources