I have a simple single-window application with a menu item that allows users to have the NSWindow appear always on top.
My function looks like this:
#IBAction func changeAlwaysOnTop(sender: AnyObject) {
if (alwaysOnTopMenuItem.state == NSOnState) {
alwaysOnTopMenuItem.state = NSOffState;
window.level = kCGNormalWindowLevelKey;
} else {
alwaysOnTopMenuItem.state = NSOnState;
window.level = kCGStatusWindowLevelKey;
}
}
Turning "Always on Top" on works well - the window floats above all other applications as it should. However, when the option is turned off, the window continues to float above all other windows, as if window.level = kCGNormalWindowLevelKey; isn't actually doing anything.
I've tried different window levels, and I've tried ordering the window out and back in again. The window continues to float above all others.
How can I set the window back to normal after setting the level to kCGNormalWindowLevelKey?
Edit: the following Objective C code works just fine:
- (IBAction)changeOnTop:(id)sender {
if (self.onTopMenuItem.state == NSOnState) {
self.onTopMenuItem.state = NSOffState;
self.window.level = NSNormalWindowLevel;
} else {
self.onTopMenuItem.state = NSOnState;
self.window.level = NSStatusWindowLevel;
}
}
You are using the wrong values for the level. You are using the keys by which window levels are looked up.
First, since you're using Cocoa, you should use the Cocoa constants for the window level: NSNormalWindowLevel and NSStatusWindowLevel.
If you look at the definitions of those constants, you'll find:
#define NSNormalWindowLevel kCGNormalWindowLevel
#define NSStatusWindowLevel kCGStatusWindowLevel
Notice the lack of the word "Key" on the end of those kCG... constants. If you then look up how those constants are defined, you'll find:
#define kCGNormalWindowLevel \
CGWindowLevelForKey(kCGNormalWindowLevelKey)
#define kCGStatusWindowLevel \
CGWindowLevelForKey(kCGStatusWindowLevelKey)
So, the values you used are keys that are passed to CGWindowLevelForKey() to get the actual level.
This works for me, and looks a bit nicer! (Swift 4)
window.level = .floatingWindowLevel
This worked for me in Swift 4.2.
override func viewDidAppear() {
super.viewDidAppear()
view.window?.level = .floating
}
Related
I am currently designing an extension to make the notifications in the notification section of the calendar expendable. The goal is to make the noficiation expand like the initial notification on the desktop does. I have changed the type of notification added to the noficiation tray to class NotificationBanner from class NotificationMessage. I am currently using a work-around to make this work, this is what my expand function looks like:
expand(animate) {
this.expanded = true;
this._actionBin.visible = this._actionBin.get_n_children() > 0;
if (this._bodyStack.get_n_children() < 2) {
this._expandedLabel = new MessageList.URLHighlighter(this._bodyText,
true, this._useBodyMarkup);
this.setExpandedBody(this._expandedLabel);
}
if (animate) {
if (!this.clickedByButton && !this.forceExpansion) {
// This is the usual way notifications are expanded, using the layout manager
this._bodyStack.ease_property('#layout.expansion', 1, {
progress_mode: Clutter.AnimationMode.EASE_OUT_QUAD,
duration: MessageTray.ANIMATION_TIME,
});
}
else if (this.forceExpansion || this.clickedByButton) {
// When auto expanding or clicked by button, change height of body
oldHeight = this.bodyLabel.get_height();
const lines = Math.ceil(this._bodyText.length / 54);
this.bodyLabel.set_height(lines * this.bodyLabel.get_height());
}
this._actionBin.scale_y = 0;
this._actionBin.ease({
scale_y: 1,
duration: MessageTray.ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
} else {
this._bodyStack.layout_manager.expansion = 1;
this._actionBin.scale_y = 1;
}
this.emit('expanded');
}
As you can see, I have 2 options for this extension: Force expand all notifications or make the user use a button to expand. The current solution is not elegant, it simply changes the height of the notification label which manages the body. Furhermore, the notification body still shows the three dots, implying that the body is still not expanded. I believe this to be an issue with the layout manager, since the proper way to expand is to set message._bodyStack.layout_manager.expansion to 1. That does not work in the case of expanding a message in the notification tray. Is anyone familiar with the layout manager or can help me find a different solution? Here is an image of what my current solution looks like:
Image of an automatically expanded notification in the notification tray due to the extension (note the three dots at the end of the first line being still there)
Okay I have found a solution, it is not related to the layout manager. The value of the message message.bodyLabel.clutter_text.ellipsize is set to 3, which is the main cause of the dots appearing on the notification. Setting this value to 0 solves this problem. I would have still loved to find a more elegant approach to displaying the body, but this will do.
I have an app for ipad/iphone, now adding also mac support by Mac Catalyst. On Mac, I want to control window resizing, in order to allow only some sizes and aspects. It goes beyond simple minimal height and weight, or aspect. I want to allow user to resize window freely, but when app gets too high and narrow, I want to also seemlessly increase width, to keep some minimal aspect.
I believe that in AppKit it can be done through NSWindowDelegate.windowWillResize() (get user defined size, count required size and return it). However I am getting error "NSWindowDelegate is unavailable in Mac Catalyst" . Is it possible to achieve the result I want by Catalyst means?
Answering my own question. It is NOT possible to create own NSWindowDelegate with windowWillResize() implemented in Catalyst. However, it IS possible to create a new target only for mac, and use it as a plugin from catalyst target.
First I load mac-only plugin (using Bundle.load() ), and instantiate its principalClass. Then I get NSWindow from UIWindow, which is easy through Dynamic library. Then I pass NSWindow to method of a plugin, which then can set own NSWindowDelegate, because it does not run in catalyst.
Sample code:
guard let bundle = Bundle(url: bundleURL) else { return }
let succ = bundle.load()
if (succ) {
let macUtilsClass = bundle.principalClass! as! MacUtilsProtocol.Type
self.macUtils = macUtilsClass.init()
var dnsw: NSObject? = nil
if (ProcessInfo.processInfo.isOperatingSystemAtLeast(
OperatingSystemVersion(majorVersion: 11, minorVersion: 0, patchVersion: 0))) {
dnsw = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(AppDelegate.ref!.window).attachedWindow
}
else {
dnsw = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(AppDelegate.ref!.window)
}
self.macUtils.SetupMainWindow(win: dnsw!)
}
I have written the same behaviour in two ways. The first one, doesn't work:
var barbutton = (left) ? navigationItem.leftBarButtonItem : navigationItem.rightBarButtonItem
barbutton = UIBarButtonItem(customView: button)
opposite to:
if (left) {
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: button)
} else {
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: button)
}
What is the difference?
As #Martin points out, the two code sequences are very different in function.
The first sets barbutton to the contents of either leftBarButtonItem or rightBarButtonItem and then discards that value and sets (the temporary variable) to a newly created button.
The second sets either leftBarButtonItem or rightBarButtonItem, depending on left to a newly created button.
There really isn't much way to shorten your code and still achieve the desired effect (without also obscuring the code).
You could use:
(left ? navigationItem.setLeftBarButtonItem : navigationItem.setRightBarButtonItem)(UIBarButtonItem(customView: button), animated: false)
But that's just weird.
I'm working on iOS 8 custom keyboard extension right now, and there are some issues in using UITextInputDelegate Methods.
Does this right: selectionWillChange: and selectionDidChange: methods should be called when user long-presses typing area? And textWillChange: and textDidChange: methods should be called whenever the text is literally changing?
Actually, what I observed is that, when I changed selection in text input area, textWillChange: and textDidChange: are called, and I cannot get a clue that the other two methods are called in what condition. If anyone knows about the usage of these delegate methods, please let me know.
It's not working for me either... what I am currently doing is just using textWillChange and textDidChange which does get called, as you mentioned, when you change your selection... (they get called BEFORE and AFTER) And then comparing the: self.textDocumentProxy.documentContextBeforeInputself.textDocumentProxy.documentContextAfterInput From BEFORE (textWillChange) to the AFTER (textDidChange) to see if selection range or length changed at all.
Something like this (set the 4 NSStrings below in your .h file of course... haven't tested this exact snippet because I wrote it from scratch just now on SO.com but I'm sure the principle works if I made any errors)
- (void)textWillChange:(id<UITextInput>)textInput {
beforeStringOfTextBehindCursor = self.textDocumentProxy.documentContextBeforeInput;
beforeStringOfTextAfterCursor = self.textDocumentProxy.documentContextAfterInput;
}
- (void)textDidChange:(id<UITextInput>)textInput {
afterStringOfTextBehindCursor = self.textDocumentProxy.documentContextBeforeInput;
afterStringOfTextAfterCursor = self.textDocumentProxy.documentContextAfterInput;
BOOL didSelectionChange = NO;
if (![beforeStringOfTextBehindCursor isEqualToString:afterStringOfTextBehindCursor]) {
didSelectionChange = YES;
}
if (![beforeStringOfTextAfterCursor isEqualToString:afterStringOfTextAfterCursor]) {
didSelectionChange = YES;
}
if (didSelectionChange) {
NSLog(#"Selection Changed!");
}
}
I had the same problem with the functions specified in the UITextInput protocol not being called. The reason as far as I can discern is due to the fact that the inputDelegate is set at runtime. According to the ios docs:
The UIKit provides a private text input delegate, which it assigns at runtime to the inputDelegate property of the object whose class adopts the UITextInput protocol. link
The fix which works in my case is to reset the inputDelegate in the function:
textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
by the following line:
[myUITextField setInputDelegate:self];
where self implements the UITextInputDelegate protocol.
Is is possible to capture the Mac OS X desktop without desktop items and any windows that may be open (i.e. just the wallpaper)?
I've experimented with CGWindowListCreateImage, CGWindowListCreateImageFromArray, and CGDisplayCreateImage, but no luck.
Essentially I'm trying to capture the desktop wallpaper without using [NSWorkspace desktopImageURLForScreen:] (it's a sandboxed app without access to the file system).
You'll need to be careful to test that this is still correct, but the desktop window sits below the Finder (it's drawn by the Dock). Passing the kCGWindowListOptionOnScreenBelowWindow CGWindowListOption to CGWindowListCreateImage should get you what you want (unless something else is drawing below that level).
Otherwise, you'll need to use CGWindowListCreate and iterate through the response excluding anything that isn't drawn by the dock at the window level kCGMinimumWindowLevel + 19.
It gets kind of tricky when there are multiple screens, but hopefully this information is enough for you to do what you need.
I know this is a super old question, and Tony Arnold's question is right, and what I used to build my own "grab the desktop" code.
I have some example code that shows how to do all these things (it's a wonderful thing walking in parts of Cocoa that are barely documented... )
I've put that sample code up in a bitbucket repository. Specifically the code sample to take a picture. (There's more interesting Cocoa code in my learning Cocoa repository, where that sample code is from )
Swift version:
extension NSImage {
static func desktopPicture() -> NSImage {
let windows = CGWindowListCopyWindowInfo(
CGWindowListOption.OptionOnScreenOnly,
CGWindowID(0))! as NSArray
var index = 0
for var i = 0; i < windows.count; i++ {
let window = windows[i]
// we need windows owned by Dock
let owner = window["kCGWindowOwnerName"] as! String
if owner != "Dock" {
continue
}
// we need windows named like "Desktop Picture %"
let name = window["kCGWindowName"] as! String
if !name.hasPrefix("Desktop Picture") {
continue
}
// wee need the one which belongs to the current screen
let bounds = window["kCGWindowBounds"] as! NSDictionary
let x = bounds["X"] as! CGFloat
if x == NSScreen.mainScreen()!.frame.origin.x {
index = window["kCGWindowNumber"] as! Int
break
}
}
let cgImage = CGWindowListCreateImage(
CGRectZero,
CGWindowListOption(arrayLiteral: CGWindowListOption.OptionIncludingWindow),
CGWindowID(index),
CGWindowImageOption.Default)!
let image = NSImage(CGImage: cgImage, size: NSScreen.mainScreen()!.frame.size)
return image
}
}