My app has an option that allows the user to choose between the standard "full-size" window titlebar/toolbar and the "compact" titlebar/toolbar made available in the NSWindow 10.10 API. Specifically, I'm using the -titleVisibility method to set either NSWindowTitleVisible or NSWindowTitleHidden depending on the user's preference. If the user checks the "Compact Titlebar" checkbox, NSWindowTitleHidden is applied to the window, otherwise, the window uses the default style. When the checkbox value changes, the value is stored in the app's user defaults, and the window is updated/redrawn.
Everything works great until the application is relaunched. Each time the app starts up, the window grows by exactly how much space is saved by switching from the default window style (NSWindowTitleVisible) to the new style (NSWindowTitleHidden). So restarting the app 5 – 6 times will make the window flush with the menubar and the dock, depending on how big the window was when the checkbox was initially checked.
In other words, it doesn't seem like the window's frame is being updated in NSUserDefaults when the property is set. Is there a workaround for this, or am I just overlooking something? Any advice would be muy helpful.
Thanks!
A better (and confirmed working) solution was posted at https://openradar.appspot.com/18510665 by pointum:
The problem is that window size is restored by the system using -[NSWindow setFrameUsingName:] before titleVisibility is set. Solution:
Remove "Autosave Name" value in Interface Builder.
Set it in code right after setting titleVisibility using -[NSWindow setFrameAutosaveName:].
Try setting the titleVisibility property to the number 1 in the User Defined Runtime Attributes
1 is the corresponding value for NSWindowTitleHidden
typedef NS_ENUM(NSInteger, NSWindowTitleVisibility) {
/* The default mode has a normal window title and titlebar buttons. */
NSWindowTitleVisible = 0,
/* The always hidden mode hides the title and moves the toolbar up into the area previously occupied by the title. */
NSWindowTitleHidden = 1,
} NS_ENUM_AVAILABLE_MAC(10_10);
However this would print a message to the console complaining that NSWindow is not key value coding-compliant for the key titleVisibility on OS X versions previous to 10.10
Simple fix for now is to save and restore the window's frame manually, here's how I do it:
In your app delegate, when application terminates, save the window's frame
- (void)applicationWillTerminate:(NSNotification *)notification
{
[[NSUserDefaults standardUserDefaults] setObject:NSStringFromRect(self.windowController.window.frame) forKey:#"WindowFrameKey"];
}
In your window controller's -awakeFromNib method, restore the frame
- (void)awakeFromNib
{
if([NSWindow instancesRespondToSelector:#selector(setTitleVisibility:)])
{
// Hide Titlebar
[self.window setTitleVisibility:NSWindowTitleHidden];
NSString *winFrameString = [[NSUserDefaults standardUserDefaults] stringForKey:#"WindowFrameKey"];
if(winFrameString != nil)
{
NSRect savedRect = NSRectFromString(winFrameString);
if(!NSEqualRects(self.window.frame, savedRect))
{
[self.window setFrame:savedRect display:YES animate:NO];
}
}
}
Related
in my Navigation Controller I need to temporarily disable the back button. I know that it can be hidden using the following or something similar:
[self.navigationController.navigationItem setHidesBackButton:YES animated:YES];
But that is not what I need, instead I want the back button to be greyed out and non-responsive to user touch events. Is their a way to achieve this without replacing the default back button?
Thanks in advance!
To disable the back button, these commands would make it do what you want it to do:
Enable:
self.navigationController.navigationBar.userInteractionEnabled = YES;
self.navigationController.navigationBar.tintColor = [UIColor blueColor];
Disabled:
self.navigationController.navigationBar.userInteractionEnabled = NO;
self.navigationController.navigationBar.tintColor = [UIColor lightGrayColor];
Update:
As of iOS 7, there's also a swipe that you'll want to disable on the UINavigationBar.
// You wrap it an 'if' statement so it doesn't crash
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
// disable the interactivePopGestureRecognizer
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
This hides the back button, so it becomes unreachable for the user. And it disables the swipe gesture.
[self.navigationItem setHidesBackButton:YES animated:YES];
Swift:
navigationItem.setHidesBackButton(true, animated: true)
See more info in Apple's documentation.
I know this is quite old but I had this problem too.
In my case in one scenario I had to disable the back button and in another one I had to disable all navigation buttons. my solution was disabling the navigation bar in total in both scenarios:
self.navigationController.view.userInteractionEnabled = NO;
This won't show the buttons as disabled but will prevent touches.
Hope this will help
I believe that following should help:
self.navigationController.navigationItem.backBarButtonItem.enabled = NO;
UPDATE
Sorry guys, my belief didn't come true.
It seems that property backBarButtonItem is designed only for setting custom title or image for Back Button.
From documentation:
If you want to specify a custom image or title for the back button,
you can assign a custom bar button item (with your custom title or
image) to this property instead. When configuring your bar button
item, do not assign a custom view to it; the navigation item ignores
custom views in the back bar button anyway.
The default value of this property is nil.
Unfortunately I didn't find any way of disabling back button with saving its native look and behaviour, because any time when I try to set custom UIBarButtonItem into navigationItem.backBarButtonItem property - it gets updated with appropriate native back button style and it always has enabled == YES.
I think this is done by Apple for a reason because we basically shouldn't force the user to stay on a detail screen and disable him from going back.
Also, in iOS7 and later user always can use swipe-from-left-edge gesture (if you don't disable it) to go back.
The only one ugly thing that I can recommend is to create a custom UIBarButtonItem and set it into leftBarButtonItem with 'Back' title, target and selector which will pop your viewController. By default it will substitute native back button.
Then you can disable it as usual using navigationItem.leftBarButtonItem.enabled = NO.
Unfortunately it will not look and act (in case of title updating depending on available space) as native back button :(
Just set a disabled back button on the navigation item of the previous view controller. Don't try to disable your custom back button if you already had one, won't work. Just set a new one which is disabled. You can reach the previous navigation item through the UINavigationBar.backItem property.
// set disabled back button
let backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItem.Style.plain, target: nil, action: nil)
backButton.isEnabled = false
navigationController?.navigationBar.backItem?.backBarButtonItem = backButton
// disable pop gesture
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
With OSX 10.10 beta 3, Apple released their dark tint option. Unfortunately, it also means that pretty much all status bar icons (with the exception of Apple's and Path Finder's that I've seen), including mine, remain dark on a dark background. How can I provide an alternate image for when dark tint is applied?
I don't see an API change on NSStatusBar or NSStatusItem that shows me a change, I'm assuming it's a notification or something reactive to easily make the change as the user alters the tint.
Current code to draw the image is encased within an NSView:
- (void)drawRect:(NSRect)dirtyRect
{
// set view background color
if (self.isActive) {
[[NSColor selectedMenuItemColor] setFill];
} else {
[[NSColor clearColor] setFill];
}
NSRectFill(dirtyRect);
// set image
NSImage *image = (self.isActive ? self.alternateImage : self.image);
_imageView.image = image;
}
TL;DR: You don't have to do anything special in Dark Theme. Give NSStatusItem (or NSStatusBarButton) a template image and it will style it correctly in any menubar context.
The reason why some apps' status items (such as PathFinder's) already work in Dark Theme is because they're not setting their own custom view on the StatusItem, but only setting a template image on the StatusItem.
Something like:
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
NSImage *image = [NSImage imageNamed:#"statusItemIcon"];
[image setTemplate:YES];
[_statusItem setImage:image];
This works exactly as you'd expect in Mavericks and earlier, as well as Yosemite and any future releases because it allows AppKit to do all of the styling of the image depending on the status item state.
Mavericks
In Mavericks (and earlier) there were only 2 unique styles of the items. Unpressed and Pressed. These two styles pretty much looked purely black and purely white, respectively. (Actually "purely black" isn't entirely correct -- there was a small effect that made them look slightly inset).
Because there were only two possible state, status bar apps could set their own view and easily get the same appearance by just drawing black or white depending on their highlighted state. (But again note that it wasn't purely black, so apps either had to build the effect in the image or be satisfied with a hardly-noticeable out of place icon).
Yosemite
In Yosemite there are at least 32 unique styling of items. Unpressed in Dark Theme is only one of those. There is no practical (or unpractical) way for an app to be able to do their own styling of items and have it look correct in all contexts.
Here are examples of six of those possible stylings:
Status items on an inactive menubar now have a specific styling, as opposed to a simple opacity change as in the past. Disabled appearance is one other possible variation; there are also other additional dimensions to this matrix of possibilities.
API
Arbitrary views set as NSStatusItem's view property have no way to capture all of these variations, hence it (and other related API) is deprecated in 10.10.
However, seed 3 introduces new API on NSStatusItem:
#property (readonly, strong) NSStatusBarButton *button NS_AVAILABLE_MAC(10_10);
This piece of API has a few purposes:
An app can now get the screen position (or show a popover from) a status item without setting its own custom view.
Removes the need for API like image, title, sendActionOn: on NSStatusItem.
Provides a class for new API: i.e. looksDisabled. This allows apps to get the standard disabled/off styling (like Bluetooth/Time Machine when off) without requiring a custom image.
If there's something that can't be done with the current (non- custom view) API, please file an enhancement request for it. StatusItems should provide behavior or appearances in a way that it standard across all status items.
More discussion is at https://devforums.apple.com/thread/234839, although I've summarized most everything here.
I end up did something like following to my custom drag and drop NSStatusItemView: (Using Swift)
var isDark = false
func isDarkMode() {
isDark = NSAppearance.currentAppearance().name.hasPrefix("NSAppearanceNameVibrantDark")
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
isDarkMode()
// Now use "isDark" to determine the drawing colour.
if isDark {
// ...
} else {
// ...
}
}
When the user changed the Theme in System Preferences, the NSView will be called by the system for re-drawing, you can change the icon colour accordingly.
If you wish to adjust other custom UI outside this view, you can either use KVO to observer the isDark key of the view or do it on your own.
I created a basic wrapper around NSStatusItem that you can use to provide support for 10.10 and earlier with custom views in the status bar. You can find it here: https://github.com/noahsmartin/YosemiteMenuBar The basic idea is to draw the custom view into a NSImage and use this image as a template image for the status bar item. This wrapper also forwards click events to the custom view so they can be handled the same way as pre 10.10. The project contains a basic example of how YosemiteMenuBar can be used with a custom view on the status bar.
Newest swift code set image template method is here:
// Insert code here to initialize your application
if let button = statusItem.button {
button.image = NSImage(named: "StatusIcon")
button.image?.isTemplate = true // Just add this line
button.action = #selector(togglePopover(_:))
}
Then it will change the image when dark mode.
When your application has drawn any GUI element you can get its appearance via [NSAppearance currentAppearance] which itself has a name property that holds something like
NSAppearanceNameVibrantDark->NSAppearanceNameAqua->NSAppearanceNameAquaMavericks
The first part is the appearance’s name, which is also available as a constant in NSAppearanceNameVibrantDark or NSAppearanceNameVibrantLight.
I don’t know if there’s a way to get just the first part, but I think this does the trick for now.
Example code:
-(void)awakeFromNib {
NSStatusItem* myStatusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
myStatusItem.title = #"Hello World";
if ([[[NSAppearance currentAppearance] name] containsString:NSAppearanceNameVibrantDark]) {
myStatusItem.title = #"Dark Interface";
} else {
myStatusItem.title = #"Light Interface";
}
}
But just in case you do want to monitor the status changes you can. I also know there is a better way to determine lite/dark mode than what's been said above, but I can remember it right now.
// Monitor menu/dock theme changes...
[[NSDistributedNotificationCenter defaultCenter] addObserver: self selector: #selector(themeChange:) name:#"AppleInterfaceThemeChangedNotification" object: NULL];
//
-(void) themeChange :(NSNotification *) notification
{
NSLog (#"%#", notification);
}
I am trying to add a UIButton to the view of a MPMoviePlayerController along with the standard controls. The button appears over the video and works as expected receiving touch events, but I would like to have it fade in and out with the standard controls in response to user touches.
I know I could accomplish this by rolling my own custom player controls, but it seems silly since I am just trying to add one button.
EDIT
If you recursively traverse the view hierarchy of the MPMoviePlayerController's view eventually you will come to a view class called MPInlineVideoOverlay. You can add any additional controls easily to this view to achieve the auto fade in/out behavior.
There are a few gotchas though, it can sometimes take awhile (up to a second in my experience) after you have created the MPMoviePlayerController and added it to a view before it has initialized fully and created it's MPInlineVideoOverlay layer. Because of this I had to create an instance variable called controlView in the code below because sometimes it doesn't exist when this code runs. This is why I have the last bit of code where the function calls itself again in 0.1 seconds if it isn't found. I couldn't notice any delay in the button appearing on my interface despite this delay.
-(void)setupAdditionalControls {
//Call after you have initialized your MPMoviePlayerController (probably viewDidLoad)
controlView = nil;
[self recursiveViewTraversal:movie.view counter:0];
//check to see if we found it, if we didn't we need to do it again in 0.1 seconds
if(controlView) {
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[controlView addSubview:backButton];
} else {
[self performSelector:#selector(setupAdditionalControls) withObject:nil afterDelay:0.1];
}
}
-(void)recursiveViewTraversal:(UIView*)view counter:(int)counter {
NSLog(#"Depth %d - %#", counter, view); //For debug
if([view isKindOfClass:NSClassFromString(#"MPInlineVideoOverlay")]) {
//Add any additional controls you want to have fade with the standard controls here
controlView = view;
} else {
for(UIView *child in [view subviews]) {
[self recursiveViewTraversal:child counter:counter+1];
}
}
}
It isn't the best solution, but I am posting it in case someone else is trying to do the same thing. If Apple was to change the view structure or class names internal to the control overlay it would break. I am also assuming you aren't playing the video full screen (although you can play it fullscreen with embeded controls). I also had to disable the fullscreen button using the technique described here because the MPInlineVideoOverlay view gets removed and released when it is pressed: iPad MPMoviePlayerController - Disable Fullscreen
Calling setupAdditionalControls when you receive the fullscreen notifications described above will re-add your additional controls to the UI.
Would love a more elegant solution if anyone can suggest something other than this hackery I have come up with.
My solution to the same problem was:
Add the button as a child of the MPMoviePlayerController's view;
fade the button in and out using animation of its alpha property, with the proper durations;
handle the player controller's touchesBegan, and use that to toggle the button's visibility (using its alpha);
use a timer to determine when to hide the button again.
By trial-and-error, I determined that the durations that matched the (current) iOS ones are:
fade in: 0.1s
fade out: 0.2s
duration on screen: 5.0s (extend that each time the view is touched)
Of course this is still fragile; if the built-in delays change, mine will look wrong, but the code will still run.
I'd like to have my NSSegmentedControl with a segment selected when enabled and with no segment selected while disabled (the kind of behavior that the view NSSegmentedControl in iTunes has).
Here some images:
enabled and selected
disabled correctly
disabled but not correctly
(*) I recognize that I could write a function to call whenever the BOOL property changes and in this function I could set all the segments desected or select the appropriate one, BUT I'd like to know if there's a way to accomplish this through Cocoa Bindings or Interface Builder.
UPDATE: added some images of the problem
EDIT: I am not completely sure about this, but i think 'No Selection Placeholder' is your best bet. http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CocoaBindingsRef/Concepts/BindingsOptions.html%23//apple_ref/doc/uid/20002304-187525
I still think you would have to programmatically specify no selection when you conditionally disable the control though.
The programmatic solution can be something like this:
- (void)setSegmentEnabled:(BOOL)enabled{
if (enabled)
{
int vState = [[NSUserDefaults standardUserDefaults] integerForKey:#"SelectedSegmentView"];
[viewSegment setSelectedSegment:vState];
segmentEnabled = YES;
}
else
{
[viewSegment setSelected:NO forSegment:0];
[viewSegment setSelected:NO forSegment:1];
[viewSegment setSelected:NO forSegment:2];
segmentEnabled = NO;
}
}
I'm just implementing my own setter for the BOOL property segmentEnabled which is being binded with the viewSegment
Sorry to be a nuisance, but I have yet ANOTHER question. How would I do something like DeskLock from macrabbit's Deskshade app? I've made the little window and that's as far as I've come. I know how to "lock" the screen in 10.6 with PresentationOptions, but I don't want to risk it because last time it wouldn't let me back in ;]
EDIT: The DeskShade app actually is meant to cover your desktop, hiding all icons. It also allows you to randomize wallpaper patterns with several fade/swipes. There is one extra feature called DeskLock that actually presents a translucent black bevel (similar to AppSwitcher build into Mac) with a lock icon, and you can place personal text. When you click the lock icon, it presents a modal that asks for a password you can set. You can also just type this password without pressing anything, followed by the Enter key, and it unlocks the screen. This uses the DeskShade feature of hiding the desktop as well.
Thanks!
To create the overlay window you have to subclass NSWindow and set its style mask and background color:
#implementation BigTransparentWindow
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
backing:(NSBackingStoreType)bufferingType
defer:(BOOL)deferCreation
{
self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask //this makes the window transparent
backing:bufferingType
defer:deferCreation];
if(self)
{
[self setOpaque:NO];
[self setHasShadow:NO];
[self setBackgroundColor:[[NSColor blackColor] colorWithAlphaComponent:0.5]];
}
return self;
}
#end
You then need to set the window's frame so that it covers all screens, and you need to set its window level appropriately:
- (IBAction)showWindow:(id)sender
{
//set the window so it covers all available screens
NSRect screensRect = NSZeroRect;
for(NSScreen* screen in [NSScreen screens])
{
screensRect = NSUnionRect(screensRect,[screen frame]);
}
[yourWindow setFrame:screensRect display:YES];
if(coverScreen)
{
//set the window so it is above all other windows
[yourWindow setLevel:kCGMaximumWindowLevel];
}
else
{
//set the window so it sits just above the desktop icons
[yourWindow setLevel:kCGDesktopIconWindowLevel + 1];
}
}
As you've mentioned, you can use the NSApplicationPresentationOptions settings for NSApp to control how the user can interact with the system. An easy way to test this without locking yourself out is to set an NSTimer that calls a method that pulls the app out of kiosk mode after a timeout period.