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
_______________________________________________
swift-users mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-users