NSRect on main screen without window - objective-c

I was wondering if it is possible to create an NSRect with maybe an NSMakeRect to make a simple square that will display on the screen without a window or any view behind it, just made all in code.
This is what I have as an example
-(void)drawRect
{
NSRect myNewRect
myNewRect = NSMakeRect(100, 100, 50, 50);
}
Thats just a simple starting point but it will not show up on the screen by itself. what do i need to add?
Thanks

Every drawing operation on Mac OS X requires some window of sort. So no, you cannot draw a rect without a window. But you can create a transparent window without any borders to draw into.

First of all, you can't "display on the screen without a window or any view behind it".
You will always be drawing on some layer-backed object (UIView, etc).
And UIViews must eventually be part of some UIWindow hierarchy to display them.
So you can't "[draw] on main screen without window" at all. That's not how Core Graphics works.
However, I believe this is what you're trying to do:
-(void)drawRect
{
CGRect myNewRect = CGRectMake(100, 100, 50, 50);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ctx, [[UIColor redColor] CGColor]);
CGContextFillRect(ctx, myNewRect);
}
Which draws a rectangle in the UIView implementing the above drawRect method.

Related

How to make a smooth, rounded, volume-like OS X window with NSVisualEffectView?

I'm currently trying to make a window that looks like the Volume OS X window:
To make this, I have my own NSWindow (using a custom subclass), which is transparent/titlebar-less/shadow-less, that has a NSVisualEffectView inside its contentView. Here's the code of my subclass to make the content view round:
- (void)setContentView:(NSView *)aView {
aView.wantsLayer = YES;
aView.layer.frame = aView.frame;
aView.layer.cornerRadius = 14.0;
aView.layer.masksToBounds = YES;
[super setContentView:aView];
}
And here's the outcome (as you can see, the corners are grainy, OS X's are way smoother):
Any ideas on how to make the corners smoother? Thanks
Update for OS X El Capitan
The hack I described in my original answer below is not needed on OS X El Capitan anymore. The NSVisualEffectView’s maskImage should work correctly there, if the NSWindow’s contentView is set to be the NSVisualEffectView (it’s not enough if it is a subview of the contentView).
Here’s a sample project: https://github.com/marcomasser/OverlayTest
Original Answer – Only Relevant for OS X Yosemite
I found a way to do this by overriding a private NSWindow method: - (NSImage *)_cornerMask. Simply return an image created by drawing an NSBezierPath with a rounded rect in it to get a look similar to OS X’s volume window.
In my testing I found that you need to use a mask image for the NSVisualEffectView and the NSWindow. In your code, you’re using the view’s layer’s cornerRadius property to get the rounded corners, but you can achieve the same by using a mask image. In my code, I generate an NSImage that is used by both the NSVisualEffectView and the NSWindow:
func maskImage(#cornerRadius: CGFloat) -> NSImage {
let edgeLength = 2.0 * cornerRadius + 1.0
let maskImage = NSImage(size: NSSize(width: edgeLength, height: edgeLength), flipped: false) { rect in
let bezierPath = NSBezierPath(roundedRect: rect, xRadius: cornerRadius, yRadius: cornerRadius)
NSColor.blackColor().set()
bezierPath.fill()
return true
}
maskImage.capInsets = NSEdgeInsets(top: cornerRadius, left: cornerRadius, bottom: cornerRadius, right: cornerRadius)
maskImage.resizingMode = .Stretch
return maskImage
}
I then created an NSWindow subclass that has a setter for the mask image:
class MaskedWindow : NSWindow {
/// Just in case Apple decides to make `_cornerMask` public and remove the underscore prefix,
/// we name the property `cornerMask`.
#objc dynamic var cornerMask: NSImage?
/// This private method is called by AppKit and should return a mask image that is used to
/// specify which parts of the window are transparent. This works much better than letting
/// the window figure it out by itself using the content view's shape because the latter
/// method makes rounded corners appear jagged while using `_cornerMask` respects any
/// anti-aliasing in the mask image.
#objc dynamic func _cornerMask() -> NSImage? {
return cornerMask
}
}
Then, in my NSWindowController subclass I set up the mask image for the view and the window:
class OverlayWindowController : NSWindowController {
#IBOutlet weak var visualEffectView: NSVisualEffectView!
override func windowDidLoad() {
super.windowDidLoad()
let maskImage = maskImage(cornerRadius: 18.0)
visualEffectView.maskImage = maskImage
if let window = window as? MaskedWindow {
window.cornerMask = maskImage
}
}
}
I don’t know what Apple will do if you submit an app with that code to the App Store. You’re not actually calling any private API, you’re just overriding a method that happens to have the same name as a private method in AppKit. How should you know that there’s a naming conflict? 😉
Besides, this fails gracefully without you having to do anything. If Apple changes the way this works internally and the method just won’t get called, your window does not get the nice rounded corners, but everything still works and looks almost the same.
If you’re curious about how I found out about this method:
I knew that the OS X volume indication did what I want to do and I hoped that changing the volume like a madman resulted in noticeable CPU usage by the process that puts that volume indication on screen. I therefore opened Activity Monitor, sorted by CPU usage, activated the filter to only show “My Processes” and hammered my volume up/down keys.
It became clear that coreaudiod and something called BezelUIServer in /System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/BezelUI/BezelUIServer did something. From looking at the bundle resources for the latter, it was evident that it is responsible for drawing the volume indication. (Note: that process only runs for a short time after it displays something.)
I then used Xcode to attach to that process as soon as it launched (Debug > Attach to Process > By Process Identifier (PID) or Name…, then enter “BezelUIServer”) and changed the volume again. After the debugger was attached, the view debugger let me take a look at the view hierarchy and see that the window was an instance of a NSWindow subclass called BSUIRoundWindow.
Using class-dump on the binary showed that this class is a direct descendant of NSWindow and only implements three methods, whereas one is - (id)_cornerMask, which sounded promising.
Back in Xcode, I used the Object Inspector (right hand side, third tab) to get the address for the window object. Using that pointer I checked what this _cornerMask actually returns by printing its description in lldb:
(lldb) po [0x108500110 _cornerMask]
<NSImage 0x608000070300 Size={37, 37} Reps=(
"NSCustomImageRep 0x608000082d50 Size={37, 37} ColorSpace=NSCalibratedRGBColorSpace BPS=0 Pixels=0x0 Alpha=NO"
)>
This shows that the return value actually is an NSImage, which is the information I needed to implement _cornerMask.
If you want to take a look at that image, you can write it to a file:
(lldb) e (BOOL)[[[0x108500110 _cornerMask] TIFFRepresentation] writeToFile:(id)[#"~/Desktop/maskImage.tiff" stringByExpandingTildeInPath] atomically:YES]
To dig a bit deeper, you can use Hopper Disassembler to disassemble BezelUIServer and AppKit and generate pseudo code to see how the _cornerMask is implemented and used to get a clearer picture of how the internals work. Unfortunately, everything in regard to this mechanism is private API.
I remember doing this sort of thing long before CALayer was around. You use NSBezierPath to make the path.
I don't believe you actually need to subclass NSWindow. The important bit about the window is to initialize the window with NSBorderlessWindowMask and apply the following settings:
[window setAlphaValue:0.5]; // whatever your desired opacity is
[window setOpaque:NO];
[window setHasShadow:NO];
Then you set the contentView of your window to a custom NSView subclass with the drawRect: method overridden similar to this:
// "erase" the window background
[[NSColor clearColor] set];
NSRectFill(self.frame);
// make a rounded rect and fill it with whatever color you like
NSBezierPath* clipPath = [NSBezierPath bezierPathWithRoundedRect:self.frame xRadius:14.0 yRadius:14.0];
[[NSColor blackColor] set]; // your bg color
[clipPath fill];
result (ignore the slider):
Edit: If this method is for whatever reason undesirable, can you not simply assign a CAShapeLayer as your contentView's layer then either convert the above NSBezierPath to CGPath or just construct as a CGPath and assign the path to the layers path?
The "smooth effect" you are referring to is called "Antialiasing". I did a bit of googling and I think you might be the first person who has tried to round the corners of an NSVisualEffectView. You told the CALayer to have a border radius, which will round the corners, but you didn't set any other options. I would try this:
layer.shouldRasterize = YES;
layer.edgeAntialiasingMask = kCALayerLeftEdge | kCALayerRightEdge | kCALayerBottomEdge | kCALayerTopEdge;
Anti-alias diagonal edges of CALayer
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CALayer_class/index.html#//apple_ref/occ/instp/CALayer/edgeAntialiasingMask
Despite the limitations of NSVisualEffectView not antialiasing edges, here's a kludgey workaround for now that should work for this application of a floating title-less unresizeable window with no shadow - have a child window underneath that draws out just the edges.
I was able to get mine to look like this:
by doing the following:
In a controller holding everything:
- (void) create {
NSRect windowRect = NSMakeRect(100.0, 100.0, 200.0, 200.0);
NSRect behindWindowRect = NSMakeRect(99.0, 99.0, 202.0, 202.0);
NSRect behindViewRect = NSMakeRect(0.0, 0.0, 202.0, 202.0);
NSRect viewRect = NSMakeRect(0.0, 0.0, 200.0, 200.0);
window = [FloatingWindow createWindow:windowRect];
behindAntialiasWindow = [FloatingWindow createWindow:behindWindowRect];
roundedHollowView = [[RoundedHollowView alloc] initWithFrame:behindViewRect];
[behindAntialiasWindow setContentView:roundedHollowView];
[window addChildWindow:behindAntialiasWindow ordered:NSWindowBelow];
backingView = [[NSView alloc] initWithFrame:viewRect];
contentView = [[NSVisualEffectView alloc] initWithFrame:viewRect];
[contentView setWantsLayer:NO];
[contentView setState:NSVisualEffectStateActive];
[contentView setAppearance:
[NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
[contentView setMaskImage:[AppDelegate maskImageWithBounds:contentView.bounds]];
[backingView addSubview:contentView];
[window setContentView:backingView];
[window setLevel:NSFloatingWindowLevel];
[window orderFront:self];
}
+ (NSImage *) maskImageWithBounds: (NSRect) bounds
{
return [NSImage imageWithSize:bounds.size flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:bounds xRadius:20.0 yRadius:20.0];
[path setLineJoinStyle:NSRoundLineJoinStyle];
[path fill];
return YES;
}];
}
RoundedHollowView's drawrect looks like this:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// "erase" the window background
[[NSColor clearColor] set];
NSRectFill(self.frame);
[[NSColor colorWithDeviceWhite:1.0 alpha:0.7] set];
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:20.0 yRadius:20.0];
path.lineWidth = 2.0;
[path stroke];
}
Again, this is a hack and you may need to play with the lineWidth / alpha values depending on the base color you use - in my example if you look really closely or under lighter backgrounds you'll make out the border a bit, but for my own use it feels less jarring than not having any antialiasing.
Keep in mind that the blending mode won't be the same as the native osx yosemite pop-ups like the volume control - those appear to use a different undocumented behindwindow appearance that shows more of a color burn effect.
All kudos to Marco Masser for the most neat solution, there're two useful points:
For smooth rounded corners to work, the NSVisualEffectView must be the root view within view controller.
When using the dark material there are still funny light cropped edges that get very apparent on the dark background. Make your window background transparent to avoid this, window.backgroundColor = NSColor.clearColor().
None of these solutions worked for me on Mojave. However after an hour of research, I found this amazing repo which showcases different window designs. One of the solution looks like the OP's desired look. I tried it and it worked with nicely anti-aliased rounded corners and no titlebar artifact remaining. Here is the working code:
let visualEffect = NSVisualEffectView()
visualEffect.translatesAutoresizingMaskIntoConstraints = false
visualEffect.material = .dark
visualEffect.state = .active
visualEffect.wantsLayer = true
visualEffect.layer?.cornerRadius = 16.0
window?.titleVisibility = .hidden
window?.styleMask.remove(.titled)
window?.backgroundColor = .clear
window?.isMovableByWindowBackground = true
window?.contentView?.addSubview(visualEffect)
Note at the end the contentView.addSubview(visualEffect) instead of contentView = visualEffect. This is one of the key to make it work.

Xcode iOS7 iPad: Adding handles to images to allow re-sizing and rotation

I am looking to alter an existing iOS application so that instead of using multi-touch gestures to size and rotate images (two-finger pinch/zoom and twist), I want there to be a handle on all four corners of the image and one at the top so that the user can grab one of the handles to re-size or rotate.
I have been researching the topic but am unable to find anything pointing me in the right direction.
See this image for an example of what I'm talking about-
I'm assuming that because you're starting with a app that already has working pinch-zoom and twist gestures that your question is merely how to show those translucent circles for the handles. I'd be inclined to create UIView subclass that draws the circle, like so:
#implementation HandleView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = YES;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(context, rect);
CGContextSetFillColorWithColor(context, [[UIColor colorWithWhite:1.0 alpha:0.5] CGColor]); // white translucent
CGContextSetStrokeColorWithColor(context, [[UIColor colorWithWhite:0.25 alpha:0.5] CGColor]); // dark gray translucent
CGContextSetLineWidth(context, 1.0);
CGContextDrawPath(context, kCGPathEOFillStroke); // draw both fill and stroke
}
#end
You could achieve the same effect with CAShapeLayer layers, too, if you didn't want to write your own drawRect with Core Graphics calls like I did above. But the idea would be the same.
Your view controller can then add those five views and add gesture recognizers for them, like so:
HandleView *handleView = [[HandleView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)];
[self.view addSubview:handleView];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[handleView addGestureRecognizer:pan];
Just repeat that for each of the handles you want on the screen. and then write your gesture recognizer to do whatever you want (e.g. move the bounding rectangle, move the appropriate handles, etc.).
Sounds fairly straight forward. The view hierarchy of one possible solution as ASCII art:
Container (ScalingRotatingView)
|
+----imageView (UIImageView)
|
+----upperLeftScalingHandle (HandleView)
|
+----upperRightScalingHandle (HandleView)
|
+----lowerLeftScalingHandle (HandleView)
|
+----lowerRightScalingHandle (HandleView)
|
+----rotatingHandle (HandleView)
All instances of HandleView would have a pan gesture recognizer, that feeds one of two methods in your controller:
--updateForScaleGesture:, where you’d use the gesture recognizer’s -translationInView: to compute and store the new scale, before updating the frames of all views appropriately, and resetting the translation to 0, and
- -updateForRotationGesture:, where you’d use the gesture recognizer’s -translationInView: to compute and store the new angle before updating the frames and resetting the recognizer’s translation.
For both calculations you need the translation in the coordinate system of the image view. For the scaling part, you can then simply divide the new edge lengths by the natural image dimensions, for the rotation you can use the approximation that only the x component of the translation matters:
sin(x) = x (for small values of x)
Oh, and it sure helps if the anchor point of your image view sits at its center…

Fade effect at top and bottom of NSTableView/NSOutlineView

I'm looking for a way to draw a fade effect on a table view (and outline view, but I think it will be the same) when the content is scrolled. Here is an example from the Fantastical app:
Also a video of a similar fade on QuickLook windows here.
To make this I tried subclassing the scrollview of a tableview with this code:
#define kFadeEffectHeight 15
#implementation FadingScrollView
- (void)drawRect: (NSRect)dirtyRect
{
[super drawRect: dirtyRect];
NSGradient* g = [[NSGradient alloc] initWithStartingColor: [NSColor blackColor] endingColor: [NSColor clearColor]];
NSRect topRect = self.bounds;
topRect.origin.y = self.bounds.size.height - kFadeEffectHeight;
topRect.size.height = kFadeEffectHeight;
NSRect botRect = self.bounds;
botRect.size.height = kFadeEffectHeight;
[NSGraphicsContext saveGraphicsState];
[[NSGraphicsContext currentContext] setCompositingOperation: NSCompositeDestinationAtop];
// Tried every compositing operation and none worked. Please specify wich one I should use if you do it this way
[g drawInRect: topRect angle: 90];
[g drawInRect: botRect angle: 270];
[NSGraphicsContext restoreGraphicsState];
}
...but this didn't fade anything, probably because this is called before the actual table view is drawn. I have no idea on how to do this :(
By the way, both the tableview and the outlineview I want to have this effect are view-based, and the app is 10.7 only.
In Mac OS X (as your question is tagged), there are several gotchas that make this difficult. This especially true on Lion with elastic scrolling.
I've (just today) put together what I think is a better approach than working on the table or outline views directly: a custom NSScrollView subclass, which keeps two "fade views" tiled in the correct place atop its clip view. JLNFadingScrollView can be configured with the desired fade height and color and is free/open source on Github. Please respect the license and enjoy. :-)

Calling drawInRect on an UIImageView's image: which rect to use?

I'm trying to write a finger paint type program using Quartz graphics. The problem I'm having is when the screen rotates. The dots and lines I draw "creep" away from where I'm touching. With each successive touch, the previous dot creeps away. This does not happen in either of the two portrait modes, but it does in both landscape modes.
I may be mistaken but I think my problem is with this call:
[drawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width , self.view.frame.size.height)];
drawImage is a UIImageView in my main view controller's nib file.
I've also tried using the following:
[drawImage.image drawInRect:CGRectMake(0, 0, drawImage.frame.size.width, drawImage.frame.size.height )];
[drawImage.image drawInRect:drawImage.bounds];
[drawImage.image drawInRect:self.view.bounds];
I guess I really have no idea whether to use self or my UIImageView object for this call.
I have tried both, in both the drawInRect call and where I set up my drawing context (being consistent).
I set up the context like this in viewDidLoad (to try to make a persistant context that I don't have to reset things in each time I draw - using push and pop context).
//---------------- set up a persistent context for drawing -----------------
UIGraphicsBeginImageContext(self.view.frame.size);
context = UIGraphicsGetCurrentContext();
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, curWidth);
CGContextSetRGBStrokeColor(context, curRed, curGreen, curBlue, curAlpha);
UIGraphicsPushContext(context);
I tried to get rid of the context I set up on rotation and reinitialize it when the view rotated, thinking this was the problem, but I'm not sure I did it right.
You shouldn't be drawing that image yourself directly. Even if you set up your own context and draw into it correctly, you've got to somehow get it flushed to the screen. This is why UIKit sets up a context for you before it calls drawRect: on views. It's ready to put things on the screen and it just needs to know what to put there.
You can set up your own image context and draw into it in touchesBegan:, etc., but what you should do then is turn that into an image using UIGraphicsGetImageFromCurrentImageContext, call [myImageView setImage:theImageIJustGot], and then [myImageView setNeedsDisplay]. That signals UIKit that you have new things to put on the screen, and UIKit will take care of the rest.

How do I MOVE a circle drawn in a subclass of UIView by overwriting the method "drawRect"?

I'm trying to figure out what i'm doing wrong but i just don't get it. Here is what i want to do:
I want to draw a circle somewhere on the screen of the iPhone and then i want the circle always to be displayed at the position where the user currently taps on the screen.
I started by creating a subclass of UIView and adding the following lines into the "drawRect" method:
- (void)drawRect:(CGRect)rect {
//Create the main view!
CGContextRef mainscreen = UIGraphicsGetCurrentContext();
//Draw the dot
//will be a circle cause rectangle is a square
CGRect dotRect = CGRectMake(50, 80, 100, 100);
[[UIColor blueColor] set];
CGContextStrokeEllipseInRect(mainscreen, dotRect);
CGContextFillEllipseInRect(mainscreen, dotRect);
}
The appears just fine but now I have no idea how to make it move around on the screen I've tried several things and nothing worked.
To draw the dot in a different location, change the origin of dotRect. To figure out where to draw it, implement -touchesBegan:withEvent: and -touchesMoved:withEvent: and record the location where the touches are occuring.