0

Note that the word "graph" here means a graph theory graph, not a statistical graph.

I am trying to create a graph that has nodes that you can move around on the screen. I decided to use a custom UIView subclass - NodeView to represent the nodes. To make it moveable, I used the CocoaPod SEDraggable.

I decided that the whole graph would be drawn by another custom UIView called GraphView. This class has a graph property and when set, it will draw the whole graph by first removing the subviews from the previous graph (if any) and then adding new NodeViews. Then I will call setNeedsDisplay which will in turn call draw(_:).

In the overridden draw method, I will draw the lines (edges, in graph theory jargon) between the nodes.

Now I need to detect that a node has been moved. When they has been moved, I need to call setNeedsDisplay to redraw the lines. I looked around and understood that this can be done with KVO. However, my graph view needs to be able to display a graph with an arbitrary number of nodes. This means that I will put my nodes into an array. According to this post, KVOing an array is not possible.

Then I tried to use NSNotificationCenter but I soon found out that there is no notification for when a view gets moved from this post.

Then I found this post but the OP uses KVO which can't be used in my case for aforementioned reasons. Also, that post is in Objective-C which I can't understand very well.

How can I create a graph like that? Am I going in the right direction? Or is there a trick that I can use that does not involve redrawing everything when the nodes are moved? I feel like redrawing the whole graph every frame will be slow.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • According to your "... According to this post, KVOing an array is not possible.", do you think that declaring the array as property observer could help? please note that you would be able to: https://stackoverflow.com/questions/41120656/how-can-i-observe-a-specific-element-with-swift-collection-types-using-property. Not sure if this would help or even give a hint, but I hope so :) – Ahmad F Apr 12 '18 at 21:21
  • @AhmadF what's the point of observing the array? It's not the array that changes. The array still has the same number of NodeViews. The frames of the NodeViews change, so I should observe *them* right? But I can't since they are in an array. – Sweeper Apr 12 '18 at 21:24
  • What if you declared each object in the array as property observer? would it make any sense? – Ahmad F Apr 12 '18 at 21:41
  • @AhmadF I need the key path to the `frame` of each element in the array to that, right? But I don't think array elements have key paths. And isn't this the same reason why you can't KVO an array of things? – Sweeper Apr 12 '18 at 22:00
  • Also, I think there should be a canonical way of doing this right? I mean redrawing things as a view moves. I feel like detecting that a view has moved and then manually call `setNeedsDisplay` is kind of "low level". Isn't there a higher level way to do this? – Sweeper Apr 12 '18 at 22:06

1 Answers1

0

I figured out that I can do this with CADisplayLink. Instead of redrawing the lines when the NodeViews move, I redraw them every frame.

// displayLink is a property of the GraphView class
displayLink = CADisplayLink(target: self, selector: #selector(updateNodePositions))
displayLink.add(to: .main, forMode: .defaultRunLoopMode)

// ...

@objc func updateNodePositions() {
    // nodeViewsToNodes is a dictionary storing all the NodeViews as keys
    // and the corresponding Node (my model object) as values. So here
    // I am basically updating the model based on the view's positions
    for kvp in nodeViewsToNodes {
        kvp.value.x = kvp.key.frame.midX
        kvp.value.y = kvp.key.frame.midY
    }
    setNeedsDisplay()
}

The draw(_:) method will then draw the lines according to the model.

Sweeper
  • 213,210
  • 22
  • 193
  • 313