How to use Implicitly Unwrapped Optionals? - swift

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

Related

value of optional type "CGContext?" not unwrapped

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.

MetalKit - Drawable.texture assertion error

I am new to MetalKit and trying to convert this tutorial from playground back to OSX app:
import MetalKit
public class MetalView: MTKView {
var queue: MTLCommandQueue! = nil
var cps: MTLComputePipelineState! = nil
required public init(coder: NSCoder) {
super.init(coder: coder)
device = MTLCreateSystemDefaultDevice()
registerShaders()
}
override public func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
if let drawable = currentDrawable {
let command_buffer = queue.commandBuffer()
let command_encoder = command_buffer.computeCommandEncoder()
command_encoder.setComputePipelineState(cps)
command_encoder.setTexture(drawable.texture, atIndex: 0)
let threadGroupCount = MTLSizeMake(8, 8, 1)
let threadGroups = MTLSizeMake(drawable.texture.width / threadGroupCount.width, drawable.texture.height / threadGroupCount.height, 1)
command_encoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupCount)
command_encoder.endEncoding()
command_buffer.presentDrawable(drawable)
command_buffer.commit()
}
}
func registerShaders() {
queue = device!.newCommandQueue()
do {
let library = device!.newDefaultLibrary()!
let kernel = library.newFunctionWithName("compute")!
cps = try device!.newComputePipelineStateWithFunction(kernel)
} catch let e {
Swift.print("\(e)")
}
}
}
I got an error at the line:
command_encoder.setTexture(drawable.texture, atIndex: 0)
failed assertion `frameBufferOnly texture not supported for compute.'
How can I resolve this?
If you want to write to a drawable's texture from a compute function, you'll need to tell the MTKView that it should configure its layer not to be framebuffer-only:
metalView.framebufferOnly = false
With this value set to false, your drawable will give you a texture with the shaderWrite usage flag set, which is required when writing a texture from a shader function.
I have the same issue even I set the frameBufferOnly to false and print the boolean for double check before using the drawable.
But when I turn off the GPU frame capture, no more assertion occurs (for now at least). Still don't know why.
Menu > Scheme > Edit Scheme > Run > Options > GPU Frame Capture: Disabled

drawRect throws "fatal error: unexpectedly found nil while unwrapping an Optional value" in Xcode playground

Why the below code throws fatal error: unexpectedly found nil while unwrapping an Optional value when run in Xcode playground? I am not sure what is wrong with the below code. Thanks for your help. I haven't tried running it outside playground.
import UIKit
import XCPlayground
class CircularProgressView: UIView {
var progressBackgroundLayer: CAShapeLayer!
var progressLayer: CAShapeLayer!
var iconLayer: CAShapeLayer!
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
convenience init() {
self.init(frame: CGRectZero)
}
func setup() {
progressBackgroundLayer = CAShapeLayer(layer: layer)
progressLayer = CAShapeLayer(layer: layer)
iconLayer = CAShapeLayer(layer: layer)
}
override func drawRect(rect: CGRect) {
progressBackgroundLayer.frame = self.bounds
progressLayer.frame = self.bounds
iconLayer.frame = self.bounds
}
}
var progressView = CircularProgressView(frame: CGRectMake(0, 0, 80, 80))
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
XCPlaygroundPage.currentPage.liveView = progressView
You shouldn't use the init(layer:) initialiser in order to create your CAShapeLayers. From the documentation (emphasis mine):
This initializer is used to create shadow copies of layers, for example, for the presentationLayer method. Using this method in any other situation will produce undefined behavior. For example, do not use this method to initialize a new layer with an existing layer’s content.
Therefore, as the behaviour of calling this initialiser is undefined in this circumstance, it's returning nil in your case – which you're then force unwrapping upon accessing, as it's an implicitly unwrapped optional.
You should instead just create your layers with init(). I would also recommend that you define your layer instances inline, and get rid of the implicitly unwrapped optionals, as they're inherently unsafe. For example:
class CircularProgressView: UIView {
let progressBackgroundLayer = CAShapeLayer()
let progressLayer = CAShapeLayer()
let iconLayer = CAShapeLayer()
...
In order to add the layers to your view, you need to use the addSublayer: method on the view's layer. For example:
func setup() {
layer.addSublayer(progressBackgroundLayer)
layer.addSublayer(progressLayer)
layer.addSublayer(iconLayer)
}
Also, drawRect: is used to draw the contents of your view, and therefore is totally the wrong place to be defining the frames of your layers (which should only occur when the view's bounds changes). You should instead consider doing this in layoutSubviews.
override func layoutSubviews() {
progressBackgroundLayer.frame = bounds
progressLayer.frame = bounds
iconLayer.frame = bounds
}
And finally, this code is pointless:
convenience init() {
self.init(frame: CGRectZero)
}
This is exactly what the UIView implementation of init does already.

Where to initialize in Swift?

I have a very general question about the Initialization in Swift.
Unlike in Objective C it's now possible to call the init() directly at the declaration outside of my functions:
e.g.
class ViewController: UIViewController {
let myView: UIView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
myView.frame = getFrame()
myView.backgroundColor = UIColor.redColor()
self.view.addSubview(myView)
}
func getFrame() -> CGRect {
return CGRectMake(0, 0, 100, 100)
}
}
In Objective C I would have done the initialization in my function.
But what if I want to call an Initializer with parameters which are not set yet?
e.g.
I want to init with a frame which is being calculated in a func()
class ViewController: UIViewController {
//THIS IS NOT WOKRING
let myView: UIView = UIView(frame: getFrame())
override func viewDidLoad() {
super.viewDidLoad()
myView.backgroundColor = UIColor.redColor()
self.view.addSubview(myView)
}
func getFrame() -> CGRect {
return CGRectMake(0, 0, 100, 100)
}
}
I don't wanna do my initializations at two different places in the Code. Is there any general pattern for the initializations?
So your options for initialisation in swift are numerous. With your current example you cannot use the method getFrame() yet because you do not yet have a reference to self as the ViewController has not get been initialised. Instead you could use:
let myView: UIView = UIView(frame: CGRectMake(0, 0, 100, 100))
As this does not require the reference to self. Alternatively you could lazy instantiation which will get run after self is available (this can only be used with var not let:
lazy var myView: UIView = {
return UIView(frame:self.getFrame())
}()
To answer your question, when using UIKit class where you often don't have control over their instantiation you can keep doing it the same was as you were in objective c and use implicitly unwrapped optionals (to prevent you having to use a ! or ? every time you instantiate a variable, e.g.:
var myView: UIView!
override func viewDidLoad(){
super.viewDidLoad();
myView = UIView(frame:getFrame())
}
This however does not work with let constants, as they need to be assigned either immediately or in the constructor of the object. Hope this helps.

pie chart/plot in swift

I've tried so many different ways of add plots package from GitHub or using core plots or combining objective-c into swift, but it appeared so many problems during the process, and during this week I didn't make a successful chart. I'm really depressed.
Is there anyone who succeed in creating a pie chart in swift? The similar questions seem don't have successful answers.
I would really appreciate your help!
Don't be depressed. You just have to add a more specific question to get more help. For example, if you start from scratch and try to integrate a plots package from Github, you have to say what package, how did you try to integrate it, what errors are you getting etc.
However, drawing a simple pie chart is pretty easy with CoreGraphics functionality. Here is a little gift from my code, this draws progress value as a simple black and white pie chart. It only has 2 sections, but you can generalize from it
#IBDesignable class ProgressPieIcon: UIView {
#IBInspectable var progress : Double = 0.0 {
didSet {
self.setNeedsDisplay()
}
}
required init(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
self.contentMode = .Redraw
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()
self.contentMode = .Redraw
}
override func drawRect(rect: CGRect) {
let color = UIColor.blackColor().CGColor
let lineWidth : CGFloat = 2.0
// Calculate box with insets
let margin: CGFloat = lineWidth
let box0 = CGRectInset(self.bounds, margin, margin)
let side : CGFloat = min(box0.width, box0.height)
let box = CGRectMake((self.bounds.width-side)/2, (self.bounds.height-side)/2,side,side)
let ctx = UIGraphicsGetCurrentContext()
// Draw outline
CGContextBeginPath(ctx)
CGContextSetStrokeColorWithColor(ctx, color)
CGContextSetLineWidth(ctx, lineWidth)
CGContextAddEllipseInRect(ctx, box)
CGContextClosePath(ctx)
CGContextStrokePath(ctx)
// Draw arc
let delta : CGFloat = -CGFloat(M_PI_2)
let radius : CGFloat = min(box.width, box.height)/2.0
func prog_to_rad(p: Double) -> CGFloat {
let rad = CGFloat(p * 2 * M_PI)
return rad + delta
}
func draw_arc(s: CGFloat, e: CGFloat, color: CGColor) {
CGContextBeginPath(ctx)
CGContextMoveToPoint(ctx, box.midX, box.midY)
CGContextSetFillColorWithColor(ctx, color)
CGContextAddArc(
ctx,
box.midX,
box.midY,
radius-lineWidth/2,
s,
e,
0)
CGContextClosePath(ctx)
CGContextFillPath(ctx)
}
if progress > 0 {
let s = prog_to_rad(0)
let e = prog_to_rad(min(1.0, progress))
draw_arc(s, e, color)
}
}
We've made some changes to the Core Plot API, including replacing NSDecimal with NSDecimalNumber, for Swift compatibility. The changes are on the release-2.0 branch and not in a release package yet. See Core Plot issue #96 for more discussion of the issue.
Have you tried to look for any CorePlot tutorial with Swift ? Like this one maybe.
Otherwise, you might want to give a look to other framework.