Jim's Depository

this code is not yet written
 

History

About 20 years ago Apple rolled out AppKit, the NS* world of user interface programming, though it was already about 10 years old.

About 13 years ago Apple rolled out UIKit, a vaguely similar but significantly altered child of AppKit for the iPhone.

Over the years those frameworks morphed slightly under the coding fashions of the time. Extension by subclassing gave up some ground to extension by delegation, layout went from "chains and springs" to a constraint solver system, the primary client language changed from Objective C to Swift. Warts and debris accumulated in the APIs and backwards compatibility kept them forever.

In recent years, Apple has stopped maintaining and producing the high level documentation which describes how to use a subsystem. They still have API level documentation which describes each individual microscopic fragment, but they no longer produce the fabulous high level documents which tell you how to use an API to solve problems, and show you which areas you should be using, and which are leftovers.

The result is that in 2020 I pity anyone learning AppKit or UIKit. The web is filled with people asking questions and getting answers of varying quality, frequently low, which then become silently obsolete.

Present

18 months ago Apple released SwiftUI. This is a clean start. Not having any pressing new UI needs I decided to let it stew for a year before looking at it.

In a nutshell SwiftUI eschews graphical or imperative code creation of user interface for a terse "in source" declarative approach. Here, terse is largely accomplished with a few of language tweaks that let return be omitted at the end of a function and let let x = X(); x.add(A); x.add(B); x.add(C); return x be written as X { A B C }. It's a lot nicer to read. Writing is weird until you read how function builders work about 12 times, but you can copy-paste your way into an interface even if you don't understand why. Remember: a View is just a regular struct until you get into the body initializer then you are concatenating Views without commas between them

What I Built

I built a cross platform iOS/macOS app with a widget to display some telemetry of remote battery voltages, weather conditions, and a camera picture. It took me about two days to learn enough SwiftUI to make the simple non-interactive widgets and display screens. A fair bit of that was working out WidgetKit which isn't hard, but it took me a while to realize that because my data was driven by realtime data I could just chuck most of the complexity and not hurt my energy consumption story.

The Good

  • The user interface is nicely legible in the source files. It says what it does and does what it says.
  • Making a Widget, with its inherently simple display is really easy. (maybe)
  • Cross platform is beautiful! I essentially paid no attention to it and things just worked. I never once wanted to fly to Cupertino and find the people that think we don't want something to abstract UIColor and NSColor, and UIImage and NSImage for us poor cross platform app writers.

The Bad

  • Those syntax shenanigans SwiftUI uses to look really clean? They really hurt the compiler and Xcode's ability to produce useful error messages and code inspection information (like throwing down the template arguments as you type a function call). I lost track of how many times Xcode threw up its little hands and asked me to file a bug report while I typed. Lesson: Move everything except the view layout itself to a different file.
  • It looks simple, because it is simple. In many ways SwiftUI is in that early project phase where they just punt on the hard stuff and tell you to live with the simple way, it's good enough. And if your app's visual can be "good enough" or rejiggered into something that SwiftUI can do, then good for you.
  • You may not be able to reach excellence. I draw strip charts of time series data, they look a lot like they come out of an old pen strip recorder. They have horizontal and vertical scale lines which are generally as thin as possible on the display. This requires that the lines be aligned to the display pixel grid or some of them become fuzzy from being smeared over two rows of pixels. You don't notice this with thick lines, but hairlines need to handle this. As near as I can tell, there is no way to find out my View's position in device coordinates to then make my drawing come out right.
  • Not all Views are suitable for deployment to customers. On the Mac, the HSplit appears to have a 1 pixel wide hit zone for moving the divider and no visual indication that the split is moveable. The TextEditor leaves behind visual artifacts if you start selecting text and drag up and down. These are problems that I can't fix without source code or just writing a replacement.
  • You can draw, sort of, there is a Path view that draws a path, and you can absolutely place Text, but only by their center point. I rebuilt my stripchart view, but it works by computing a Path for each line width, color combination and drawing them in a View, then an array of Text with absolute placements to put the labels on. It's gross. Maybe I should have just wrapped my UIKit stripchart and gone on, but this works cross platform.
  • If you do not embrace Combine, you will likely have a lot of parameters being passed up and down your View trees.
  • Swift's inability for let members of a struct to reference each other really hurts when you have related data. I get it, there are reasons, but I'm pretty sure a topological sort in the compiler would take care of that. I dealt with it by making new objects which encompass all the other objects and taking care of the relationships in that init.
  • There are things which will break you. Consider the Label() type. It easily lets you pair an icon and a string. Just the thing for lots of lists. You see them everywhere. Now try to use one outside a list, because say you are a postage stamp sized widget that doesn't have interaction. Suddenly your VStack of Labels looks like garbage because the text no longer lines up and the icons aren't centered over each other. Not illegible, just sloppy. Can you fix it? There is no high level documentation from Apple, so it's off to the internet where possibly with a combination of Geometry and AlignementGuides (which are a little pile of hack) you can probably get the text in line and the icons to right align, which is better than nothing. About 50 lines of obtuse code.
  • No problem, don't use VStack{ Label() }, use a Table. They didn't make a table. I really can't fathom why unless someone was so scarred from the HTML <table> wars or intimidated by the "displays 100000 rows just fine" versions in the older APIs. Developers want to be able to have a few columns and a dozen rows to organize our displays and data. Give us some column sizing, and alignment options (including radix point, we have needs) and lets us get on with life. I don't want to use a native table, I'm trying to be cross platform here, remember "We love the Mac." (As quoted by Tim Cook twice a year at WWDC and Mac product release.)

So What Does a Grumpy Old Coder Do?

So, after all that frustration, the next day I started a new Mac app and chose SwiftUI because it really is easy and seductive and this app is mostly just for me to analyzer data and maybe a few other people. I'm not answering to a graphic designer with a Photoshop Phantasy user interface. It has to be nice, but if SwiftUI doesn't want to go somewhere, I have the liberty to not go there.

I'm displaying a couple of side by side files, source code and LLVM intermediate language generated from it. Compile the source, look at the LLVM and generated assembly code. Maybe some parse trees or internal AST dumps.

  • The App prototyping was glorious! Quick and easy to noodle about with.
  • @ObserableObject worked nicely for tying my internal data to the screen. Things Just Worked! Hit the compile button, replace the LLVM text, and the screen updates.
  • TextEditor fell on its face. Rendering bugs aside, it doesn't have the capabilities to do what I needed and no where to hook in and extend. SwiftUI does the easy stuff™
  • I wrapped an NSTextView, so goodbye any pretense of multiplatform, but that was never in the cards since its tied to a compiler anyway. I wasted about half a day learning to wrap and coming to grips with the Coordinator and where to keep my delegate and then that I really kind of needed to use Combine to sink my updates around, but after those hard lessons were learned it was pretty good.
  • Totally not SwiftUI, but if I click on a line in the source and I want to jump to the LLVM assembly that corresponds… I just feel I shouldn't have had to fight NSTextView quite so hard. I won in the end, but it took hours. My text is NSAttributedString type so it can have color and line spacing and whatnot, that makes everything like walking through mud.
  • I wanted little line numbers on the side of the NSTextView. Your API is older than your developers! This has to have come up. Fortunately you can find a solution in GitHub from Yichi Zhang. I was ready to waste a few more hours before declaring it a mess and giving up and his one file ruler class worked the first time I hooked it in! (I still had work on it a bit to use small, digit spaced text and to move the digits down to the baseline of the text. That's a little more LayoutManager and NSGlyph than most people want to know about.)
  • I got in a fight with the ToolBar. I just wanted my compile icon with the word "compile" under it because, really, when I said "compile icon" you visualized absolutely nothing. There is no picture for "compile". This is, sadly, too hard. The various toolbars will draw my Label() in different ways, none like that, except the style meant for Preferences which you can not specify in SwiftUI for some reason. It's all hard to work out because although all the API fragments are documented, there are no high level documents, so how to put them together and where to specify them is left as an exercise to the internet Oijia board. I could force it by making the toolbar item be a VStack, but then it renders poorly. After 90 minutes I gave up and make a Button with the text and symbol in it.
  • Adding a menu and menu items would have take 30 fewer minutes if Apple wrote the high level documentation.
  • I've harped on high level documentation, think about putting a NSTextView into an NSScrollView so it can scroll, probably a fair few bits to get right there. You can google it and get a bunch of answers, most either miss something important or do a lot of unnecessary stuff. But how about this: Putting an NSTextView Object in an NSScrollView A small amount of clean code, from people who know how the underlying parts work and can see their source code to verify assumptions. Look at that table of contents, it covers everything to get you up and going. Then you can work with any fiddly bits you need to customize for your situation. These documents require a team of engineers who can write and are expensive, but the number of man hours they save your customers is immense.
  • So at the end of the first day I had listings in parallel columns with syntax coloring. By the end of the second day I had clicking on source jumping to the appropriate spot in the LLVM, line numbers, assembly listings (goodbye sandbox, can't invoke clang from inside the sandbox without heroics). It's now the third day and although I still have a little list of features to add or try and reject, I'm writing this posting instead because my app is ready to use.

Conclusion

  • Use SwiftUI for Widgets. It's really your only choice there. If you already have code to gather your data, budget two days to make your first widget.
  • Use SwiftUI for quick-and-dirty internal apps where user interface defects aren't show stoppers.
  • Maybe use SwiftUI for the high level of a complicated app? Be ready to embrace Combine. I worry about Combine creating unfathomable interactions, but I haven't used it on a complicated enough project to know.
  • If you have anything complicated… table, styled text, drawings, be ready to drop into UIKit or AppKit.

Recommendations to Apple

  • Print out a banner that says "They do need that!" and hang it in the engineers' room (if we ever get to use rooms again).
  • Bring on a couple engineers and writers to write the high level documentation. Also get someone to bonk the coders on the head when I lookup API documentation for foo(bar:Int) and the detail page tells me that it is foo with an argument named bar of type Int which foos. I kind of was looking for a little more than that even if it seemed obvious to the coder when he hit add documentation in Xcode.
  • Send someone out across the internet for postings on "how to" and "swifui" and document all of the API usage questions people have in that high level documentation.