Change heaviness of NSWindow drop shadow? - macos

I have an NSWindow that has a drop shadow, but it is way to dark. The shadow spreads too far and is too heavy for me. It's the default shadow for the NSWindow and I haven't edited it at all.
What I want to know is if there is a way to shorten the blur radius or lower the heaviness of the drop shadow so it appears a bit more subtle.
Thanks!

There's no public API, but you can do It by swizzling some methods on NSThemeFrame (this is the view class responsible for the window's frame, border, etc).
Here's an example ( a subclass of NSWindow):
#import <objc/runtime.h>
#interface SOWindow : NSWindow
#end
#interface SOWindowThemeFrameOverrides : NSView
#end
#implementation SOWindow
+ (void)load
{
NSArray *methodsToOverride = #[#"_shadowOffset", #"_shadowFlags", #"_shadowType"];
for (NSString *selector in methodsToOverride) {
Method m = class_getInstanceMethod(NSClassFromString(#"NSThemeFrame"), NSSelectorFromString(selector));
Method m2 = class_getInstanceMethod([SOWindowThemeFrameOverrides class], NSSelectorFromString(selector));
class_addMethod(NSClassFromString(#"NSThemeFrame"), NSSelectorFromString([NSString stringWithFormat:#"_original%#", selector]), method_getImplementation(m), method_getTypeEncoding(m));
method_exchangeImplementations(m, m2);
}
}
#end
#implementation SOWindowThemeFrameOverrides
- (NSSize)_shadowOffset
{
if ([self.window isKindOfClass:[SOWindow class]]) {
return NSMakeSize(0, 8);
} else {
return [self _original_shadowOffset];
}
}
- (NSUInteger)_shadowFlags
{
if ([self.window isKindOfClass:[SOWindow class]]) {
return 0;
} else {
return [self _original_shadowFlags];
}
}
- (NSInteger)_shadowType
{
if ([self.window isKindOfClass:[SOWindow class]]) {
return 4;
} else {
return [self _original_shadowType];
}
}
#pragma mark Placeholder methods
- (NSSize)_original_shadowOffset
{
// implementation will be filled in at runtime
return NSZeroSize;
}
- (NSUInteger)_original_shadowFlags
{
// implementation will be filled in at runtime
return 0;
}
- (NSInteger)_original_shadowType
{
// implementation will be filled in at runtime
return 0;
}
#end
When the SOWindow class is loaded by the runtime, the + load method is invoked. The method switches NSThemeFrame's implementation of the 3 shadow methods by their implementation in SOWindowThemeFrameOverrides, also adding the original methods to the class with the _original prefix.
When the swizzled methods are called, we check to see if the window is a SOWindow, if It is we use the custom shadow, if It's not we forward the call to the original implementations.
This is what I get by returning 4 from _shadowType:
Please note that this is a huge hack and would probably be rejected if you tried to submit It to the AppStore.

Related

The right way to make a continuously redrawn Metal NSView

I'm learning Metal and Cocoa and trying to make a boilerplate application as a platform for future experiments. As part of the process, I'm implementing a view which will redraw itself (or, more accurately, contents of its CAMetalLayer) on 60fps. Also for educational purposes Im avoiding MTKView (for "learning Cocoa part"). Here's an abbreviated code snippet of how I'm tackling the problem:
#implementation MyMetalView // which is a subclass of NSView
- (BOOL) isOpaque {
return YES;
}
- (NSViewLayerContentsRedrawPolicy) layerContentsRedrawPolicy {
return NSViewLayerContentsRedrawOnSetNeedsDisplay;
}
- (CALayer *) makeBackingLayer {
// create CAMetalLayer with default device
}
- (BOOL) wantsLayer {
return YES;
}
- (BOOL) wantsUpdateLayer {
return YES;
}
- (void) displayLayer:(CALayer *)layer {
id<MTLCommandBuffer> cmdBuffer = [_commandQueue commandBuffer];
id<CAMetalDrawable> drawable = [((CAMetalLayer *) layer) nextDrawable];
[cmdBuffer enqueue];
[cmdBuffer presentDrawable:drawable];
// rendering
[cmdBuffer commit];
}
#end
int main() {
// init app, window and MyMetalView instance
// invocation will call [myMetalViewInstance setNeedsDisplay:YES]
[NSTimer scheduledTimerWithTimeInterval:1./60. invocation:setNeedsDisplayInvokation repeats:YES];
[NSApp run];
return 0;
}
Is it the right way to do what I want? Or have I chosen a long and not recommended approach?
It is strongly preferred to use CVDisplayLink rather than a generic NSTimer to drive animations that need to match the refresh rate of the display.
You'll want to create an ivar or property to hold a CVDisplayLinkRef:
CVDisplayLinkRef displayLink;
Then, when your view is going onto the screen and you want to start animating, you'll create, configure, and start your display link:
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, self);
CVDisplayLinkStart(displayLink);
The display link callback should be a static function. It will be invoked at the beginning of the display's v-blank period (on modern displays where there is no physical v-blank, this still happens at a regular 60Hz cadence):
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
[(MyMetalView *)displayLinkContext setNeedsDisplay:YES];
return kCVReturnSuccess;
}
When your view leaves the display, or if you want to pause, you can release the display link and nil it out:
CVDisplayLinkRelease(displayLink);
following #warrenm solution adding dispatch_sync to refresh and other minor :
#import "imageDrawer.h"
#import "image/ImageBuffer.h"
#import "common.hpp"
#implementation imageDrawer {
CVDisplayLinkRef displayLink;
}
CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
dispatch_sync(dispatch_get_main_queue(), ^{
[(__bridge imageDrawer*)displayLinkContext setNeedsDisplay:YES];
});
return kCVReturnSuccess;
}
-(void)setContDisplay {
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, (__bridge void*)self);
CVDisplayLinkStart(displayLink);
}
-(void)awakeFromNib {
[self setContDisplay];
}
- (void)drawRect:(NSRect)rect {
[super drawRect:rect];
int w=rect.size.width, h=rect.size.height;
// do the drawing...
}
#end

Bindings working from IB, not from my addObserver...

My Document-based app was created without storyboards in Xcode 6.3 so it began life without a window controller (I still don't have a window controller -- just trying to give some background and context).
I have a class structure implemented for working with a gradient and storing it's formative values in my document.
My Document class holds a reference to a Theme object.
My Theme class holds a reference to a Gradient object.
My Gradient class holds a reference to an NSNumber for the start point of the gradient.
In IB an NSSlider is bound to File's Owner, with Model Key Path of "self.theme.gradient.startPointX"
This works fine, as evidenced by Gradient.m -didChangeValueForKey logging out the specific key whose value is being changed.
So why doesn't a similar notification occur in my Document class when the slider for the gradient start point is changed after I have asked to observe it?
Document.m
- (instanceType)init {
self = [super init];
if (self) {
self.theme = [[MyTheme alloc] init];
// first attempt -- not live when second attempt is compiling
[self addObserver:self
forKeyPath:#"theme.gradient.startPointX"
options:NSKeyValueObservingOptionNew
context:#"myDocument"];
// second attempt -- not live when the first attempt is compiling
[self.theme.gradient addObserver:self
forKeyPath:#"startPointX"
options:NSKeyValueObservingOptionNew
context:#"myDocument"];
}
return self;
}
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(#"Document notified that \"%#\" has changed.", key);
}
-
Theme.m
- (instancetype)init
{
if (self = [super init]) {
self.gradient = [[Gradient alloc] init];
}
return self;
}
-
Gradient.h
#interface Gradient : NSObject
#property (strong, nonatomic) NSNumber *startPointX;
#end
-
Gradient.m
- (instancetype)init
{
if (self = [super init]) {
self.startPointX = #(0.47f);
}
return self;
}
- (void)didChangeValueForKey:(NSString *)key
{
if ([key isEqualToString:#"startPointX"]) {
NSLog(#"Gradient notified a change to %# has occurred.", key);
}
It turns out that if you implement -didChangeValueForKey: it blocks/suspends normal notification of those properties you might be observing.
Commenting out my Gradient.m implementation of
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(#"'%#' has been changed.", key);
}
caused my observations from Document to begin working fine.

NSCollectionView not updating when adding objects

I have an NSCollectionView with an array controller that is successfully showing objects after they are added and the application is restarted, but not when immediately when added.
I have a controller class which is a subclass of NSObject that reads data from a plist into an NSMutableArray, which is bound to an Array Controller, which is in turn bound to my NSCollectionView. I believe my bindings are correct, as if I add an object, after I restart my application, everything shows up fine, including the new object and all the bound attributes. But when I add an object, it won't be added immediately. The application needs to be restarted. I believe that since my bindings appear to be correct, this is an issue with my controller class not being Key-Value compliant.
I have implemented all of the methods I believe I should have, as per the "Key-Value Coding Accessor Methods" section of the Key-Value Coding programming guide. I believe I have implemented each of the required accessors in the [Collection Accessor Patterns for To-Many Properties][1] section. Furthermore, in the [Quick Start for Collection Views][2], which I have completed, it states not even all of these methods need to be implemented (which I have confirmed).
Here are some code samples to better explain what I am doing.
My collection class, "MYCollection":
#import <Foundation/Foundation.h>
#import "MyObject.h"
#interface MYCollection : NSObject
#property (retain, readwrite) NSMutableArray* objects;
- (void)insertObject:(MYObject *)object inObjectsAtIndex:(NSUInteger)index;
#end
#import "MYObjectCollection.h"
#import "MYObject.h"
#implementation MYObjectCollection
#synthesize objects = _objects;
- (id)init {
self = [super init];
if (self) {
_objects = [self objects];
}
return self;
}
- (NSArray*)objects {
// here I retrieve the objects from the plist into a mutable array
// let's call that array "sortedArray"
return sortedArray;
}
- (void)setObjects:(NSMutableArray *)objectsArray {
// here I write the object array to a plist
_objects = objectsArray;
}
-(void)insertObject:(MYObject*)object inObjectsAtIndex:(NSUInteger)index {
[_objects insertObject:object atIndex:index];
[self setObjects:_objects];
return;
}
-(void)addObjectsObject:(MYObject*)object {
[_objects addObject:object];
[self setObjects:_objects];
return;
}
-(void)removeObjectFromObjectsAtIndex:(NSUInteger)index {
[_objects removeObjectAtIndex:index];
[self setObjects:_objects];
return;
}
-(void)removeObjectsObject:(MYObject*)object {
[_objects removeObject:object];
[self setObjects:_objects];
return;
}
-(id)objectInObjectsAtIndex:(NSUInteger)index {
return [_objects objectAtIndex:index];
}
-(NSUInteger)countOfObjects {
return [_objects count];
}
- (NSEnumerator *)enumeratorOfObjects {
return [_objects objectEnumerator];
}
#end
I am adding objects to this controller by means of an external view, elsewhere:
MYObjectCollection *collection = [[MYObjectCollection alloc] init];
[collection insertObject:new inObjectsAtIndex:[collection.objects count]];
I'm not sure how to continue troubleshooting this issue. I believe that my bindings are correct and I think I have implemented all of the necessary methods for Key-Value coding, but maybe I haven't, or maybe they're wrong. Any help would be appreciated.

Why do I get different results when I run a program from testing trough Xcode and just taping the icon on the device?

I am having a really weird problem because i get completely different results between testing my program WHILE connected to the computer (trough xcode) but ON my device. and just taping the icon while not being plugged to xcode. (I think it might be coordinate issues).
So i was thinking there might be a difference between testing in these 2 ways.
Sorry i forgot to specify, I used to get the same results in both ways but then i created a singleton for my location manager instead of creating a single location manager object in each window.
This is how i am creating the Header:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
// protocol for sending location updates to another view controller
#protocol LocationManagerDelegate <NSObject>
#required
- (void)locationUpdate:(CLLocation*)location;
#end
#interface LocationManagerSingleton : NSObject <CLLocationManagerDelegate> {
CLLocationManager* locationManager;
CLLocation* location;
//id delegate;
}
#property (nonatomic, retain) CLLocationManager* locationManager;
#property (nonatomic, retain) CLLocation* location;
#property (nonatomic, assign) id <LocationManagerDelegate> delegate;
+ (LocationManagerSingleton*) sharedInstance; // Singleton method
#end
and this is the implementation:
#import "LocationManagerSingleton.h"
//static LocationManagerSingleton* sharedCLDelegate = nil;
#implementation LocationManagerSingleton
#synthesize locationManager, location, delegate;
#pragma mark - Singleton Methods -
+ (LocationManagerSingleton*)sharedInstance {
static LocationManagerSingleton *_sharedInstance;
if(!_sharedInstance) {
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[super allocWithZone:nil] init];
});
}
return _sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
#if (!__has_feature(objc_arc))
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
#endif
#pragma mark - Custom Methods -
// Add your custom methods here
- (id)init
{
self = [super init];
if (self != nil) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.distanceFilter = 5;
self.locationManager.purpose = #"This app uses your location for Augmented Reality";
[self.locationManager startUpdatingLocation];
[self.locationManager startUpdatingHeading];
NSLog(#"LocationManager initialized with accuracy best for Navigation");
NSLog(#"CUrrent Latitude: %f, Current Longitude: %f",locationManager.location.coordinate.latitude,locationManager.location.coordinate.longitude);
}
return self;
}
#pragma mark - CLLocationManagerDelegate Methods -
- (void)locationManager:(CLLocationManager*)manager
didUpdateToLocation:(CLLocation*)newLocation
fromLocation:(CLLocation*)oldLocation
{
/*…some filer method to check if the new location is good …*/
bool good = YES;
if (good)
{
[self.delegate locationUpdate:newLocation];
}
//self.location = newLocation;
//NSLog(#"Updated: %#",newLocation);
}
- (void)locationManager:(CLLocationManager*)manager
didFailWithError:(NSError*)error
{
/* ... */
}
#end
Okay it seems that OpenGL was causing the problem. My theory was that since a pointer variable used for texturing was inside a loop when the localization manager updated and redraw this variable would get messed up because it was being reinitialized everyrun but the value wouldnt be set to 0, and since opengl has the pointer to the adress not to the pointer it would read corrupted data (since the loop might have updated that space until opengl was told the new adress of the variable). Still i have no idea why it worked perfectly while hooked up to the computer and not by itself.

NSSlider animation

How can I create NSSlider animation when changing float value of it. I was trying:
[[mySlider animator] setFloatValue:-5];
but that didn't work.. just change the value without animation. So maybe someone knows how to do this?
Thanks in advance.
Ok - so this isn't as quick and pretty as I hoped but it works.
You can't actually use animators and Core Animation on the slider knob - because Core Animation works only on layers and there's no access to the knob values in the slider layer.
So we have to resort instead to manually animating slider value.
Since we're doing this on a Mac - you can use NSAnimation (which isn't available on iOS).
What NSAnimation does is simple - it provide an timing/interpolation mechanism to allow YOU to animate (as opposed to Core Animation which also connects to the views and handles the changes to them).
To use NSAnimation - you most commonly would subclass it and override setCurrentProgress:
and put your logic in there.
Here's how I implemented this - I created a new NSAnimation subclass called NSAnimationForSlider
NSAnimationForSlider.h :
#interface NSAnimationForSlider : NSAnimation
{
NSSlider *delegateSlider;
float animateToValue;
double max;
double min;
float initValue;
}
#property (nonatomic, retain) NSSlider *delegateSlider;
#property (nonatomic, assign) float animateToValue;
#end
NSAnimationForSlider.m :
#import "NSAnimationForSlider.h"
#implementation NSAnimationForSlider
#synthesize delegateSlider;
#synthesize animateToValue;
-(void)dealloc
{
[delegateSlider release], delegateSlider = nil;
}
-(void)startAnimation
{
//Setup initial values for every animation
initValue = [delegateSlider floatValue];
if (animateToValue >= initValue) {
min = initValue;
max = animateToValue;
} else {
min = animateToValue;
max = initValue;
}
[super startAnimation];
}
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
[super setCurrentProgress:progress];
double newValue;
if (animateToValue >= initValue) {
newValue = min + (max - min) * progress;
} else {
newValue = max - (max - min) * progress;
}
[delegateSlider setDoubleValue:newValue];
}
#end
To use it - you simply create a new NSAnimationForSlider, give it the slider you are working on as a delegate and before each animation you set it's animateToValue and then just start the animation.
For example:
slider = [[NSSlider alloc] initWithFrame:NSMakeRect(50, 150, 400, 25)];
[slider setMaxValue:200];
[slider setMinValue:50];
[slider setDoubleValue:50];
[[window contentView] addSubview:slider];
NSAnimationForSlider *sliderAnimation = [[NSAnimationForSlider alloc] initWithDuration:2.0 animationCurve:NSAnimationEaseIn];
[sliderAnimation setAnimationBlockingMode:NSAnimationNonblocking];
[sliderAnimation setDelegateSlider:slider];
[sliderAnimation setAnimateToValue:150];
[sliderAnimation startAnimation];
Your method works, but there's something much simpler.
You can use the animator proxy, you just need to tell it how to animate it.
To do this, you need to implement the defaultAnimationForKey: method from the NSAnimatablePropertyContainer protocol.
Here's a simple subclass of NSSlider that does this:
#import "NSAnimatableSlider.h"
#import <QuartzCore/QuartzCore.h>
#implementation NSAnimatableSlider
+ (id)defaultAnimationForKey:(NSString *)key
{
if ([key isEqualToString:#"doubleValue"]) {
return [CABasicAnimation animation];
} else {
return [super defaultAnimationForKey:key];
}
}
#end
Now you can simply use the animator proxy:
[self.slider.animator setDoubleValue:100.0];
Make sure to link the QuartzCore framework.
Here is a Swift version of IluTov answer, setting a floatValue with some animation config:
override class func defaultAnimation(forKey key: NSAnimatablePropertyKey) -> Any? {
if key == "floatValue" {
let animation = CABasicAnimation()
animation.timingFunction = .init(name: .easeInEaseOut)
animation.duration = 0.2
return animation
} else {
return super.defaultAnimation(forKey: key)
}
}

Resources