Flexible Protocol-Oriented / Value Type Architecture for KVO/KVC?


(Jon Hull) #1

Not sure if this is the right place, but I could use some help figuring out best practices for architecting protocol-oriented / value type frameworks in Swift, especially where I used to use KVO/KVC in objective C. Apologies of the long email. It is a very complex project and I am trying to give enough background to understand the context.

I am writing a vector/drawing framework in Swift. It has the capabilities of something like Sketch combined with special shape support (similar to Affinity Designer). I have a specific need which I am writing it for, but I have about 4-5 other ideas which could use it as well, so I am trying to stay as general/flexible as possible.

I am very happy with the architecture for the actual drawing part. It is protocol oriented, and very flexible/powerful. I am a little stumped on how best to add interactivity in a similarly powerful/flexible way though…

I have written large editors before in ObjC, and what I typically did there was have self-generating UI which connected via KVO/KVC (I basically made my own bindings which also generated the necessary UI on the fly). This made the system extremely easy to add new elements to, which meant it could be iterated upon quickly. I want similar capabilities in my Swift framework, but I am struggling.

There are 2 main aspects of interactivity:
1) The drag handles - These are different depending on the type of object (e.g. rectangle vs bezier path)
2) The inspector controls for the selected object (e.g. the line width) - again different based on the type of object

So far, the best approaches I have come up with use blocks that get passed to/from the object in order to keep things decoupled. For example, for hit testing I have the following protocol method which takes a point and an “inclusion block” which takes a result and returns whether it is an acceptable result.

  func localHitTest(pt:CGPoint, inclusionBlock:(HitTestResult)->Bool) -> HitTestResult?

The object then gives the block increasingly detailed descriptions of how it was hit, starting with “my bounding box was hit” and then talking about what part was hit, or if it was the background of a group that was hit, etc.... You reject the more general result using the block to receive more detailed ones, or you accept the general ones for speed if you don’t care about the details (or return nil if it wasn’t hit). This allows you to have different behaviors when the user single vs double clicks, etc… by providing different blocks (e.g. single click may select a group as a whole and double may select an object inside of it).

For drag handles, my current approach is to try something similar. The object has a method which vends all possible drag handles, and each handle has an array of all possible operations it could perform. All of this is narrowed by an inclusion block:

  func dragHandles(inclusionBlock:(ActionEffectType, DragHandle.HandleType)->Bool) -> [DragHandle]

The “ActionEffectType” is an enum defining the effect of the operation so that the tool can choose the desired effect. Different tools may have different handles they want to display, and things like the option key may change which operation is being used (e.g. corner resize vs. symmetric resize around center).

The operations are all structs with blocks that take a delta (for where the handle was dragged) and return an updated version of the object. This part feels a bit off/inelegant to me.

It seems to work well enough so far, but I would really like to find a way for extensions to add handles as well (right now the object has to define them all in a single method). I am also making it up as I go along, so I am not even sure if I am on the right track. I could be missing an obvious/better solution…

For the inspector controls, I don’t have anything working yet, but I am currently thinking of using something similar to the operations above. That is, the object would vend an array of properties which can be altered, and attached to each one would be a block which knows how to actually change that property. I can then build UI based on the property types and call the blocks when the UI is edited (and ick… regenerate the UI whenever the object is changed).

That is my best attempt (currently) at getting KVO/KVC-style flexibility.

Anyone have better solutions/approaches to any of the above?

Thanks,
Jon