I have posted several times asking for help to resolve a conundrum involving the ability to invoke "needsDisplay = true" from a different class. I have done some tests and seem to have revealed some inconsistent behavior in Swift which I don't understand. As I said in previous posts, for some reason I haven't acquired a deep understanding of Apple's object oriented programming - my brain somehow can't wrap itself around a number of its concepts.
First, here is an outline of the class structure of my Mac app, leaving out unimportant details:
class GameViewController: NSViewController {
@IBOutlet weak var graph: GraphView!
@IBAction func h1_Slider(_ sender: NSSlider) {
h1_length = sender.floatValue
dodraw() // This is only here to make a point about - it is not used normally
}
func dodraw() {
graph.needsDisplay = true
return
}
<lots of other stuff to set up Metal drawing>
}
class Renderer: NSObject, MTKViewDelegate {
public var graphRef = GameViewController() //. Allows access to GameViewController from elsewhere
<lots of stuff to set up Metal>
func draw(in view: MTKView) { // Metal drawing loop
grafRef.dodraw() // this is supposed to cause GraphView to update its drawing
<this is where the Metal drawing is done>
}
}
class GraphView:NSView
{
override func draw(_ dirtyRect:CGRect) {
<This is where the Bezier drawing takes place in a View in the Storyboard View identified as an instance of GraphView
}
}
Basically, the inner loop of the Metal drawing, which occurs 60 times a second, calls the dodraw() method in the GameViewController via the reference graphRef. This should set the needsDisplay Bool to true in GraphView so that the graph can be drawn. Instead I get "Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value" with the graph underlined in red.
Here is the inconsistency, as I see it. If I replace the "needsDisplay = true" in the dodraw() method with a print("Q") and do nothing else, the letter Q is printed many times in the output window of Xcode. This shows that the graphRef link in Renderer is working fine. Likewise, if I put back the "graph.needsDisplay = true" and remove the dodraw() call in the Metal drawing loop, the graph is displayed while I move the slider in the GameViewController class. This shows that there seems to be no trouble unwrapping the Optional value in the "graph.needsDisplay = true" statement.
I consider this to be a very inconsistent behavior: I have no trouble invoking the needsDisplay = true command by appending a "graph." before it and I have no trouble referring to the dodraw() method from the Metal inner loop. Only trouble is I can't do these at the same time. What is going wrong? Is it a misconception about how Swift works?
Post
Replies
Boosts
Views
Activity
I am developing a Mac app in Swift which does graphics animations in two windows. One is a Metal window (MTKView) which works well. The other one plots a graph as a subclass of NSView and is connected to the Metal window using an outlet (which works). When I want to update the second window, I use "graph.needsDisplay = true", where graph is the name of the outlet.
For some reason, the draw function does not execute when I use setNeedsDisplay in the MTKView class, whose draw command executes 60 times a second. I have verified that the draw function does work, since the graph is plotted when I resize the window, as it is supposed to do.
I have a version of this program (written using Obj. C and OpenGL ES) for iOS which I wrote about 5 years ago and it works fine. The draw function is the exact equivalent of the code below (in Obj. C):
class GraphView: NSView {
override func draw(_ dirtyRect:NSRect)
{
super.draw(dirtyRect)
var width:Float = 0.0, height:Float
let backgroundColor = NSColor.cyan
var iGraph, iData, i1:Int
var x,y:Int
backgroundColor.set()
NSBezierPath.fill(bounds)
height=Float(dirtyRect.size.height)
width=Float(dirtyRect.size.width)
let path=NSBezierPath()
NSColor.black.setStroke()
path.lineWidth = 0.5
path.move(to:NSPoint(x:0, y:Int(height)/2))
path.line(to:NSPoint(x:Int(width), y:Int(height)/2))
path.stroke()
current+=1
if current>200 {
current = 0 }
points[current]=jz
i1=current
x = 0
y = Int(0.25*height*(1.0-points[i1]/height_vector))
path.move(to:NSPoint(x:0, y:Int(y)))
for i in 0..<200
{
x = i*Int(width/200.0)
if i1<=0 {i1+=200}
y = Int(0.5*height - 0.45*height*points[i1]/height_vector)
path.line(to:NSPoint(x:x,y:y))
i1 -= 1
}
path.stroke()
}
}
Can anyone help? I have been trying everything under the sun and nothing works. I have been doing development of the Mac and iOS for more than 20 years, but, to be honest, have very little deep understanding of object oriented programming.
I submitted a query on a similar question which was answered about 2 days ago. Thanks in advance.
I am developing a Mac app in Swift which does graphics animations in two windows. One is a Metal window (MTKView) which works well. The other one plots a graph as a subclass of NSView and is connected to the Metal window using an outlet (which works). When I want to update the second window, I use "graph.needsDisplay = true", where graph is the name of the outlet.
For some reason, the needsDisplay setting was being overridden: I set it equal to "true" and it was immediately set to "false", as I determined by printing the value. This was being done "behind my back" and is a complete mystery.
Is there any reason why my setting would be overridden?
Thanks!
I am sure this is an elementary question, but I am new to Swift and Metal, but have a fair amount of experience using Objective C and OpenGL.
I would like to render some mathematically generated surfaces in Metal and need to fill the vertex buffer with positions and normals. I would guess that creating a one dimensional array and adding to it using "append" is not the most efficient way to do this. The buffer might be quite large and I would like to set aside the memory for it in advance, which is how I did it using Objective C. Can anyone suggest the best way to do this? Thanks in advance.