Mac Dialog in Auto Layout vs. SwiftUI
On Mastodon, we had a discussion about whether you are more or less productive with SwiftUI or UIKit/AppKit. Der Teilweise (@teilweise@layer8.space) chimed in with an actual, measurable benchmark: a flexible-width window, with reflowing text, and equal-size buttons. Doable in 10 minutes. Can SwiftUI beat this?
[…]
Here’s the reference implementation on GitHub.
[…]
- Richard Kolasa got pretty far in 6mins, but the default SwiftUI window has too much free movement :)
- Mike Apurin points out that window resizing is hell (Code).
- Ryan Lintott shared a solution that reflows the text and increases window height properly (Code). Uses his FrameUp library to help with the layout.
Debating whether SwiftUI is “production-ready” in 2024 is an eye-roll argument, though. It has, limitations, sure, but what doesn’t?
“Production-ready” is probably not the right question. It is clearly being used in production. But you could look at how easy it is to make standard Mac layouts that were straightforward with the old system. In this particular example, it seems like sort of the anti-Perl: easy things made hard but possible. If you look at the SwiftUI layouts in Apple’s apps, they generally don’t sweat these details. SwiftUI started on watchOS, which doesn’t really have resizable windows and where buttons are usually full width and stacked vertically. On the other hand, there are certain types of layouts and data flow that are easier in SwiftUI.
Sadly, the way
windowResizability
works makes it impossible to implement this without aGeometryReader
or aLayout
, and even then it’s a bit janky. SwiftUI needs something like the compression resistance priority here.
Today I’d say SwiftUI has its pros and cons. I still doubt that it is better than UIKit/AppKit. It’s (just) different.
I like the good things about Autolayout more than I like the good things in SwiftUI. And I hate the bad things in SwiftUI passionately while I have made peace with the bad things in UIKit.
(We all thought Autolayout errors were the worst possible …)
there is, of course, no way to build a UI that changes with content using springs and struts. it’s all top-down constraints.
that said, here’s my quick UI:
- keeps the buttons the same size.
- maintains correct spacing.
- margins don’t scale, just buttons.
there’s not much to it, so it went pretty fast.
I added an AppKit target to the @broadcastsapp codebase just to experiment, and of 245 source files only 11 are portable All the pre-existing SwiftUI code is uncompilable, because even basic things like size classes don’t have a macOS equivalent or translation. I continue to believe that the AppKit SwiftUI target is simply a dead end and needs to be rolled into the Universal app platform instead. I don’t want to continue with this bringup experiment at all.
Another thing that “should be easy in macOS SwiftUI but isn’t”:
Reopening a window after closing it, with the correct position, size and split view pane sizes.
It works correctly if I quit the app and restart. Window is restored with previous metrics (automatically stored in UserDefaults).
It does not work correctly if I close the window and click on the Dock icon which opens a new window. I expect the new window to have same metrics as the one I just closed, but nope.
Khoa:
This same code runs fine on Mac but causes severe hangs on iOS
Not me implemented 90% of functionality with the new SwiftUI API, only to learn the missing 10% is impossible because it’s missing configuration, and now rewriting it to something else.
it’s just annoying that SwiftUI API is so very closed. one can rarely add missing piece without reimplementing a thing from the ground. That aspect is very much the opposite of UIKit etc.
I would say it’s “make easy things easy, and hard things impossible” philosophy
My SwiftUI view and its containing NSHostingView/NSWindow are not getting along, and I think it’s an Apple bug.
Seems like everyone here is stumped, just like I was.
Does SwiftUI REALLY not support basic table-cell animation after four years? I assume it’s me, not the framework…
SwiftUI is terrible when it comes to reliability for building any custom interactive component. Random things just break in the next release. If you want to build fluid, interactive designs for iOS that are rock stable, stick with UIKit.
Still experimenting with SwiftUI for Macintosh layouts because some of the toolbar item placement options and controls just don’t have the flexibility they do in AppKit.
Keyboard shortcuts for toolbar items not showing when holding the ⌘ key on iPad: am I doing something wrong or is it just another “not yet supported in SwiftUI” thing?
So of course the solution was obvious (sarcasm)! Adding .labelStyle(.titleAndIcon) to the Label is the way to go. So many SwiftUI dark patterns…
In my previous snip, I used a cheeky Threads knockoff interface to demonstrate the subject matter. It occurred to me how crazy it is that I can even do this. SwiftUI has done away with so much work that used to be pervasive in interface programming, interface builder or not.
SwiftUI is awesome for starters… just until you get some strange error on a ForEach: “Cannot convert value of type ‚[XYZ]‘ to expected argument type ‘Range<Int>’” that worked perfectly until you wrote the wrong action code in a Button somewhere.
So many weird bugs in SwitUI, e.g. adding
contextMenu
steals all touches from the underlying view instead of just around that view, thus making overlay components break underlying UI interactions…
No macOS SwiftUI component has let me down as much as List. Just when you think you’ve got it working well, there is always some tiny issue relating to reorder, DisclosureGroup expansion, highlight or layout.
I’ve been doing some experimentation with SwiftUI on macOS 14. Things are working amazingly well. I have yet to find anything weird, broken, or nonfunctional. Interop with AppKit world is excellent.
Here’s a surprisingly hard thing to implement in SwiftUI: a panel that contains a resizable image that looks right on both iOS and tvOS..
Things are easier in SwiftUI, except debugging, debugging sucks.
I’ll be adding to this post as I go along as a way of documenting some of the “fun” bits of SwiftUI[…]
Really frustrating to spend the day reworking on macOS UI, getting it really responsive and snappy, and then the exact same SwiftUI code on iOS and iPadOS runs like sludge.
Apple completely removed the ability to open the SwiftUI Settings scene using legacy
NSApp.sendAction()
method using theshowSettingsWindow:
(macOS 13) orshowPreferencesWindow:
(macOS 12 and earlier) selectors. The only available method of opening the Settings scene (apart from the App menu → Settings menu item) is to use the newSettingsLink
view.This presents two major restrictions:
- There is no way to detect when the user has clicked this button if additional code is desired to run before or after the opening of the
Settings
scene.- There is no way to programmatically open the
Settings
scene.
I blame the annual cycle in part for the fundamental problems in Swift and SwiftUI. There’s a proposal to add a feature for property specific inits (a bonkers idea). It exists to patch a problem with macros, which fixed a problem with property wrappers, which were added to allow SwiftUI to subvert value type semantics. I believe if Apple had spent more time dog fooding SwiftUI it would be very different. No amount of pausing to fix bugs will allow Apple to fix the fundamentals.
I still can’t get over the fact that in SwiftUI you can’t create a Table with a dynamic set of columns. And I don’t mean reordering or resizing. I mean: you can‘t use
if
orForEach
when creating table columns. ☹️
We’re in year 4 of SwiftUI and building a good multiplatform document based app is still hard to impossible.
Building a VERY simple document based app, with DocumentGroup and TextEditor, like the sample is easy. But doing more than that is hard: DocumentGroup with NavigationSplitView? Forget it. Customizing document creation? Forget it…
Today is the day on which I realize:
I like working in SwiftUI more than I like building programmatic interfaces in AppKit.
It’s not even close. I’m really annoyed by all the wiring[…]
This chain speaks to me so much! I’m in the process of reducing SwiftUI usage in my only macOS app because it just seems to fall apart. I find AppKit tedious, but reliable, and there’s ~always~ a solution. Even hacky solutions in AppKit/UIKit never feel as fragile or hacky as SwiftUI hacks.
SwiftUI has really helped make Mac development more exciting (for me), and further cements how far outa the race Xojo is. At this point, I don’t think there is anything Xojo can do to catch up, but I believe they know that already.
Sure it’s not without it’s downfalls, adapting some of the work flows to the SwiftUI way, can be a massive paradigm shift, and sometimes feels like hacky spaghetti code. Alerts for instance feel unnatural, but I can see why they’ve done them this way.
Sometimes, I wonder if I’ve made a huge mistake by tackling a giant rewrite of my 10-year-old ObjC/UIKit app in Swift/SwiftUI that’ll probably end up costing me ~1.5 years of development time.
Then I try to do ANYTHING with ANY new API in the old codebase from Objective-C and UIKit.
It quickly becomes apparent that this rewrite was not much of a choice — it’s a necessity.
SwiftUI Mac, changing focus from one TextField to another marks the document as “Edited”, even when the text wasn’t changed in either TextField.
We achieved a lot more stable code with Swift (in comparison to Objective-C; I know it’s discussable)… but we lost so much with SwiftUI!
SwiftUI/EnvironmentObject.swift:90: Fatal error: No ObservableObject of type BottomBarViewModel found. A View.environmentObject(_:) for BottomBarViewModel may be missing as an ancestor of this view.
Theoretically SwiftUI previews in Xcode are a good idea. But if it takes A LOT longer to just view this preview than to start the app and navigate to that view, or even “Failed to launch app “XCPreviewAgent.app” in reasonable time“… it’s just not helpful.
One of the things I hate about SwiftUI is the crappy visual editor. It’s light years behind what we had for Storyboards. It’s not even as good as the original Interface Builder was.
Even in Visual Studio (201x!) editing of Windows Forms is way better. And that, too, parses code to build the UI! Change a line of code in the *.Designer.cs, switch the view: Boom, there’s you UI.
I didn’t expect that, but SwiftUI.TextEditor lagging on edit for relatively short text. I can feel it lag while I’m typing letters.
I came to the conclusion that SwiftUI.textEditor refreshes the entire content each time, I think the same happens for TextField also. Wrapped NSControls don’t suffer from the same problems.
Well, refreshing everything each time is kind of the underlying model.
See also: Accidental Tech Podcast.
Previously:
- Dragging From a List With SwiftUI
- Keyboard Cowboy 3.19
- TimeStory Dev Journal
- AppKit vs. SwiftUI: Stable vs. Shiny
- SwiftUI Data Flow 2023
- InterfaceBuilder.swift
- Thoughts on SwiftUI After WWDC 2022
- SwiftUI Equal and Ideal Sizes
- Emulating Equal-Size Constraints in SwiftUI
Update (2024-03-14): See also: If Not Nil.
Update (2024-04-08): Howard Oakley:
Another fundamental problem I’ve run into is the app life cycle. Many Mac apps start with a brief initialisation phase, during which they might check required resources, look online for app updates, and load app-wide preferences and menus. Once those are complete or under way, the app turns to the task of opening documents and their windows, perhaps. Then, when the user quits the app, there’s a completion phase in which any last changes to settings are made, and the app gracefully exits. When using AppKit, these are normally accomplished in an AppDelegate, but Apple cautions against that for SwiftUI. In a box marked Important, it states: “Manage an app’s life cycle events without using an app delegate whenever possible.”
For someone porting an app from AppKit to SwiftUI, that comes as a bit of a bombshell, and implies that Apple hasn’t worked out exactly how to convert common AppDelegate functions into SwiftUI’s App protocol. After five years, that’s more than a little unhelpful.
Update (2024-06-04): Ryan Lintott (via Der Teilweise):
FrameUp updated to v0.8!
New
.equalWidthPreferred()
and.equalHeightPreferred()
modifiers.
This looks like a great way of easily handling a common case. Something like this should be built into SwiftUI.
Update (2024-06-12): Dylan:
My biggest problem with SwiftUI right now is how terribly under-performant it is. Apple’s most powerful iOS device ever, powered by the cutting-edge M4 chip, can’t do this layout animation without horrible choppiness and hangs.
14 Comments RSS · Twitter · Mastodon
I gave up on Xcode in the early 2000s. I could write code using NSMUtableArray, but Apple didn't bother including an implementation in a library. I had to write my own mutable array code to get my project working. I assume Apple has fixed this, but they lost me. I wonder how many people are going to give up on SwiftUI?
UI work has gotten harder over the years largely due to tooling and APIs getting worse.
I find I am most productive using springs+struts and a combination of container views with bits of autolayout. I use IB wherever possible, but if a layout is easier to code I will do that.
Springs + struts solve 80% of my layout needs and do so quickly and understandably. StackViews are good, too. Constraints are fine for specialized cases, like 1 or 2 controls or containers that need centering or specific offsetting or tracking.
If I have to set up 20+ constraints, it's more complicated than it's worth and I usually just rethink and simplify the layout. In some cases I find it easier to measure text and calculate frame rects manually, which is more intuitive to me, faster, and less error prone than figuring out which constraint subtlety breaks everything or which 8 constraints need to be redone if I ever make a change.
IB has been absolutely neglected. NSGridView should be great for static layouts, but IB and its bugs make it harder to use than it should be. It's also never been good with constraint-heavy layouts (especially when adding or removing a view), which is often your hint to throw things in a StackView and move on with life.
The frameworks remain the biggest pain point. The deeper ViewController classes like NSSplitViewController and NSTabViewController have subtle bugs or automatic behaviors that you cannot easily disable or tune. How many man-hours have been lost fighting UINavigationController and UINavigationBar to get basic behaviors or appearances? It's also fun when in certain contexts the system adds constraints that break yours, because something was undocumented or there's some other bug.
The selling point of using Apple's frameworks is that you inherit the work they've done, but you often inherit so many bugs that writing from scratch for your own requirements might be better.
—
I have a negative interest in SwiftUI. Every blog post that shows "this amazing thing you can do with 15 lines of SwiftUI" is usually highly specialized and could be a future AppKit or UIKit control or behavior. Writing a similar AppKit/UIKit subclass usually won't lag beyond 100 items, is usually back-deployable, and usually won't break in a random OS update. I also understand and respect the principles of the HIG more than SwiftUI (or modern Apple employees) ever will.
Everything about SwiftUI is the opposite of what I want. It feels like the frameworks and tooling are being sacrificed or deliberately neglected to push devs into the Swift+SwiftUI prison complex.
It's fascinating to see how things get some spotlight years later.
And that’s what I’d like to make clear: My statement that SwiftUI isn’t production ready is from 2020.
Four years ago, SwiftUI was a whole different thing. Today I’d say SwiftUI is a solid 1.x (for small values of x).
It shines in parts and it has its problems but so do UIKit and AppKit.
@Daniel I'm curious what are the problems that you encounter in UIKit? As someone who has been using UIKit since iOS 3, very rarely do I run into a UIKit bug. I've found these days when doing code reviews that most problems developers face with UIKit is because they chose the wrong tool for the job.
That, or they are just too lazy to take the time to read the documentation and understand a particular API. A classic example is Auto Layout. People complain that they don't understand the error messages it spits out in the console, only to find out that said individual never took the time to learn the Visual Format Language. Countless times I've had to explain that the Auto Layout errors in the console will represent the issue with the Visual Format Language, and so how the hell can you possibly expect to understand what the error is telling you if you refuse to take the 20-30 minutes necessary to learn it? Once you know the Visual Format Language, a majority of the Auto Layout errors in the console then become obvious.
For all the "problems" people these days claim to exist in UIKit and AppKit, those frameworks certainly led to the creation of quite a number of legendary apps on Apple's platforms. You look at some of those original iLife apps all the way up to the Pro apps like Final Cut Pro. Many 3rd party apps as well like Delicious Library and TweetBot.
In the past 4 or 5 years with SwiftUI, what can we point to as the crown jewel of apps built with SwiftUI? The Settings app that barely functions properly?
I think it's time for Apple to decide to decide. Do they want to keep throwing away development efforts to a Framework that took 4 years to get to a "1.0", despite the fact that they already had a battle tested, production UI framework? Imagine all the improvements we could have had to UIKit during that period had those engineering resources not been involved in trying to build another UI framework that just was not necessary. SwiftUI is starting to feel like the "Project Titan" of UI Frameworks.
As nice as it is that the Visual Format Language _exists_, I don't think "just learn this syntax that absolutely nothing else in the world uses, and Apple itself hasn't touched in a decade" is a great argument in favor of it.
I’m not quite following? That because Apple’s AutoLayout APIs and visual format aren’t available on say Android, or other platforms, that learning it is somehow optional or not worth one’s time? If one wants to be a professional (and competent) Apple platforms developer, I would expect them to learn something as fundamental as layout. Does anything else in the world use Swift UI? Should we not learn Swift UI because nothing else uses it?
I think that comment just makes my point with regards to many of the issues devs these days have with UIKit. It’s because they just don’t want read the docs, and take the time to study. They’d much rather fight the frameworks, with the particular example I chose being AutoLayout. And lo and behold the response I get is very similar to the many excuses I get from devs while doing code reviews, who would rather complain and whine about how hard AutoLayout is rather than taking the time to practice, and perfect their craft.
“AutoLayout is lousy! You get these terrible error messages…”
“Have you taken the time to learn the visual format language? It makes reading and understanding those error messages much easier to understand and resolve.”
“Why would I do that!! No one else uses the visual format language and Apple hasn’t changed them in a decade!”
Apparently APIs have an expiration date. If Apple doesn’t change them within a certain time period, they go bad.
> If you look at the SwiftUI layouts in Apple’s apps, they generally don’t sweat these details
Is the print panel using Swift UI? It has the same design as Settings but using the view debugger in my app shows it using just AppKit.
@HSkept That’s interesting. I’ve been told it’s SwiftUI, and I thought AppKit didn’t have the main form control that the print panel uses. Maybe it’s hybrid?
I have a separate branch for SwiftUI UI, so that when I may finally migrate, the trip will be less painful.
It was working great for a year.
And so, after Xcode 15.3, the whole friggin thing is broken.
Previously, it worked great.
It will compile fine, but crash on launch, and no helpful debugging either.
SwiftUI is a friggin mess.
It can blow up in your face at any moment, and neither Xcode or the debugging tools will give you any warning before it happens.
Get your act together Apple.
@Michael, I do not understand how this is implemented, but the print panel appears to be 100% AppKit built with no Swift UI at all. Additionally they are regular AppKit views and not distinct subclasses or having special NSAppearance names.
> It was working great for a year. And so, after Xcode 15.3, the whole friggin thing is broken. Previously, it worked great. It will compile fine, but crash on launch, and no helpful debugging either. SwiftUI is a friggin mess.
It can blow up in your face at any moment, and neither Xcode or the debugging tools will give you any warning before it happens. Get your act together Apple.
This is exactly why I want nothing to do with SwiftUI. When you implement UI in AppKit (IB or programmatically) it may take longer initially but it’s essentially done when it’s done. There are nibs 10+ years old that work just fine now (or will work with minimal changes). I don’t want to rewrite UI spaghetti code every year.