value of optional type "CGContext?" not unwrapped - swift

I try to draw a line in the view, but my code fail to complier due to optional type error. I am new to swift and objective-c and spend many hours to search answer. The issue does not fix so far. So, Can anyone provide some clue to fix this issue?
Code:
import UIKit
class DrawLines: UIView {
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
//Drawing code
// context
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 3.0)
CGContextSetStrokeColorWithColor(context, UIColor.purpleColor().cgColor)
//create a path
CGContextMoveToPoint(context,0,0)
CGContextAddLineToPoint(context,250,320)
}
}
Error:

UIGraphicsGetCurrentContext() returns optional, to use it in your example you need to call context!.
The best way to use it it wrap it in if-let:
if let context = UIGraphicsGetCurrentContext() {
// Use context here
}
or even better use guard let:
guard let context = UIGraphicsGetCurrentContext() else { return }
// Use context here

In this case the solution is to use ! when getting the context:
let context = UIGraphicsGetCurrentContext()!
The app will crash when there is no current context which means you have done something very wrong.

Just forced unwrap the context, it is 100% safe but solves only one issue.
From the documentation of UIGraphicsGetCurrentContext:
The current graphics context is nil by default. Prior to calling its drawRect: method, view objects push a valid context onto the stack, making it current.
In Swift 3 (assumed from the draw signature) the graphics syntax changed significantly:
class DrawLines: UIView {
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
context.setLineWidth(3.0)
context.setStrokeColor(UIColor.purple.cgColor)
//create a path
// context.beginPath()
context.move(to: CGPoint())
context.addLine(to: CGPoint(x:250, y:320))
// context.strokePath()
}
}
PS: But to draw the line you should uncomment the beginPath() and strokePath() lines.

Related

How to make a fading eraser using CGMutablePath?

I have a CGMutablePath of touch event handling. I can able to erase clean path by using below code. But I want to make a fading-out eraser that is shows on below image. I tried it to make this for some days but failed to do that. Fading-out means showing less alpha around touches area of eraser.
var path = CGMutablePath()
public override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.saveGState()
ctx.addPath(path)
ctx.setLineCap(.round)
ctx.setLineWidth(20.0)
ctx.setBlendMode(.clear)
ctx.strokePath()
ctx.restoreGState()
}

How to get target UITraitCollection as well as target rect

I have a tool pane which in compact H mode, will be at the bottom spanning the full screen, but in compact V mode (or non compact H mode), it will be on the right as a floating pane. How do I get the target UITraitCollection + the target size? They seem to be in 2 different methods:
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
// need size rect
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// need traits
}
I need both infos for animating things properly! Thanks so much!
You can in fact get both values, the size and the trait collection in both methods, by operating on the UIViewControllerTransitionCoordinator.
let didQueueAnimation = coordinator.animate(alongsideTransition: { context in
guard let view = context.view(forKey: UITransitionContextViewKey.to) else {
return
}
let newScreenSize = view.frame.size
guard let viewController = context.viewController(forKey: UITransitionContextViewKey.to) else {
return
}
let newTraitCollection = viewController.traitCollection // <-- New Trait Collection
// You can also get the new view size from the VC:
// let newTraitCollection = viewController.view.frame.size
// Perform animation if trait collection and size match.
}, completion: { context in
// Perform animation cleanup / other pending tasks.
})
if (didQueueAnimation) {
print("Animation was queued.")
}
Apple's idea here is to simplify the call site with one context parameter that can be queried for multiple properties, and also execute asynchronously so that the values for the final transitioned view or VC can be fetched accurately in one place even before the update occurs.
While you can perform animations in either of the WillTransition methods, I would use the coordinator.animate(alongsideTransition:completion:) method if possible rather than coordinator.notifyWhenInteractionChanges(_:) since it synchronizes the system animation alongside your own custom animation and you can still query the context for the new traitCollection or frame.size using the techniques above.

Drawing directly in a NSView without using the draw(_ updateRect: NSRect) function

I would like to draw CGImage pictures directly to a View and with the normal method using the draw func I only get 7 pictures in a second on a new Mac Book Pro. So I decided to use the updateLayer func instead. I have defined wantsUpdateLayer = true and my new updateLayer func is called as expected. But then starts my problem. When using the draw func, I get the current CGContext with "NSGraphicsContext.current?.cgContext" but in my updateLayer func the "NSGraphicsContext.current?.cgContext" is nil. So I do not know where to put my CGImage, that it will be displayed on my screen. Also "self.view?.window?.graphicsContext?.cgContext" and "self.window?.graphicsContext?.cgContext" are nil, too. There are no buttons or other elements in this view and in the window of the view, only one picture, filling the complete window. And this picture must change 30 times in a second. Generating the pictures is done by a separate thread and needs about 1 millisecond for a picture. I think that from "outside" the NSView class it is not possible to write the picture but my updateLayer func is inside the class.
Here is what the func looks like actually:
override func updateLayer ()
{
let updateRect: NSRect = NSRect(x: 0.0, y: 0.0, width: 1120.0, height: 768.0)
let context1 = self.view?.window?.graphicsContext?.cgContext
let context2 = self.window?.graphicsContext?.cgContext
let context3 = NSGraphicsContext.current?.cgContext
}
And all three contexts are nil in the time the function is called automatically after I set the needsDisplay flag.
Any ideas where to draw my CGImages?
The updateLayer func is called automatically by the user interface. I do not call it manually. It is called by the view. My problem is where inside this method to put my picture to be shown on the screen. Perhaps I have to add a layer or use a default layer of the view but I do not know how to do this.
Meanwhile I have found the solution with some tipps from a good friend:
override var wantsUpdateLayer: Bool
{
return (true)
}
override func updateLayer ()
{
let cgimage: CGImage? = picture // Here comes the picture
if cgimage != nil
{
let nsimage: NSImage? = NSImage(cgImage: cgimage!, size: NSZeroSize)
if nsimage != nil
{
let desiredScaleFactor: CGFloat? = self.window?.backingScaleFactor
if desiredScaleFactor != nil
{
let actualScaleFactor: CGFloat? = nsimage!.recommendedLayerContentsScale(desiredScaleFactor!)
if actualScaleFactor != nil
{
self.layer!.contents = nsimage!.layerContents(forContentsScale: actualScaleFactor!)
self.layer!.contentsScale = actualScaleFactor!
}
}
}
}
}
This is the way to directly write into the layer. Depending on the format (CGImage or NSImage) you first must convert it. As soon as the func wantsUpdateLayer returns a true, the func updateLayer() is used instead of the func draw(). Thats all.
For all who want to see my "Normal" draw function:
override func draw (_ updateRect: NSRect)
{
let cgimage: CGImage? = picture // Here comes the picture
if cgimage != nil
{
if #available(macOS 10.10, *)
{
NSGraphicsContext.current?.cgContext.draw(cgimage!, in: updateRect)
}
}
else
{
super.draw(updateRect)
}
}
The additional speed is 2 times or more, depending on what hardware you use. On a modern Mac Pro there is only a little bit more speed but on a modern Mac Book Pro you will get 10 times or more speed. This works with Mojave 10.14.6 and Catalina 10.15.6. I did not test it with older macOS versions. The "Normal" draw function works with 10.10.6 to 10.15.6.

Swift: NSBezier

This is the code inside my custom view class:
func drawTestingPoint(_ point: CGPoint, target: Int, output: Int) {
let path = NSBezierPath()
path.appendArc(withCenter: point, radius: 5, startAngle: 0, endAngle: 360)
NSColor.black.setStroke()
if target == output {
NSColor.green.setFill()
} else {
NSColor.red.setFill()
}
path.lineWidth = 3
path.fill()
path.stroke()
}
override func draw(_ dirtyRect: NSRect) {
//If I call the drawTestingPoint function here it works
}
Inside my viewDidLoad method in my NSViewController class I set up the custom view and try to draw the testing point:
let size = getDataViewSize()
let origin = CGPoint(x: view.frame.width/2-size.width/2, y: view.frame.height/2-size.height/2)
dataView = DataView(frame: CGRect(origin: origin, size: size))
view.addSubview(dataView)
dataView.drawTestingPoint(CGPoint(x: view.frame.width/2 y: view.frame.height/2), target: target, output: output)
dataView.needsDisplay = true
My problem is that no point is getting drawn. I think there can't be anything wrong with my drawTestingPoint function because when I call it inside my draw(_ dirtyRect: NSRect) function in my custom NSView class, it works. What can I do so I can call this function inside my viewDidLoad function how you can see in the codes snippets above so my point gets drawn
You can't just draw any time you want. Normally you set up a view and implement draw(_:) as you've done. The system calls the draw method when it needs the view to draw its contents. Before calling your draw(_:) method it sets up the drawing context correctly to draw inside your view and clip if you draw outside of the view. That's the bit you're missing.
As a general rule you should NOT draw outside of the view's draw(_:) method. I've done drawing outside of the draw(_:) method so infrequently that I don't remember what you'd need to do to set up the drawing context correctly. (To be fair I do mostly iOS development these days and my MacOS is getting rusty.)
So the short answer is "Don't do that."
EDIT:
Instead, set up your custom view to save the information it needs to draw itself. As others have suggested, when you make changes to the view, set needsDisplay=true on the view. That will cause the system to call the view's draw(_:) method on the next pass through the event loop

How to use Implicitly Unwrapped Optionals?

I'm porting an NSView to Swift and I need to use Quartz CGContextAddPath.
import Cocoa
class MYView: NSView {
init(frame: NSRect) {
super.init(frame: frame)
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor .redColor() .set()
NSRectFill(self.bounds)
var p :CGMutablePathRef = CGPathCreateMutable()
var ctx = NSGraphicsContext.currentContext().graphicsPort()
CGContextAddPath(ctx, p) // compiler rejects this line
}
}
How do you understand this error message ?
Cannot convert the expression's type 'Void' to type 'CGContext!'
The Swift signature of CGContextAddPath is:
func CGContextAddPath(context: CGContext!, path: CGPath!)
What is my error ?
When I use this:
let context = UnsafePointer<CGContext>(ctx).memory
I now have a runtime error:
Jun 3 15:57:13 xxx.x SwiftTest[57092] <Error>: CGContextAddPath: invalid context 0x7fff73bd0060. This is a serious error. This application, or a library it uses, is using an invalid context and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.
Here is the code that I'm currently using:
import Cocoa
class MYView: NSView {
init(frame: NSRect) {
super.init(frame: frame)
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
var p :CGMutablePathRef = CGPathCreateMutableCopy( CGPathCreateWithRoundedRect(self.bounds, 10, 10, nil))
var ctx = NSGraphicsContext.currentContext().graphicsPort()
let context = UnsafePointer<CGContext>(ctx).memory
CGContextAddPath(context, p) // compiler no longer rejects this line
var blueColor = NSColor.blueColor()
CGContextSetStrokeColorWithColor(context, NSColor.blueColor().CGColor)
CGContextSetLineWidth(context, 2)
CGContextStrokePath(context)
}
}
As of Swift 1.0
If your deployment target is 10.10 you can use the convenience method introduced with Yosemite.
let context = NSGraphicsContext.currentContext().CGContext
If you have to support 10.9 you'll have to cast the context manually as per below.
let contextPtr = NSGraphicsContext.currentContext().graphicsPort
let context = unsafeBitCast(contextPtr, CGContext.self)
Use NSGraphicsContext.currentContext().graphicsPort(). It returns void*. You have to cast it to
CGContextRef
let ctx = UnsafePointer<CGContext>(NSGraphicsContext.currentContext().‌​graphicsPort()).memo‌​ry