6 ms·
Hmm I think the difference between a primarily imperative API like `addSubview` vs a primarily declarative one (“what’s the list of subviews at any given moment
by danabramov 8y ago
Hmm I think the difference between a primarily imperative API like `addSubview` vs a primarily declarative one (“what’s the list of subviews at any given moment?”) is a pretty important differentiator. It has nothing to do with the browser DOM — it’s about the programming model. I tried to express that in the article but maybe I didn’t convey my point well enough.
- mpweiher 8y ago> `addSubview` That's been my revised working assumption, but it's almost completely unclear from your writings, and also comes from a fairly deep (but understandable!) misunderstanding of GUI frameworks. addSubview is not a defining feature of most GUI frameworks. drawRect is. addSubview is used once in construction, and then you're done. And a lot (if not most) of the time it is hidden, because you just load a GUI definition. For example in Cocoa, you define your GUI in Interface Builder. IB saves a nib/xib, which is an serialised object graph with parameters. You then load that nib and voilà, there's your GUI! The GUI is fully static/declarative. It reacts to dynamic content by drawing it in its "drawRect" method. So where does the misunderstanding come from? It comes from recent changes in how developers (ab-)use GUI frameworks. I haven't full grokked how this came about, but it seems to be part (a) widget toolkits (b) horrible drawing APIs and (c) the iPhone with LayerKit/CoreAnimation. This change has been that for example, when there is just some dynamic text to draw, people have been using text-label widgets instead of just drawing the damn text in drawRect. So suddenly you have people constructing, adding and removing views at runtime as a matter of course, rather than as something done in exceptional circumstances, which I gather is what you (rightly) object to. However, this is not the "programming model" of GUI frameworks, it is an abuse of those GUI frameworks. Which is why your idea that the difference is about programming model, while understandable and somewhat defensible, is ultimately mistaken. To put it succinctly, people are "drawing with widgets", instead of drawing in drawRect: like they're supposed to. So instead of drawRect, they are using addSubview to draw. However, widgets were not meant as a medium for drawing, they were meant as mechanism for constructing the (mostly) static UI that then draws itself, including the dynamic parts. As it is not really the supported way, it is cumbersome and error-prone. If you were to actually adapt the framework APIs to a "drawing with widgets" model, every view would have a "drawSubviews" method in addition to or in lieu of the "drawRect" method. See also: UIs Are Not Pure Functions of the Model - React.js and Cocoa Side by Side https://blog.metaobject.com/2018/12/uis-are-not-pure-functions-of-model.html https://blog.metaobject.com/2018/12/uis-are-not-pure-functio... There is also a deeper pattern there, which goes back all the way to the beginnings of computer graphics: the back-and-forth between "object-oriented" graphics (see GKS[1], PHIGS[2]) and immediate-mode graphics. (Note that this is not OO in the computer language sense, but in the computer graphics sense). Everybody, it seems has this idea that it would be nice to have a declarative tree of your graphics (and it also happened historically as a result of display-lists for vector graphics). It would also be nice to have a reusable library/API for this. Enter GKS/PHIGS. But then it turns out that things don't quite match up, so you end up having to express your domain graphics as complex (sub-)trees. So you need to imperatively edit the shape tree/database. Which is complex, painful and error-prone. In the end, it becomes easier to just drop the entire shape database and re-create it from scratch every time. At which point the whole idea of a shape database becomes somewhat moot. Enter immediate mode graphics. See OpenGL, Postscript, Quartz, etc. However, drawing everything procedurally is cumbersome. So you add back structure and composable elements. So let's have them be domain-/application-specific, draw themselves in immediate mode and also handle interaction. We might call them "Views". What's neat about Views is that they straddle the "object-oriented" and "immediate" graphics divide, and you can decide yourself where you want to be. You can shift more to the object/widget side and use object-composition, or you can shift towards the immediate-mode side and use procedural drawing. Best of both worlds, at least in theory. And then things happen that make people shift towards the object-graphics side (sucky graphics APIs, phone UIs etc.) and lo-and-behold, we have the same problems that we used to have with GKS/PHIGS! And then we propose the same solution (modulo environmental and accidental differences). And round and round we go. [1] https://en.wikipedia.org/wiki/Graphical_Kernel_System https://en.wikipedia.org/wiki/Graphical_Kernel_System [2] https://en.wikipedia.org/wiki/PHIGS https://en.wikipedia.org/wiki/PHIGS
- danabramov 8y agoAh ok I see what you mean. Well yeah, I’m talking about how it’s being used in practice. drawRect is a primitive but once you start dealing with layout and text measurement I think it can get hairy and at that point you might end up with imperative subview soup again. Somehow people using React don’t fall into that. drawRect is low level because it only specifies rendering. But UIs usually care about local state and when to destroy or create it. Especially in lists. That’s something I mention in the post which React has a solution for but I don’t think drawRect is sufficient for expressing this generally. See the ShoppingList reordering example.
- mpweiher 8y ago> being used in practice. And that's great. But then please argue/describe from practice, and not from some largely mythical fundamental differences in programming model that only confuse. That would be really helpful, thanks. > layout and text measurement Yup, as I mentioned, the text APIs in Cocoa/CocoaTouch/Quartz are so rancid that just slapping on a TextLabel is incredibly more convenient, despite the fact that you get horrible messy subview soup (I like that term, can I borrow it?). The solution would probably be better text APIs. Which are actually not that hard to build. https://github.com/mpw/DrawingContext https://github.com/mpw/DrawingContext (Alas, the text stuff in particular is only partially complete, I had more important projects. The very rough idea is to be able to essentially printf() into a view) > drawRect [..] only specifies rendering. Yep. > But UIs usually care about local state Right, that's why drawRect is embedded into these things called Views, which have local state. > Especially in lists. Right. And you have easy-to-use Views like NSTableView that handle lists beautifully, without you having to worry about the active set of subviews. Essentially you give it a data source and it will ask the data source about what it needs, when it needs it. Meaning it can handle arbitrarily large/infinite lists without problems. There are layers of customizability, from just delivering data via specifying cells to customise drawing/interaction all the way to having the NSTableView use arbitrary subviews to represent rows/columns. https://developer.apple.com/documentation/appkit/nstableview?language=objc https://developer.apple.com/documentation/appkit/nstableview... No new programming model required, just a view within the current programming model. And of course if you create your own views, they handle both the drawing (drawRect) and the interaction. https://developer.apple.com/documentation/appkit/nsview?language=objc https://developer.apple.com/documentation/appkit/nsview?lang...