Cocoa ScreenSaverView - objective-c

I am trying to play with ScreenSaverView in mac OS X.
I followed this tutorial http://cocoadevcentral.com/articles/000088.php
and it worked (can't say flawlessly ,but worked).
I also saw in part 2 of this tutorial they are playing with openGL stuff etc.
I have 2 general questions:
1)Can I use SprteKit within screensaver view?
2)What is wrong with my code here -> it compiles well, however I don't see anything except a black screen (I want to see my *.png).
- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self)
{
_imageView = [[NSImageView alloc]initWithFrame:[self bounds]];
[_imageView setImage:[NSImage imageNamed:#"SettingsButton.png"]];
[self addSubview:_imageView];
[self setAnimationTimeInterval:1.0];
}
return self;
}
EDIT: Attempt to use a draw rect:
- (void)drawRect:(NSRect)rect0
{
[super drawRect:rect0];
NSImage *anotherImage = [NSImage imageNamed:#"SettingsButton.png"];
[anotherImage drawAtPoint:NSMakePoint(10,100) fromRect:NSMakeRect(0,0,[anotherImage size].width,[anotherImage size].height) operation:NSCompositeCopy fraction:1.0];
// Calculate a random color
CGFloat red = SSRandomFloatBetween( 0.0, 255.0 ) / 255.0;
CGFloat green = SSRandomFloatBetween( 0.0, 255.0 ) / 255.0;
CGFloat blue = SSRandomFloatBetween( 0.0, 255.0 ) / 255.0;
NSColor* color = [NSColor colorWithCalibratedRed:red
green:green
blue:blue
alpha:1];
NSColor * color2 = [NSColor colorWithPatternImage:anotherImage];
[color2 set];
NSBezierPath * path = [NSBezierPath bezierPathWithRect:rect0];
[path fill];
}
When I set a color it works , I can see a screen filled by Random colors (fine as meant)
When I use color2 which is a pattern from the image
nothing is works :-( -> I tried different images
same nothing there...
I checked in build phase that I do copy the images as a bundle resources
What could be the problem ?
EDIT: Ok so after my attempt in drawRect I suspected that imageNamed method is causing a troubles and rewrite my origin attempt to:
- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self)
{
_imageView = [[NSImageView alloc]initWithFrame:[self bounds]];
NSBundle * tempBundle = [NSBundle bundleForClass:[self class]];
// Load your image, "top.tiff". Don't forget to release it when you are done (not shown).
NSImage * theImage = [[NSImage alloc] initWithContentsOfFile:
[tempBundle pathForImageResource:#"personal1.jpg"]];
[_imageView setImage:theImage];
[self addSubview:_imageView];
[self setAnimationTimeInterval:1.0];
}
return self;
}
And Whoaallaaa it worked !!(Kinda) I see my picture :-) !!!
As for part1 of the Question -> Yes it is possible
Just tried and it works !
with a little down side I can exit ScreenSaver only with cmd key xD
Managed to solve this issue,by subclassing SKView and delivering event to the next responder.
However it gave me a huge idea -> actually it just opens an opportunity to make a simple SK game as part of the screensaver could have been a cute feature.
Sadly I can't submit it to app store :)

It's not clear to me if a SpriteKit view (SKView) can even be used in a normal OSX/iOS app. I have searched in the past and found nothing.
If SpriteKit is anything like other game frameworks (from which it borrows much of its structure) then it will use a traditional game loop; i.e. clear screen, draw everything, wait a bit and then repeat.
Cocoa apps use an runloop that reacts to events and take steps to only redraw what needs to be redrawn.
So I would say "No" to your first question.
As far as your second, code-related, question is concerned, then I cannot see much wrong with it, however I am not familiar with that init method; what class does it subclass?

Related

Cocoa: How to draw inset text as in Mail.app?

How can I draw this style of text in Cocoa (OS X)? It seems to be used in several Apple apps including Mail (as pictured above) and several places in Xcode sidebars. I've looked around but haven't been able to find any resources suggesting how to reproduce this specific style of text. It looks like an inset shadow and my first guess was to try using an NSShadow with the blur radius set to a negative value but apparently only positive values are allowed. Any ideas?
I have some code that draws an embossed cell (originally written Jonathon Mah, I believe). It might not do exactly what you want but it'll give you a place to start:
#implementation DMEmbossedTextFieldCell
#pragma mark NSCell
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
{
/* This method copies the three-layer method used by Safari's error page. That's accessible by forcing an
* error (e.g. visiting <foo://>) and opening the web inspector. */
// I tried to use NSBaselineOffsetAttributeName instead of shifting the frame, but that didn't seem to work
const NSRect onePixelUpFrame = NSOffsetRect(cellFrame, 0.0, [NSGraphicsContext currentContext].isFlipped ? -1.0 : 1.0);
const NSRange fullRange = NSMakeRange(0, self.attributedStringValue.length);
NSMutableAttributedString *scratchString = [self.attributedStringValue mutableCopy];
BOOL overDark = (self.backgroundStyle == NSBackgroundStyleDark);
CGFloat (^whenLight)(CGFloat) = ^(CGFloat b) { return overDark ? 1.0 - b : b; };
// Layer 1
[scratchString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithCalibratedWhite:whenLight(1.0) alpha:1.0] range:fullRange];
[scratchString drawInRect:cellFrame];
// Layer 2
BOOL useGradient = NO; // Safari 5.2 preview has switched to a lighter, solid color look for the detail text. Since we use the same class, use bold-ness to decide
if (self.attributedStringValue.length > 0) {
NSFont *font = [self.attributedStringValue attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL];
if ([[NSFontManager sharedFontManager] traitsOfFont:font] & NSBoldFontMask)
useGradient = YES;
}
NSColor *solidShade = [NSColor colorWithCalibratedHue:200/360.0 saturation:0.03 brightness:whenLight(0.41) alpha:1.0];
[scratchString addAttribute:NSForegroundColorAttributeName value:solidShade range:fullRange];
[scratchString drawInRect:onePixelUpFrame];
// Layer 3 (Safari uses alpha of 0.25)
[scratchString addAttribute:NSForegroundColorAttributeName value:[NSColor colorWithCalibratedWhite:whenLight(1.0) alpha:0.25] range:fullRange];
[scratchString drawInRect:cellFrame];
}
#end
Please don't pick this as the answer I just implemented the above suggestions for fun and put it here because it will probably be useful to someone in the future!
https://github.com/danieljfarrell/InnerShadowTextFieldCell
Following from the advice of indragie and wil-shipley here is a subclass of NSTextFieldCell that draws the text with an inner shadow.
The header file,
// InnerShadowTextFieldCell.h
#import <Cocoa/Cocoa.h>
#interface InnerShadowTextFieldCell : NSTextFieldCell
#property (strong) NSShadow *innerShadow;
#end
Now the implementation file,
// InnerShadowTextFieldCell.m
#import "InnerShadowTextFieldCell.h"
// This class needs the NSString category -bezierWithFont: from,
// https://developer.apple.com/library/mac/samplecode/SpeedometerView/Listings/SpeedyCategories_m.html
#implementation InnerShadowTextFieldCell
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
//// Shadow Declarations
if (_innerShadow == nil) {
/* Inner shadow has not been set, override here with default shadow.
You may or may not want this behaviour. */
_innerShadow = [[NSShadow alloc] init];
[_innerShadow setShadowColor: [NSColor darkGrayColor]];
[_innerShadow setShadowOffset: NSMakeSize(0.1, 0.1)];
/* Trying to find a default shadow radius which will look good for
a label of any size, let's get a rough estimate based on the
hypotenuse of the cell frame. */
[_innerShadow setShadowBlurRadius: 0.0075 * hypot(NSWidth(cellFrame), NSHeight(cellFrame)) ];
}
/* Because we are using the -bezierWithFont: we get a slightly
different path than had we used the superclass to drawn the
text path. This means that the background colour and text
colour looks odd if we use call the superclass's,
-drawInteriorWithFrame:inView: method let's do that
drawing here. Not making the call to super might cause
problems for general use (?) but for a simple label is seems
to work OK */
[self.backgroundColor setFill];
NSRectFill(cellFrame);
NSBezierPath *bezierPath = [self.title bezierWithFont:self.font];
[self.textColor setFill];
[bezierPath fill];
/* The following is inner shadow drawing method is taken from Paint Code */
////// Bezier Inner Shadow
NSShadow *shadow = _innerShadow;
NSRect bezierBorderRect = NSInsetRect([bezierPath bounds], -shadow.shadowBlurRadius, -shadow.shadowBlurRadius);
bezierBorderRect = NSOffsetRect(bezierBorderRect, -shadow.shadowOffset.width, shadow.shadowOffset.height);
bezierBorderRect = NSInsetRect(NSUnionRect(bezierBorderRect, [bezierPath bounds]), -1, -1);
NSBezierPath* bezierNegativePath = [NSBezierPath bezierPathWithRect: bezierBorderRect];
[bezierNegativePath appendBezierPath: bezierPath];
[bezierNegativePath setWindingRule: NSEvenOddWindingRule];
[NSGraphicsContext saveGraphicsState];
{
NSShadow* shadowWithOffset = [shadow copy];
CGFloat xOffset = shadowWithOffset.shadowOffset.width + round(bezierBorderRect.size.width);
CGFloat yOffset = shadowWithOffset.shadowOffset.height;
shadowWithOffset.shadowOffset = NSMakeSize(xOffset + copysign(0.0, xOffset), yOffset + copysign(0.1, yOffset));
[shadowWithOffset set];
[[NSColor grayColor] setFill];
[bezierPath addClip];
NSAffineTransform* transform = [NSAffineTransform transform];
[transform translateXBy: -round(bezierBorderRect.size.width) yBy: 0];
[[transform transformBezierPath: bezierNegativePath] fill];
}
[NSGraphicsContext restoreGraphicsState];
}
#end
This could probably be made more robust but it seems fine for just drawing static labels.
Make sure your change the text color and text background color properties in Interface Builder otherwise you will not be able to see the shadow.
From your screenshot, that looks like text drawn with an inner shadow. Hence, the standard NSShadow method of using a blur radius of 0 won't work because that only draws the shadow under/above the text.
There are two steps to drawing text with an inner shadow.
1. Get the drawing path of the text
To be able to draw a shadow inside the text glyphs, you need to create a bezier path from the string. The Apple sample code SpeedometerView has a category that adds the method -bezierWithFont: to NSString. Run the project to see how this method is used.
2. Fill the path with an inner shadow
Drawing shadows under bezier paths is easy, but drawing a shadow inside one is not trivial. Fortunately, the NSBezierPath+MCAdditions category adds the -[NSBezierPath fillWithInnerShadow:] method to make this easy.

NSComboBox - problems with background when selected

I'm a iOS developer, and recently I'm programming a desktop APP for MAC OSX. I still don't have much experience with the View's components of OSX, so maybe it's a silly or easy question, but I have made a little research about this problem and haven't found any solution yet.
Here's the problem:
I have a custom specialization of a NSView, that is used as the view of a Content ViewController used in my NSPopover.
Inside this view, that I'm calling "PopoverBackgroundView", I painted inside the drawRect this red background, and calculated another minor rect and painted with this gray-like color. Here's the code:
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor colorWithDeviceRed:174/255.0 green:72/255.0 blue:72/255.0 alpha:1.0] setFill];
NSRectFill(dirtyRect);
[[NSColor colorWithDeviceRed:51/255.0 green:51/255.0 blue:51/255.0 alpha:1.0] setFill];
NSRectFill(NSMakeRect(BORDER_WIDTH, BORDER_WIDTH, dirtyRect.size.width - 2*BORDER_WIDTH, dirtyRect.size.height - 2*BORDER_WIDTH));
}
So, inside the PopoverBackgroundView.m I'm programatically creating a NSComboBox. This comboBox will have the numbers 1 to 10. When I allocate it, everything seems just fine:
The problem is, after I select any options inside the combobox, it's background somehow "goes away" became transparent, I don't know, and become like this:
Please notice the red-like frame (background color of the view) around the NSComboBox, that appeared just AFTER I select something.
Here's the code where I'm allocation the comboBox and initializing it:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
(...)
self.comboBox = [[NSComboBox alloc] initWithFrame:CGRectMake(15, frame.size.height - 55, 90, 25)];
self.comboBox.delegate = self;
[self.comboBox setDrawsBackground:NO];
[self.comboBox setSelectable:YES];
[self.comboBox setEditable:NO];
for (int i = 1; i<=10; i++)
{
NSString *mystr = [NSString stringWithFormat:#"%d", i];
[self.comboBox addItemWithObjectValue:mystr];
}
[self addSubview:self.comboBox];
}
return self;
}
Any idea how can I 'fix' this "selected background"? All that I want it's the selected state to be equals to the normal state, i. e. ,the comboBox should be always like the first image, even after the selection.
Is there something wrong with the allocation code? Something mission? I'm really thinking that just some property that I'm not using or initializing, but I couldn't find yet.
Thanks in advance,
Just to give a feedback, I finally was able to resolve my problem.
I don't know exactly why, but the problem was all caused because the way I was drawing my border in the drawRect. Somehow, these code
[[NSColor colorWithDeviceRed:174/255.0 green:72/255.0 blue:72/255.0 alpha:1.0] setFill];
NSRectFill(dirtyRect);
were been propagated by the subviews, don't know if was the setFill or the NSRectFill. So, the "background" of the NSCombobox was been painted with this color.
After seeing this post :Adding border and Rounded Rect in the NSView I changed my draw rect to this:
- (void)drawRect:(NSRect)dirtyRect
{
NSBezierPath *background = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:10 yRadius:10];
[[NSColor colorWithDeviceRed:174/255.0 green:72/255.0 blue:72/255.0 alpha:1.0] set];
[background setLineWidth:10];
[background stroke];
}
and everything is working fine now, as I wanted. Now, after I select my combobox, no strange background is been drawing.
If someone knows whey this was happening with the previous code, please, let me know.

How to make NSView not clip its bounding area?

I created an empty Cocoa app on Xcode for OS X, and added:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 200, 200)];
self.view.wantsLayer = YES;
self.view.layer = [CALayer layer];
self.view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
self.view.layer.transform = CATransform3DMakeRotation(30 * M_PI / 180, 1, 1, 1);
[self.window.contentView addSubview:self.view];
}
But the rotated layer's background is clipped by the view's bounding area:
I thought since some version of OS X and iOS, the view won't clip the content of its subviews and will show everything inside and outside? On iOS, I do see that behavior, but I wonder why it shows up like that and how to make everything show? (I am already using the most current Xcode 4.4.1 on Mountain Lion).
(note: if you try the code above, you will need to link to Quartz Core, and possibly import the quartz core header, although I wonder why I didn't import the header and it still compiles perfectly)
It turns out that if the line:
((NSView *)self.window.contentView).wantsLayer = YES;
is added to the very beginning, then it works as expected:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
((NSView *)self.window.contentView).wantsLayer = YES;
self.view = [[NSView alloc] initWithFrame:NSMakeRect(200, 200, 200, 200)];
self.view.wantsLayer = YES;
self.view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
[self.window.contentView addSubview:self.view];
self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
self.view.layer.transform = CATransform3DMakeRotation(30 * M_PI / 180, 0, 0, 1);
}
So it looks like if all the views are made to be layer backed, then it works the same as it does on iOS. (If there is a quick way to make all views layer backed automatically, that'd be good).
the anchorPoint line cannot be moved before addSubview line, or else it is incorrect, although I wonder why that would make any difference.
The line self.view.layer = [CALayer layer]; can be removed if window.contentView is layer backed. Both the contentView and self.view don't need to set the layer, and I wonder why too.
The transform line cannot be before the addSubview line, or else it won't rotate, and I wonder why too.
The third thing is that, I thought if I go to Interface Builder and make the contentView a class of ContentView (subclassing NSView), and in its init method, do a self.wantsLayer = YES;, then it would work too, but it does not.
But anyway, the code above works, and I will update the reasons above why when I find out more.

being called but not drawing

I have some code in the drawRect: method that draws a series of circles from an array. I can verify that the points stay in memory long enough and get to the point where they should be drawn. However, it seems as if the fill method of NSBezierPath is not drawing the circles.
Here is my drawRect:
- (void)drawRect:(NSRect)dirtyRect
{
NSPoint aPoint;
NSRect aRect = NSMakeRect(0, 0, 6, 6);
// Erase the rectangle
[[NSColor clearColor] set];
NSRectFill([self bounds]);
// Draw the dots
[[NSColor colorWithCalibratedRed:(77.0/255.0) green:(11.0/255.0) blue:(11.0/255.0) alpha:1.0] set];
for(NSValue *aValuePoint in arrayOfPoints) {
aPoint = [aValuePoint pointValue];
aRect.origin.y = aPoint.y - aRect.size.height/2.0;
aRect.origin.x = aPoint.x - aRect.size.width/2.0;
// The code does get to this point, it does not draw however...
[[NSBezierPath bezierPathWithOvalInRect:aRect] fill];
}
}
I have another method drawDotAtPoint: where I add the points to an array
- (void)drawDotAtPoint:(NSPoint)aPoint
{
NSLog(#"drawDotAtPoint:(%f, %f)", aPoint.x, aPoint.y);
[arrayOfPoints addObject:[NSValue valueWithPoint:aPoint]];
// I've also tried using [self superview] instead of just self
[self setNeedsDisplay: YES];
}
And while I am able to verify that these methods are called at the correct times (or so it seems) no drawing after the initial drawRect: will take place
Sidenote:
My goal is to actually draw a point and have a ring appear around it, enlarge and fade out. Much similar to the GPS current location indicator on iOS, in the Maps app. Any insight on that would also be appreciated.
Check your points and make sure that you don't have an inverted rectangle and that your X/Y coords are what you expect. Make sure that your anchor point and coordinates are correctly relative.
You may be helped along a bit by using a tool called Quartz Debug that comes with apple's developer tools. It is in the /Developer/Applications/Graphics Tools/ folder. After starting this tool, go to the "tools" menu and turn on "Flash Screen Updates". This will give you an idea of when things are being redrawn or not.
Hope that helps some.
I've copy-pasted your view code. I stuck this in an app delegate for testing. BadView is the custom view here (no reflection on you -- "Bad" because it's misbehaving :) ) The windows are both created in IB, and the WebView is likewise placed in the parent window in IB.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// badWindow is an IBOutlet to the overlay window, parentWindow an IBOutlet
// to the window with the webview, myWebView an IBOutlet to the webview
BadView * myBadView = [[BadView alloc] initWithFrame:[[badWindow contentView] frame]];
// Create some random points for testing
[myBadView createArrayOfPoints];
[badWindow setStyleMask:NSBorderlessWindowMask];
[badWindow setOpaque:NO];
[badWindow setAlphaValue:0.9]; // Will still act opaque without this
[badWindow setHasShadow:NO];
[badWindow setContentView:myBadView];
[parentWindow addChildWindow:badWindow ordered:NSWindowAbove];
[[myWebView mainFrame] loadRequest:[NSURLRequest requestWithURL:
[NSURL URLWithString:#"http://www.apple.com/"]]];
[myBadView release];
}

CALayer - clear internal rendering context in favor of custom drawing code

When subclassing an CALayer and implementing the drawInContext method, I would assume that any drawing I do within there is all that will show up, but instead if I set (for example) borderWidth/borderColor then CALayer will draw a border on it's own above all my custom drawing code.
This is a CALayer subclass:
#implementation MyCustomCALayer
- (id)init
{
self = [super init];
if(self)
{
[self setNeedsDisplayOnBoundsChange:YES];
}
return self;
}
- (void)drawInContext:(CGContextRef)context
{
CGRect rect = CGContextGetClipBoundingBox(context);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rect);
}
#end
Created in a UIView something like:
- (void)ensureLayer
{
if(myLayer)
return;
myLayer = [[[MyCustomCALayer alloc] init] autorelease];
myLayer.borderColor = [UIColor greenColor].CGColor;
myLayer.borderWidth = 1;
myLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
[super layoutSublayersOfLayer:layer];
[self ensureLayer];
if(![[layer.sublayers objectAtIndex:0] isEqual:myLayer])
[layer insertSublayer:myLayer atIndex:0];
}
What happens, is the MyCustomCALayer fills a rectangle with red, this is what I would expect to see and nothing else, since i've implemented the drawInContext method, but instead I see a red rectangle with a green border on top, always on top, i've tried just about every combination I can think of to get rid of the green border being drawn and cannot figure it out.
My reasoning is I would like to use the borderWidth and borderColor and other properties of the CALayer instead of creating my own properties, because the code that I need to draw contains a border, a fill, etc... but the rendering I need to do is not a simple shape. So the only way i've found around this is to set the borderWidth to 0 and add my own property to my subclass, like myBorderWidth, which is ridiculous.
This is done with the latest iOS SDK, but i'd imagine it's the same for Mac.
Hope this makes sense, any thoughts?
Thanks!
You’re out of luck; CoreAnimation doesn’t support overriding its implementation of rendering for the basic layer properties. Please do file a bug.