Working with my app, i encountered some issues with drawing stuff:
I was wondering why, back to Obj-c, -moveToPoint() and -lineToPoint() were drawing everything with no problem and now, with swift, everything seems the same except for a strange border appearing on my view. Let me explain better:
Imagine that your task is to draw a basic line from A(0,0) to B(10,10)
We all know how to make that in obj-c, but in swift there is something new to me:
var path : NSBezierPath = NSBezierPath(rect: dirtyRect)
let color = NSColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
color.set()
path.moveToPoint(NSPoint(x: 0,y: 0))
path.lineToPoint(NSPoint(x: 10,y: 10))
path.lineWidth = 5.0 //Makes the line 5px width, but it even
//creates an annoying border along the view
//Try it out, i can't figure out how to get rid of it
path.stroke()
You are initializing the bezier path with a rect, so that rect is part of the path that gets stroked when you call path.stroke(). You can just initialize the path as NSBezierPath().
I suggest, instead of using NSBezierPath, NSColor, and NSPoint, you should use the updated UIBezierPath, UIColor, and CGPoint respectively.
Related
I have an NSTextView with some exclusion rects so I can shift the text vertically without having to resort to adding newlines to the string. However, I've noticed that the exclusion rect is rather limited or perhaps even buggy because I cannot shift the text vertically more than ~45% of the textview's height. I have found a workaround where I increase the textview's height by 2x but this feels gross and I'd rather do it "properly"
In the image above, I have three text views (from left to right)
Programmatically without encapsulating it inside an NSScrollView, 2x height
Programmatically with NSScrollView encapsulation. 2x height
Using interface builder, regular height.
The exclusion rect is drawn with a CAShapeLayer and you can see that "hello world new world" isn't properly positioned outside of the exclusion rect.
I tried all three examples to make sure I wasn't missing something with regards to IB default settings or the dynamics of the text view when encapsulated in an NSScrollView (Im new to AppKit and TextKit), however all 3 examples exhibit the same bug.
Code to update the text exclusion rect
(Each time the slider moves on the bottom right, it will update their text rect)
label.stringValue = "excl: \(sender.integerValue): height: \(view.frame.height)"
print(sender.integerValue)
let exclHeight: CGFloat = CGFloat(slider.integerValue)
[aTextView, bTextView, cTextView]
.compactMap { $0 }
.forEach {
let rect = CGRect(
x: 5,
y: 5,
width: aTextView.frame.width - 10,
height: exclHeight)
$0.wantsLayer = true
$0.textContainer?.exclusionPaths.removeAll()
$0.textContainer?.exclusionPaths.append(.init(rect: rect))
$0.layer?.sublayers?.forEach {
$0.removeFromSuperlayer()
}
let layer = CAShapeLayer()
layer.frame = rect
layer.backgroundColor = NSColor.blue.withAlphaComponent(0.2).cgColor
layer.borderWidth = 1
layer.borderColor = NSColor.white.cgColor
$0.layer?.addSublayer(layer)
}
}
The problem seems to be that the exclusionPath is before the first line.
Just playing around with the parameters, a two line sample text with the rect y positioned after the first line works without any problems.
So it looks like the issue is calculating the container height when it starts with a exclusionPaths in -[NSLayoutManager usedRectForTextContainer:]
#interface LOLayoutManager : NSLayoutManager
#end
#implementation LOLayoutManager
- (NSRect)usedRectForTextContainer:(NSTextContainer *)container
{
NSRect rect = [super usedRectForTextContainer:container];
NSRect newRect = NSMakeRect(0, 0, NSMaxX(rect), NSMaxY(rect));
return newRect;
}
#end
Instead of returning y position from the exclusionPaths and the line fragments height, this returns a big rect starting at 0, 0. This should work as long as the NSTextView only contains one text container.
I'm developing a magnifying glass like application for mac. My goal is to be able to pinpoint individual pixels when zoomed in. I'm using this code in mouseMoved(with event: NSEvent):
let captureSize = self.frame.size.width / 9 //9 is the scale factor
let screenFrame = (NSScreen.main()?.frame)!
let x = floor(point.x) - floor(captureSize / 2)
let y = screenFrame.size.height - floor(point.y) - floor(captureSize / 2)
let windowID = CGWindowID(self.windowNumber)
cgImageExample = CGWindowListCreateImage(CGRect(x: x, y: y, width: captureSize,
height: captureSize), CGWindowListOption.optionOnScreenBelowWindow, windowID,
CGWindowImageOption.bestResolution)
The creation of the cgImage takes place in the CGWindowListCreateImage method. When I later draw this in an NSView, the result looks like this:
It looks blurred / like some anti-aliasing was applied during the creation of the cgImage. My goal is to get a razor sharp representation of each pixel. Can anyone point me in the right direction?
Ok, I figured it out. It was a matter of setting the interpolation quality to none on the drawing context:
context.interpolationQuality = .none
Result:
On request some more code:
//get the context
guard let context = NSGraphicsContext.current()?.cgContext else { return }
//get the CGImage
let image: CGImage = //pass the result from CGWindowListCreateImage call
//draw
context.draw(image, in: (CGRect of choice))
Is it possible to add an imageView behind a UITableView? How would I add this image:
So that users could see it behind a tableview (background set to clear)?
The following should work:
tableView.backgroundColor = UIColor(patternImage: UIImage(named: "your_image_name")!)
As the function names imply, a UIColor is created from the image you supply.
Refer to the Apple documentation here for more information.
If you go into the Storyboard, you can insert the Image View, insert the image in, then in the Document Outline, you can move the image above the table and it'll go behind it. Then set the tableView alpha in the inspector as .5 or however opaque you want it.
Funny, I just did a tutorial that had this in it, but code was used instead of an image. In Swift 2.0 you create a file (let's call it BackgroundView) of type UIView, add this code in the pre-made drawRect function:
// Change these colors to the ones you want.
let lightPurple: UIColor = UIColor(red: 0.377, green: 0.075, blue: 0.778, alpha: 1.000)
let darkPurple: UIColor = UIColor(red: 0.060, green: 0.036, blue: 0.202, alpha: 1.000)
let context = UIGraphicsGetCurrentContext()
//// Gradient Declarations
let purpleGradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), [lightPurple.CGColor, darkPurple.CGColor], [0, 1])
//// Background Drawing
let backgroundPath = UIBezierPath(rect: CGRectMake(0, 0, self.frame.width, self.frame.height))
CGContextSaveGState(context)
backgroundPath.addClip()
CGContextDrawLinearGradient(context, purpleGradient,
CGPointMake(160, 0),
CGPointMake(160, 568),
[.DrawsBeforeStartLocation, .DrawsAfterEndLocation])
Then create a configureView function in the TableViewController and put this line of code in it:
tableView.backgroundView = BackgroundView()
Then call the function in the viewDidLoad.
I have created a view and in my draw rect method I create paths depending on what a user does with sliders. Using standard colors , everything works and looks very nice. I am trying to follow a code snippet from apple that shows how to draw patterns into a rect at this link:
Apple Drawing Guide
The example shows how to create a function callback with the pattern desired and then an additional method call to draw the rect. If I call the code as it is written from my rect it will draw my pattern as I would expect, however, I do not want to fill my rect , I want to fill a specified path in the rect. If I change the call in the drawing method from CGContextFillRect to CGContextFillPath, it doesn't work. I'm sure there is something I am overlooking to modify this code to get it to do what I want.
My callback pattern is a simple checkerboard:
code:
// Call Back function for Graphics Pattern
#define PATTERN_SIZE 10
void patternSpec(void *info , CGContextRef pContext){
NSLog(#"patternSpec Callback Called");
CGFloat subUnit = PATTERN_SIZE / 2;
CGRect square1 = {{0,0}, {subUnit, subUnit}},
square2 = {{subUnit, subUnit}, {subUnit, subUnit}},
square3 = {{0 , subUnit}, {subUnit, subUnit}},
square4 = {{subUnit , 0}, {subUnit, subUnit}};
CGContextSetRGBFillColor(pContext, 1.0, 0.0, 0.0, 1.0 );
CGContextFillRect(pContext, square1);
CGContextSetRGBFillColor(pContext, 1.0, 0.0, 0.0, 1.0 );
CGContextFillRect(pContext, square2);
CGContextSetRGBFillColor(pContext, 0.0, 0.0, 0.0, 1.0 );
CGContextFillRect(pContext, square3);
CGContextSetRGBFillColor(pContext, 0.0, 0.0, 0.0, 1.0 );
CGContextFillRect(pContext, square4);
}
// Method that draws the pattern
static void drawPattern (CGContextRef myContext)
{
NSLog(#"drawPattern Called ");
CGPatternRef pattern;
CGColorSpaceRef patternSpace;
CGFloat alpha = 1.0;
//width, height;
static const CGPatternCallbacks callbacks = {0, &patternSpec, NULL};
CGContextSaveGState (myContext);
patternSpace = CGColorSpaceCreatePattern (NULL);// 6
CGContextSetFillColorSpace (myContext, patternSpace);// 7
CGColorSpaceRelease (patternSpace);// 8
pattern = CGPatternCreate (NULL,CGRectMake (0, 0, PATTERN_SIZE, PATTERN_SIZE),
CGAffineTransformIdentity, PATTERN_SIZE, PATTERN_SIZE,
kCGPatternTilingConstantSpacing true, &callbacks);
CGContextSetFillPattern (myContext, pattern, &alpha);// 17
CGPatternRelease (pattern);// 18
//CGContextFillRect(myContext, rect);
CGContextDrawPath(myContext, kCGPathFill);
CGContextRestoreGState (myContext);
}
Here is a snippet of the code where I would like to call the routine:
CGContextSetLineWidth(context, .7);
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0);
// Standard non-inverted view scenario.
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0.00 , bMargin);
CGContextAddLineToPoint(context, highPX - curveSP , bMargin);
[self addCurve:context startX:highPX startY:bMargin radius:bo curveSp:curveSP curveDir:FL_BL];
CGContextAddLineToPoint(context, highPX, ((h - tMargin) - curveSP) );
[self addCurve:context startX:highPX startY: (h - tMargin) radius:bo curveSp:curveSP curveDir:FL_TL];
CGContextAddLineToPoint(context, (lowPX - curveSP), (h - tMargin) );
[self addCurve:context startX: lowPX startY: (h - tMargin) radius:bo curveSp:curveSP curveDir:FL_TR];
CGContextAddLineToPoint(context, lowPX, (bMargin + curveSP) );
[self addCurve:context startX:lowPX startY: bMargin radius:bo curveSp:curveSP curveDir:FL_BR];
CGContextAddLineToPoint(context, w, bMargin);
//CGContextDrawPath(context, nonInvertedView);
CGContextDrawPath(context, kCGPathStroke);
// fill with pattern
drawPattern(context);
The actual apple example also includes an NSRect arg in the drawing method, but since I don't want to fill a rect, I figured I could omit that. not sure though.
Thanks
CGContextDrawPath resets the current path. (They used to mention that somewhere, but I couldn't find it in a quick search.)
Save the graphics state before you stroke, then restore before you fill with the pattern.
(I assume you're specifically trying to get an outer stroke by stroking and then filling over half of it. If you want or can accept a centered stroke, kCGPathFillStroke will do the job with a single CGContextDrawPath call.)
So heres an update: I don't fully understand whats happening, but if I drop the code in the drawPattern method into a test app with an empty rect, it draws just like it should. If I drop the code into my method for drawing a path into a view , I get very strange behavior; it even tries to redraw parts of the view controller it should't even know about.
As soon as I deleted the CGContextSaveGState() , CGColorSpaceRelease(), CGPatternRelease(), and the CGContextRestoreGState(), the code started doing exactly what I wanted. I modified the method to this:
static void drawPattern(CGContextRef *pContext){
static CGPatternRef pattern;
static CGColorSpaceRef patternSpace;
static CGFloat alpha = 1.0;
static const CGPatternCallbacks callbacks = {0, &patternSpec, NULL};
patternSpace = CGColorSpaceCreatePattern (NULL);
CGContextSetFillColorSpace (pContext, patternSpace);
pattern = CGPatternCreate (NULL,
CGRectMake (0, 0, PATTERN_SIZE, PATTERN_SIZE),
CGAffineTransformIdentity,
PATTERN_SIZE,
PATTERN_SIZE,
kCGPatternTilingConstantSpacing,
true, &callbacks);
CGContextSetFillPattern (pContext, pattern, &alpha);
}
Now I can either call the defined pattern, or set a define fill color:
CGContextSetFillColor(context);
or:
drawPattern(context);
I would appreciate input on this, because I would like to know if leaving out some of these saveState or Release methods is a problem such as memory leaks.
Thanks
Using the plethora of drawing functions in Cocoa or Quartz it's rather easy to draw paths, and fill them using a gradient. I can't seem to find an acceptable way however, to 'stroke'-draw a path with a line width of a few pixels and fill this stroke using a gradient. How is this done?
Edit: Apparently the question wasn't clear enough. Thanks for the responses so far, but I already figured that out. What I want to do is this:
(source: emle.nl)
The left square is NSGradient drawn in a path followed by a path stroke message. The right is what I want to do; I want to fill the stroke using the gradient.
If you convert the NSBezierPath to a CGPath, you can use the CGContextReplacePathWithStrokedPath() method to retrieve a path that is the outline of the stroked path. Graham Cox's excellent GCDrawKit has a -strokedPath category method on NSBezierPath that will do this for you without needing to drop down to Core Graphics.
Once you have the outlined path, you can fill that path with an NSGradient.
I can't seem to find an acceptable way however, to 'stroke'-draw a path with a line width of a few pixels and fill this stroke using a gradient. How is this done?
[Original answer replaced with the following]
Ah, I see. You want to apply the gradient to the stroke.
To do that, you use a blend mode. I explained how to do this in an answer on another question. Here's the list of steps, adapted to your goal:
Begin a transparency layer.
Stroke the path with any non-transparent color.
Set the blend mode to source in.
Draw the gradient.
End the transparency layer.
According to Peter Hosey's answer I've managed to do a simple gradient curve, which looks like this:
I've done this in drawRect(_:) method of UIView class by writing the code below:
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextBeginTransparencyLayer (context, nil)
let path = createCurvePath()
UIColor.blueColor().setStroke()
path.stroke()
CGContextSetBlendMode(context, .SourceIn)
let colors = [UIColor.blueColor().CGColor, UIColor.redColor().CGColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let colorLocations :[CGFloat] = [0.0, 1.0]
let gradient = CGGradientCreateWithColors(colorSpace, colors, colorLocations)
let startPoint = CGPoint(x: 0.0, y: rect.size.height / 2)
let endPoint = CGPoint(x: rect.size.width, y: rect.size.height / 2)
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, CGGradientDrawingOptions.DrawsBeforeStartLocation)
CGContextEndTransparencyLayer(context)
}
Function createCurvePath() returns an UIBezierPath object. I've also set path.lineWidth to 5 points.