Hacker News new | past | comments | ask | show | jobs | submit login
SwiftUI Is Convenient, but Slow (alinpanaitiu.com)
223 points by mort96 on Nov 28, 2022 | hide | past | favorite | 189 comments



Other commenters have pointed to SwiftUI being declarative as a big part of this problem, but I take a more nuanced view: the problem is actually that SwiftUI is a complexity-hiding abstraction. Specifically it hides the complexity of when UI is refreshed and how it is refreshed. This results in a situation where engineers who don’t have a clear understanding of how it works resorting to essentially guesswork to try and fix performance issues. Now, there are certainly folks who do have a deep understanding of how SwiftUI works but that group is likely small and the situation isn’t helped by middling documentation and SwiftUI being closed source. An analogue to this can be seen in something like SQL, which is a (somewhat) declarative specification for queries that is also complexity-hiding. SQL, wielded by a novice, can be pretty slow (just one ill-advised join would likely do it), but would still “work”. The difference with SQL is that there is a plethora of literature about how to write it in a way that results in fast queries and many of the consumers of SQL are open source so enterprising engineers can dig down and figure out what a particular query is doing and why something is slow. We’ve also invested a ton of resources in creating engineers who can write good SQL. I’m interested to see how SwiftUI progresses in these regards.

Unrelated addendum: The author should look at the drawingGroup API. This could significantly speed up the drawing of the month grids on refresh by caching the resulting rasterized view instead of repeatedly redrawing it.


I think Swift UI's more concrete problem is that it just doesn't have a sufficiently mature enough API with the right mix of low level primitives and higher abstractions.

Say whatever you want about modern web development, but I think React/JSX + HTML/CSS has proven you can use a declarative model to build complex user interfaces.


This could be taken to support my point. There is a MASSIVE amount of literature on the internet about how to do this, and it's reasonably straightforward to see what is happening with the DOM when you use these frameworks. Likewise, a novice developer can create something that causes scrolling to stutter or other performance problems pretty easily in this world.

Take on the other hand, some of SwiftUI's more advanced features, like PreferenceKey which are extremely under-documented (the generic sounding name doesn't help either). Of course folks are going to struggle to do more complex things.

I wonder how much the situation would be helped if the thing you searched for help allowed in-line SwiftUI examples, like you can do on the web. Playgrounds is kind of that thing but you aren't searching a repository of playgrounds for documentation.


I think the web developer's toolkit has benefitted from competition - every browser prioritizes supporting dev tools because that's the only way to get more sites to support your particular quirks. people building web frameworks competed for developers ruthlessly, because starting to learn a new framework is only a few search queries away, and a great deal of effort has been made to improve the overall developer experience.


This is what I really like about Flutter. It's well designed with different layers of complexity and convenience tradeoffs. And yes, also open source.

Watch this talk from one of the core developers about the design https://www.youtube.com/watch?v=dkyY9WCGMi0


I love being able to control+click on anything in flutter to see how it was implemented.

In fact, Dart doesn't even support closed source.


I really like the SQL comparison. I'll remember that the next time I have to explain a non-iOS developer why a given piece of SwiftUI code is flaky or slow.


I feel like it is a mistake to bring what is effectively React to native development.

Browser-based web apps are basically punching above their weight class. Compared to the power of native UI's, the browser is broken. React understood this and created two innovations:

  1. The Virtual DOM
  2. The reactive UI
Now, I'm no expert, but Number 1 seems like a terrific idea; and Number 2 seems like it's needed in order to implement Number 1.

But, to my thinking, a reactive UI is a big problem of leaky abstractions, which is what I think you're saying.

To me, iOS/macOS UI development needed neither the baby nor the dirty bathwater. I feel like the whole thing is a mistake. As a programmer, I want control over updating the UI.


React is terrible at reactive[1] UIs. You have to do a lot of work to tell react what to not rerender. Of course things generally work if programmers aren't careful, but the UI gets slower and slower over time as more features are added and more repaints are done.

VDom is one of those arguable things, plenty of frameworks are faster than React w/o a vdom, and some frameworks are faster than React and also use a VDom.

[1] Arguably react is not even that reactive, you have to manually wire up everything yourself.


You have this backwards no? The virtual DOM is needed in order to support Reactive UI, in most cases (svelte comes to mind as an exception).


I think you're right, now that I think about it again.


For the record, you do have fairly fine grained control over what updates when with SwiftUI. The issue is that this isn’t obvious to new developers, nor is it easy to understand what tools to use to fix issues once you understand what is happening.


I really wish apple would spend more time on their tooling. A lot of the new features like async/await and SwiftUI have way less visibility than what came before. A lot of this stuff makes the code compact and much easier to read but when things go south trying to use the inspectors they have often doesn’t get you very far.


There are a lot of foot guns, like ObservableObject and @Published causing views that reference the object (even if they don't use it in any way) to be re-rendered that cause issues. And even Apple can't figure it out, which can be seen from the Ventura System Settings performance.

SwiftUI is nice for small apps and demos. But as soon as you add persistence, or try and do anything advanced the complexity balloons and you need to understand it completely. I think SwiftUI is just a bad abstraction with a lot of accidental complexity. It makes drawing much easier/faster for developers but all the state management stuff has become very difficult.


Apple can figure it out- they implemented @Published such that it contains a reference to it's enclosing ObservableObject which invokes objectWillChange() very liberally. The fact that they wrote their settings UI poorly is a statement about the developer's assigned to build those components and/or the lack of QA involved.

I do agree that ObservableObject/@Published should have been designed to prevent unnecessary re-renders. It's actually quite easy, and I expect Apple will include this in the future: Add an extension to @Published when the value type is Equatable and only send objectWillChange() when newValue != oldValue. I do something like this in my apps to eliminate superfluous re-renders, but the implementation is non-trivial due to an undocumented feature in Swift [0].

[0] https://github.com/apple/swift-evolution/blob/main/proposals...


Most databases have an "EXPLAIN PLAN" so you can see which (if any) indexes the database use and how tables are joined.

So this is much better than Swift.


drawingGroup is exactly what I was looking for, thanks a lot! I didn’t know this was added to SwiftUI.


I’m happy this worked for you. This in itself is an interesting anecdote about documentation quality. I knew this API existed but forgot the exact API and searches for “caching SwiftUI rasterized view” and similar queries did not return this API (which, for the record, isn’t exactly what it does…) . I eventually found it in a roundabout way but had I not been pretty sure it existed, I would probably have given up.


  > This in itself is an interesting anecdote about documentation quality.
apple used to make these great deep-dive pdfs for new frameworks like core data, quartz (core graphics), even obj-c itself that explained many nuances and caveats of each subsystem/framework...

imho i think swiftui could use a good doc like that


“Programming guides”!

Those were so awesome because they explained the whole system. The core graphics one was an absolute lifesaver when I started working with iOS. Apples UI a frameworks seemed way more complex than anything I’d ever worked with before but once I understood the layers they started to click. I don’t know what I would have done with a bunch of class guides with patchy comments.


same here, i would have been totally lost without them as well... read them end-to-end when i started out and never regretted it


The SQL comparison is a great illustration


It seems like every time a new UI framework comes around, a tradeoff is made between convenience and flexibility, with the advertising always being around "look how easy it is to make a table view", but this doesn't particularly impress me anymore, because as I've used these newer paradigms it seems like I am trading 5 minutes (UIKit) for 3 minutes (SwiftUI) when it comes to basic UI development, but later on trading 30 minutes (UIKit) for 3 hours (SwiftUI) when it comes to anything significantly complex. This is not just true for iOS but web as well.

I'm unsure how feasible it would be but it would be really cool if it were possible to benchmark frameworks both by how quick it is to implement basic UI components as well as more complex UIs and score them based on that.

I do feel like its quite an insidious trap to do a project to 80% completeness in a framework then be forced to make the awkward decision of "Do I continue with the current framework where the extra 20% will take a long unknown amount of time or rewrite in the old framework and take the time hit but with easily estimatable timelines?".


> "look how easy it is to make a table view"

My trap in web dev was "look how easy it is to do responsive layout".

Once CSS flex & grid became broadly available, I quickly began to shed my use of frameworks. It took me ~10 years of hard work to get to the point of feeling comfortable in a 100% vanilla web development ecosystem. MDN is my bible now.

The advantages of owning your entire web development vertical are impossible to overstate. The counter arguments are so painful to hear in 2022 - "why would you want to re-invent the wheel" kind of crap. The truth is, I don't write most of my vanilla web code from scratch anymore. Once you build 1 thing and it's in github, it takes 5 seconds to copy/paste that component to some other project. Good luck doing that same activity between Angular and React code piles. Or even Angular code piles of differing versions.


And then someone else has to come behind you to support your custom framework.

I can’t count the number of times where an “architect” has evolved their own custom framework because they thought their problem was a special snowflake. It’s usually worse, less document and less tested than the popular alternative.


> And then someone else has to come behind you to support your custom framework.

Are you asserting that developing an understanding for a vanilla web codebase is somehow worse than figuring out how to upgrade an Angular2 project to Angular8?

The biggest reason we use "custom" framework is because all the "standard" frameworks change too rapidly to support our business model. They also fall out of support before our B2B contracts expire, creating very difficult situations at due diligence time. We sell software to banks, so we don't get much room to work with regarding our 3rd parties.


> Are you asserting that developing an understanding for a vanilla web codebase is somehow worse than figuring out how to upgrade an Angular2 project to Angular8?

That depends on the details of the vanilla web codebase no? There's no bright line between "vanilla web" and "custom in-house Angular-like framework we created from scratch that stymies new developers." It's all a matter of how the vanilla codebase is developed.

(but sure there are certainly reasons to do something without a pre-built framework, especially if you have certain support commitments)


On the other hand mainstream frameworks have a big community behind them, while the custom framework's support is only you - thus making it:

1) far more likely that you have undiscovered vulnerabilities in it,

2) actually a lot more work to keep it up to date and properly secured over time, since it's all up to your team (and this becomes exponentially harder with every 3rd party lib that you use).

Not implying it's the case with your company, of course, but most of B2B companies that I've seen that use custom frameworks solve these 2 problems by simply ignoring them and not updating anything - hoping for the best and relying primarily on security through obscurity for protection.


This makes no sense. Frontend web frameworks don’t do anything for security besides escaping HTML occasionally.


I’m asserting that as the needs of your website expand and grow, you’re going to need to add cross cutting concerns that are not core to your business logic and you will inevitably end up creating your own framework.

See also: custom ORM, custom logging framework, custom authentication, etc.


> It’s usually worse, less document and less tested than the popular alternative.

That's often true, but the situation is not much different to framework stagnation.

"Popular" frontend frameworks I have worked with throughout the years, which are all more or less obsolete now:

Dojo, KnockoutJS, ExtJS, Backbone.JS, AngularJS, Angular 2+, Ionic

Web frontends are replaced every couple of years, often triggered by new feature development using the shiny new framework, and the cost of supporting the old, now unpopular framework.

Focusing on the actual web standards instead of another framework abstraction has long term value. For example, being able to query a DB using SQL is as relevant as it was 30 years ago, despite the numerous ORM/QueryGenerators on top of it.


And yes when I started web development CSS didn’t exist. Knowing web standards doesn’t keep you from having to learn.


Frameworks for layout in css is one thing, but frameworks are still super useful for skipping the repeated effort of things like dropdowns, navs, typography, sliders, etc.


There's a fun bit of marketing [0] NeXT did when AppKit was the new thing, comparing building a reasonably non-trivial application with it to a Sun workstation using C. Obviously biased, but still an interesting look into the development environment that among other things brought us the Web and DOOM.

0. https://www.youtube.com/watch?v=UGhfB-NICzg


Hear hear.

There's also a lot of noise in the community coming from either juniors or hobbyists praising whatever new shiny feature they just learned about which sells the wrong impression that, in this particular case, SwiftUI is way better or more mature than it actually is.


I think the more you climb the abstraction ladder (make things easier by abstracting the details), the more the chance, not only of saving time, but also of rigidity.


This is an area where I appreciate Angular and Angular Material. Angular Material provides the "easy to make table", which itself is simply a styled version of the underlying Angular CDK library. CDK provides unstyled building blocks for the Material components, which makes it significantly easier to roll your own.


> but later on trading 30 minutes (UIKit) for 3 hours (SwiftUI) when it comes to anything significantly complex

if something is faster/better implemented in UIKit, then do it! And then shove that in a `UIViewRepresentable` and plug it in to your SwiftUI code. :)

The best part of SwiftUI is being able to fall back on UIKit or AppKit.


I've worked with both SwiftUI (iOS/Mac) and Jetpack Compose (Android).

Surprisingly, I've found that Compose is hands-down better on both the "developer experience" front as well as the "UI quality" front

SwiftUI is "clever" and it's a black box. You get what you get, and Apple steers you away from customising too much. Compose is open source. I can see how the high level components have been built from lower-level primitives—and I can do the same.

SwiftUI provides a set of Views and modifiers from Apple, and that's about it (without dropping into UIKit/AppKit, which is costly). Compose is lower-level. For example, a "Button" component in SwiftUI is a black box. I can modify it in a few basic ways, but that's it. Whereas in Compose, I can customise anything. Including jumping into the implementation of "Button", and referencing that to build my own fully custom implementation.

SwiftUI is at the bleeding edge of the Swift type system, and you're always fighting with it. You quickly start getting compile errors if your method bodies are deemed too complex ("The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"). Customising views with styling protocols is a mess too. Compose doesn't make me think about the type system. Rather than returning views, Compose functions render them. This makes a huge difference.

After using Compose, I think Apple have made a real design mistake with SwiftUI by relying too heavily on hacking the Swift type system.


Except for a few complex components, Compose is for all intents, a complete rewrite of the Android view system. SwiftUI, at least in its current iteration, mostly wraps UIKit and AppKit controls and is bound by the contract of those controls.

I get the impression that Compose was started as a project to build a platform native version of React or Flutter, with a strong emphasis on incorporating it into existing projects with large teams. SwiftUI on the other hand, seems like it was originally built as a layout engine, then someone saw React and Flutter and incorporated some of its best ideas into it during a later part of the design.

A hypothetical comparison:

Compose: Button { Surface { Label() } } SwiftUI: if (mac) { NSButton(...) + modifiers } else if (ios) { UIButton(...) + modifiers }

Let's say you want to create an icon button for your app:

Compose: IconButton { Surface { Column { Icon() Label() } } } SwiftUI: IconButton { Button() + modifiers }

Compose provides all of the building blocks you need to push the framework in the ways you want to go in. SwiftUI seems designed to look at it from a Xib/Storyboard perspective.

The first problem you can run into with SwiftUI is that if Apple hasn't implemented support for certain modifiers on a Button, you can either create something entirely from scratch, or you can try to figure out hacky ways around the problem.

The second problem is that it's entirely closed source and locked to each OS release. So if they decide in a future release to change the underlying implementation, you now have to rewrite that IconButton to say, "do this on older release, and do this new thing on newer releases"


> You quickly start getting compile errors if your method bodies are deemed too complex ("The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions").

I had no idea that was a thing! I'm honestly confused on why it would need you to do that manually. I'd think that splitting things into separate variables and then merging them together would be essentially identical to just inlining as long as you don't mutate anything or change scopes. Does the Swift compiler not optimize expressions to remove locals when they're unchanged since they were declared?


The problem is calculating all the types. This is largely a consequence of overloading (and especially operator overloads) and literals. Splitting them up into separate variables works because Swift type-checks each line separately, so moving an expression into a separate variable makes Swift resolve the type for that expression on its own instead of as part of a larger expression.

For example, if you have the line

  print(1 + (2 as UInt))
this compiles as it infers the type of `1` to be UInt as well. But if you split it up

  let a = 1
  let b = 2 as UInt
  print(a + b)
you get a type error as you cannot add Int + UInt. This demonstrates how the declaration `let a = 1` forces it to resolve the type there, and the default type for integral literals is Int.


Oh, that's disappointing. I had assumed that Swift did "full" type inference by backtracking from usage to assignment and then checking to see if the value assigned fit the constraints of the usage rather than just checking a single statement at a type like C++'s `auto`. I think I first encountered type inference like this from an OCaml course in college, but at this point I'm most used it it from Rust (for example: https://play.rust-lang.org/?version=stable&mode=debug&editio...).


According to https://github.com/apple/swift/blob/6d2a3bbf9d518c2ff8c63731...

> The Swift language contains a number of features not part of the Hindley-Milner type system, including constrained polymorphic types and function overloading, which complicate the presentation and implementation somewhat. On the other hand, Swift limits the scope of type inference to a single expression or statement, for purely practical reasons: we expect that we can provide better performance and vastly better diagnostics when the problem is limited in scope.

However Hindley-Milner systems are generally linear in complexity whereas Swift's type system experiences combinatorial complexity explosions in the presence of overloads and operators and literals.


Yeah, it makes sense that they have features that make it harder. I can't say that it's the _wrong_ choice, but it's enough to remove any potential remaining interest I might have even if it did reach the level of support on Linux that I would otherwise want. I honestly prefer not having function overloading, and I certainly wouldn't want to give up features that I actually would want to use to be able to have it be supported in a language.


Is it the same fundamental problem as with C# overloading and lambdas?

http://web.archive.org/web/20150905081104/http://blogs.msdn....


In the cases I've gotten this error, I am guessing it is covering something a bit more complicated in how it needs to discover the cause and generate an error message. I've found actual other errors in the code, removed them, and then this message goes away. It might be that whatever compiler improvements or pre-pass over SwiftUI is not completely working yet.


You can often fix this particular error by removing all type inference. Declare all variable types and include the types of any static properties/functions before the dot operator.


I’m just wondering how big of an expression it takes so that the compiler can’t do a simple tree walk in a reasonable amount of time?


I played around with SwiftUI a bit last year and I can't remember the specifics, but there was some kind of syntax in it that wasn't even a language feature. Like it was a weird special case just for SwiftUI where you could call functions in some sleeker way that you wouldn't normally be able to do or implement if you were making your own library (I assume?)

That seemed so bizarre to me


SwiftUI might be slower than UIKit at the moment, but it has significantly more potential for performance improvements in the frameworks.

With UIKit, developers mostly express the "how" – put this control at these coordinates etc. This makes it quite hard for the framework to a) understand if anything has actually changed, and b) optimise how the UI gets rendered.

With SwiftUI, developers much more express the "what" – show the user this information, with these constraints, etc. This allows the framework much more scope to optimise how the layout is accomplished. For example a List of 3 items could skip a bunch of complexity around the handling of swapping views in and out of the hierarchy as it's unlikely to scroll far enough.

Additionally, by making the data model a first class concept more than it was with UIKit, SwiftUI has much more understanding about data flow and when the UI needs to be re-rendered, or which parts need to be re-rendered.

It's still early days, compared with the maturity of UIKit (which stems from AppKit, which is 20+ years old at this point), but the scope is much deeper and I'm confident the performance will improve as Apple iterate, and as they understand how it's used by developers and improve those code paths.


Curious how you address the counter argument: That in practice declarative UI has historically been slower than coordinate-based systems? E.g., we're talking here about SwiftUI vs. AppKit, but there's the long history of slow interfaces with HTML/CSS.

Based on the historical evidence I'm familiar with "declarative UI = slow" is just something I assume to be true now. For that to change, declarative UI frameworks have to stop talking about how they could be fast, and start actually being fast.

(I'm also just very sensitive to latency, and I've watched new declarative UI frameworks rise in popularity, while at the same time the latency in the software I use increases, so in my head they're linked.)


The key point is this:

> significantly more potential for performance improvements in the frameworks

"Hand-crafted" imperative code will always have more potential, but most code is not this sort of code, and most engineers probably don't have the skillset to be able to do this, at least not when traded-off with implementing features and shipping customer value (no criticism, this is likely the right trade-off).

Declarative code shifts that control to the platform owner where they can improve things for everyone. Apple has a significant vested interest in this, I'd say more so than React, as people blame their iPhone for being slow, or their browser, but not React or SwiftUI.

I'd also suggest that most declarative UI frameworks I'm familiar with have been in higher level languages such as JS, HTML, or QML, or things that run in a browser environment. I'm not sure we've seen something that's in a relatively performant, compiled language, for an environment that assumes reasonable graphics performance.

Apple are explicitly targeting 60fps here, it's clear performance is a first class concern, rather than just an afterthought, and this is the most convincing case for a declarative UI framework achieving it that I've seen.


Eh, I don't think it has to do with being "hand crafted".

I think the notion of declarative is some flawed bunk - to tell the framework "what" to do you need to specify "how" - removing the how and pretending you will magically infer the intention is a recipe for disaster.

"and most engineers probably don't have the skillset to be able to do this"

The irony being declarative languages require a higher bar of skills to use well, without the UI falling over.

"implementing features and shipping customer value"

Again, you can build bad code fast, but eventually more garbage is just more garbage, you have to go back and "fix" it or the value proposition shrinks faster than value added by new features.


If I want an input box to flow to fill the width from its starting point to the right side of the window/display minus a margin... what's so bad about the UI tooling handling this? I mean, I've worked with tools where it's a lot of work to maintain that simple thing, that you wind up doing over, and over again... What value does it really add in spending even a half hour (often more) just handling layout reflows... we're talking about computers in your hand that are more powerful than super computers a couple decades ago, and faster than desktops even a decade ago.

It isn't a new problem, HTML solved it relatively well several decades ago now... not that HTML has been the pinnacle of performance, it's been pretty damned effective at delivering a layout/form that can scale to the user without excessive effort.


That's not what declarative is, though, "imperative" or OO frameworks have layout managers (too).

There is some confusion, i.e. a spectrum (declarative first or imperative first, somewhere in the middle), about what is meant by declarative e.g. this article mentions android as "imperative" https://medium.com/everything-full-stack/declarative-ui-what...

While this one claims android is by default declarative. http://www.cberr.us/tech_writings/essays/declarative_vs_prog...

Having a DOM or element tree with layout applied doesn't make you declarative, this is more what is meant by declarative:

https://en.wikipedia.org/wiki/QML or https://www.solidjs.com/examples/counter

I.e. "low code" coding with little actual behavior specified.


In addition to layout managers, UIKit and AppKit both have constraint based layouts if you use Autolayout. Autolayout itself follows the declarative paradigm (well unless you use it to manually and directly set every X, Y, width, and height constraint, then I guess you are using imperatively). You can declare that a button be two thirds the width of its parent, for example. It’s also fast (well I guess I mean between Apple’s optimizations and modern cpu speeds, there is only noticeable latency when you do intentionally ridiculous things like trying to layout one view per pixel with nested, relative constraints).

It’s kind of funny comparing it to SwiftUI’s new .layout API. It’s the exact opposite! A declarative UI framework with an imperative layout option.


> "Hand-crafted" imperative code will always have more potential, but most code is not this sort of code, and most engineers probably don't have the skillset to be able to do this, at least not when traded-off with implementing features and shipping customer value (no criticism, this is likely the right trade-off).

This trade-off sounds like declarative frameworks make it easier for bad developers to make mediocre software while making it impossible for great developers to make great software?


In my experience SwiftUI gets the right balance here.

For a few hero screens we were able to implement what we wanted, with the performance we wanted, in non-idiomatic but not-bad ways. For almost the entirety of the rest of the app SwiftUI pretty much did the right thing without much work and gave us a performant app essentially for free.

In our previous UIKit app so many screens had edge cases where the engineers implementing it hadn't had time to optimise for particular ways of using it, and the whole thing felt a bit janky to use. Not criticising the work they did, but the tradeoffs were such that we didn't have time to polish that much.

Declarative frameworks can either make it easier for bad developers to make mediocre software while limiting great developers, or, with the right tradeoffs that I believe SwiftUI does well, they can allow bad (or time-constrained) developers to make pretty good software, while leaving the door open for great deveolopers (or more highly resourced teams) to make great software.


Depends on the definition of "great" ... if you're a company building a one-off app that only a couple people are ever going to use, do you want efficient, and lower cost to build/deploy getting the job done, or do you want "great"? And would you be willing to pay out of your own pocket for others to do that work?

I'm all for software quality and craftsmanship. I think far too often, far too many corners are cut, and there are often huge projects which have been poorly written. That said, it really depends. Most people only use a handful of one-off apps. But most apps are one-offs. The developer time is far more costly than the resources to run the app, or the time of the people using the app, generally speaking. Saving 0.01 seconds may make things seem smoother, but it's not really going to make the person using the app more effective.


I'm not saying every piece of software needs to be great, just that not having the tools to be able to aloe great software at all is a problem (and I'd argue that's the direction Apple is headed in today, i.e., I don't think Apple's tech is a good choice to build another Sketch https://en.m.wikipedia.org/wiki/Sketch_(software) today).


I would separate your post into two things: One is the idea that declarative interfaces can be faster, and the other is that Apple is dedicated to fast interfaces.

The former, as you are seeing from several other posters, is not a new thing, and is in my opinion a history of continual and significant failure. It is so consistent that I now have an almost visceral revulsion to people singing me the song about how wonderful "declarative" can be, which is sort of ironic because they intend the opposite. Calling something "declarative" is one of the strongest signals that a technology is going to be a pain in the ass to use. (In fact I'm sitting here racking my brains for a stronger one and I'm not sure I can come up with one. "Enterprise-ready" perhaps? Maybe "Hosted by the Apache project", which often indicates a quality project but one that is definitely going to be a real pain.)

The real reason this will go fast is that Apple is prioritizing speed.

I also was concerned about your comment above "developers mostly express the "how" – put this control at these coordinates etc." So far as I know, that is a strawman; no modern UI toolkit works that way. The web has had a major influence on them and every major toolkit has a more web-like layout available (and I am collapsing history here for simplicity, I am aware that relative layouts were available before the web, but the web definitely made them all step it up another notch, further consolidated by interface diversity between touch & mouse & screen sizes). If anyone using a major modern toolkit is dropping text at a particular coordinate, that's on them using the toolkit incorrectly, the toolkit has long since stopped forcing that.

What the toolkit needs in order to do what you're talking about is basically the DOM for the thing it is displaying right now (for whatever the toolkit calls that concept). It is not particularly a problem for the toolkit if that DOM is built by imperative code or by some 'declaration'. It probably will end up supporting both anyhow, because how the DOM tree gets built isn't the important part. Basically identically to how a browser functions, it doesn't matter to the browser (as a UI renderer) whether the DOM it is working with was built "imperatively" or "declaratively" or "reactively" or "purely functionally" or anything else; the DOM has what the browser UI needs to render quickly, and to the extent it has troubles rendering quickly, the solution is more information in the DOM for the browser to chew on and use in its decisions, not a rewrite in how the DOM is generated. Declarativeness is entirely irrelevant here, in some sense because regardless of how you get there, the DOM-equivalent is already by its nature guaranteed to be "declarative".


It's all about the baseline. Baseline declarative has "good enough" performance while the same money/time investment into imperative UI will get you worse performance. It's only when you've poured in a lot more resources into the imperative version that you start to see it overtake the declarative version.

This problem is illustrated well by InfernoJS. When the JS framework benchmark was first released, the vanilla (imperative) version blew away all the frameworks on the list. InfernoJS came along and beat the vanilla performance, so they updated the vanillaJS version. They went back and forth a few times with InfernoJS constantly upping the game and them looking at what Inferno was doing to get ideas into improving their performance further.

Today, the vanilla version is faster, but only by a little and only after a bunch of rewrites. Meanwhile, the InfernoJS declarative code didn't change a huge amount. The underlying framework did most of the updating. This means that Inferno would be fast on a second project while vanilla would once again start off slow and have to find ways to optimize their specific app all over again.

Only the most demanding companies and legacy apps will continue with anything other than SwiftUI once it has improved a bit. Saving all those dev man-years while giving up just a little bit just isn't a tradeoff that most companies (even big ones) are willing to make.


I'm not familiar with Inferno JS, but it appears to be based on the DOM, which is fundamentally declarative (i.e., flow-based layout rather than coordinate-based layout)? I.e., correct me if I'm wrong, but that doesn't sound like a pure enough way to compare declarative vs. imperative if it's built on a declarative foundation?


Yes and no. The DOM is declarative internally, but the fastest way to use it is imperative where you manually create nodes one at a time, update properties on those nodes individually, and string them together one command at a time.

The effect for the programmer isn’t so different from a series of draw calls except that it’s more sensitive to individual updates tanking performance which requires even more consideration from the imperative programmer.

To me, the best comparison is between an AOT language with manual memory (eg, C) vs a JITed language with garbage collection (eg, Java). In theory, the JIT can provide equivalent or even faster code, but that’s not generally the case in practice. The GC isn’t generally faster, but it is safer and easier. Some companies need their software written in C, but Java is good enough for most given the productivity gains.

In theory, runtime inference could dynamically improve performance of declarative systems beyond what AOT imperative code can do, but in practice, companies don’t invest the resources to make this happen. At the same time, not having to manually manage the output to ensure garbage doesn’t creep in accidentally greatly improves developer productivity.


The theoretical performance improvments of declarative UIs are just.....theoretical.


Same observation here.

I remember a time, not so long ago, when serious people were advocating JITed languages (Java, C#) for performance workload because the JIT was theoretically able to produce better code for the platform given it had full knowledge of the architecture and execution context.


For (long running) data stream workloads, JIT does have an auto-profiler benefit. It doesn't preclude writing code well.


For long running workloads with consistent usage patterns it has a benefit.

It is not merely enough to be long running, that is a requirement, but not enough by itself.


Are there any studies measuring it on real-world workloads?


Building UIs with coordinates is hard and effortful. Because of this, you have no time to make it slow.

Building the same UI declaratively is much easier. And a simple UI is fast. But because it was so easy to make it, now you have extra development time to make it slower (by adding extra features).


Elm is really fast when it comes to rendering web interfaces


But this is also one of the big problems of SwiftUI. Because it is effectively a fancy DSL for combining interactive lego pieces, many subtle interactions are just assumed implementation detail. Like how should a "tap" on a Rectangle with a transparent color be interpreted if the rectangle is offset. Because the underlying framework (UIKit) has no notion of these things and because the SwiftUI DSL can't foresee all permutations of its members (Rectangles, HStacks, Spacers, GeometryReaders, etc), oftentimes people build a certain combination of things that results in a certain behaviour. But then the next iOS update this behaviour changed because it was just an ever-changing implementation detail. This leads to constant churn.

I agree that at some point we will see less of these surprising behaviours, but as long as Apple iterates on SwiftUI they will happen again and again.

Also, regarding performance, any performance optimisation will lead to more of these surprises. To keep my example: Maybe at some point an engineer will add a SwiftUI optimisation for offset rectangles and suddenly the tap doesn't work anymore.

After using SwiftUI a lot I'd rather use a more deterministic framework. Or, have it be open source so I can understand what's happening under the hood. The current game of build, inspect and pray every version update is frustrating.


That line of reasoning reminds me of the fabled sufficiently smart compiler: one day, the sufficiently smart compiler will understand what we mean in our programs and generate code that is faster than what an expert could write in C or even assembly. Unfortunately, the sufficiently smart compiler still hasn't arrived.

And similarly, the performance potential of SwiftUI has not been unlocked. Maybe it will happen, but while we wait for the SwiftUI to implement those fabled optimizations, developers have to wrestle with—from what I gather from the article—a lot of extra accidental complexity so that they don't impose an unusably slow interface to their users.


This is a fair point, but from what I've seen from Swift UI these optimisations are already happening.

Lists used to be backed by UITableViews in all contexts I believe. Now, if I remember rightly, they are backed by a mix of UICollectionView and raw combinations of views depending on various factors like the number of elements or whether that number changes.

These sorts of changes didn't go super smoothly, I think iOS 13.0 was the first one with SwiftUI, then 13.3 changed a lot of the under the hood details and broke some things (we had 13.2/3 specific hacks), but since iOS 14 things have been much more stable and there have been fewer breakages.


Optimizations have been happening in compilers for years, but they've never come close to the "sufficiently smart compiler" ideal.

IOW, you need more than an observation that optimization is happening.


> one day, the sufficiently smart compiler will understand what we mean in our programs and generate code that is faster than what an expert could write in C or even assembly.

Who is making this argument? Pretty sure I’ve heard most compilers (and transpilers maybe) will do a better job of the low level stuff than your average dev, but AFAIK no one notable is arguing a true expert couldn’t squeeze out some additional performance using say Assembly if and when it’s sensible to do so.


> That line of reasoning reminds me of the fabled sufficiently smart compiler: one day, the sufficiently smart compiler will understand what we mean in our programs and generate code that is faster than what an expert could write in C or even assembly. Unfortunately, the sufficiently smart compiler still hasn't arrived.

Hasn't it? Less and less gets written in C these days, even e.g. HFT or game engines tend to use higher-level languages.


It hasn't. It's possible for programmers to get massive speedups by re-architecting their code to make better use of the CPU caches or avoid branch mispredictions and that happens because the compiler was not sufficiently smart to do that transformation itself.


> It's possible for programmers to get massive speedups by re-architecting their code to make better use of the CPU caches or avoid branch mispredictions

Sure, but they can, and do, do that just as well in Java as they could in C or assembly.


How could the framework have improved to fix the problems in the OP?

It seems like they were mostly caused by (developer) design flaws by oversubscription to events causing wasteful redraws. What kind of changes are you expecting to fix this?


Your argument doesn't work.

It seems like a variation of:

"If I tell the compiler what instead of how, it should be able to produce the msot optimized code, even if it doesn't do so now, a sufficiently advanced compiler could".

This kind of thing never materializes.


It's convenient, until you've written 90% of your app in record time and then end up so blocked on the remaining 10% you wish you would have just used UIKit.

I love how quick and easy SwiftUI is for simple, Apple-like / stock app interfaces. But the second you try to do something outside of their guard rails (some of which you don't even see until you've gone outside them), SwiftUI quickly makes you realize you are swimming against the current. I also find debugging SwiftUI to be exceptionally painful compared to UIKit.

As others have said though, this improves with every release so I'm confident things will only get better.


It's not all or nothing. If you have an interface that would be easier to do with UIKit, simply use UIKit.


The frustrating thing is not knowing when to use UIKit in advance, until it's too late.

But yes with experience you can learn and get a sixth sense of when UIKit will be needed.


The main issue with SwiftUI is not its syntax, but how Apple refuses to actually adopt it. Apple pretends that SwiftUI is the "default" for the development of iOS apps, but then refuses to adopt basic features within their APIs (like in MapKit[0] for example, where it's literally impossible to build an interface with anything complicated without moving over to UIKit). For SwiftUI to actually be usable by the majority of iOS developers, Apple needs to start taking it seriously.

[0]: https://developer.apple.com/documentation/mapkit/


Wondering if this is related: In MacOS Ventura navigating around settings has become VISIBLY slow. Seconds can pass between clicking a settings option and its contents being rendered. Kind of fascinating that it got released like this at Apple.

That said it's only slow to load, it does not freeze or stutter. I guess that's still an upside to what it would've looked like under Windows or Linux.


With modern displays being able to refresh at 144-350Hz we could expect modern UI to feel smoother and smoother over time.

GPU is not a bottleneck. CPU should not be a bottleneck.

Then why?


Seems the setting screens are loaded on-demand, maybe using some plug-in architecture that results in overhead. Also, some panels require network communication which makes them even slower.

But even then, I agree there's no excuse.


Each settings panel is run in a separate xpc process, that is launched on demand. So the UI is rendered out of process too. Open Activity Monitor and see how many processes are run when navigating around in System Settings.


Some parts of the panes are also webviews too, which definitely doesn't help. From memory you can go in and inspect them from the Safari web inspector.


Programmers.


What Andy giveth, Bill taketh away


I'm not the author of this piece, but I find the transition to SwiftUI incredibly interesting and a bit frightening. I've noticed myself that things you'd expect to be swift (pardon the pun), such as loading a screen with a few simple controls in the new Ventura SwiftUI System Settings app, can take a second or more. I'm hoping things like that is either shoddy work on the part of the app or issues which will get ironed out, but I fear that we might see an era of much more random latency issues until hardware becomes powerful enough to make that go away.


I can assure you loading times in terms of seconds has everything to do with the app making a network call or some other loading, and not SwiftUI itself.


It's spinning up a remote view for the preference pane, and then SwiftUI is taking a while to render it because the author did a poor job of optimizing it.


“Poor”.

I mean that’s quite possibly the most charitable, Iraqi-general-during-desert-storm way you could put it.


Yeah I can’t imagine SwiftUI is adding seconds of latency to something UIKit can handle in milliseconds. That seems absurd.


Haven't done Swift and Cocoa in a while, but is there any major disadvantage to just not using SwiftUI?


It's been out for 3 years, but it's honestly still quite rough around the edges. It's like the highways and some roads are paved, but otherwise you're off-roading if you need to drive outside of the city.

There's a fair bit of UIKit-parity that's still missing, which forces you to create a ViewRepresentable class and do the UIKit bridging yourself (this ranges in complexity, but can definitely present significant challenge depending on the task). This becomes more frequent too for lesser used Apple platforms (watchOS and tvOS).

Another place that's lacking that the author doesn't quite dig into is that SwiftUI has no concept of cell re-use at all. The Lazy UI components (stacks, grids) will _delay_ their initialization, however, nothing actually ever gets re-used. So if you're working with a layout similar to Netflix, every time you scroll left/right/up/down, new cells are being created and are loading more image data into memory. With UIKit, cell re-use is a foundational concept that all devs should be utilizing, so it's a bit of a headscratcher that something like this is still missing from SwiftUI by now.

To top it all off, as Apple does get SwiftUI up to spec, they are completely resistant to releasing support libraries (or anything along those lines) - so rudimentary UI features/modifiers/etc. that get added years later will only ever support the latest version of iOS and forward. This makes it much more restrictive to work with, as most apps are targeting at least 1 year back and not only the latest major version of the OS.


> The Lazy UI components (stacks, grids) will _delay_ their initialization, however, nothing actually ever gets re-used. So if you're working with a layout similar to Netflix, every time you scroll left/right/up/down, new cells are being created and are loading more image data into memory.

What's the trade-off here for loading versus pre-loading? I guess if you are pre-loading all of that content is loaded into memory from the start, right? I'm guessing there's a performance advantage to have images pre-loaded, but then:

1. Are you doing that dynamically? How would you know what content to pre-load onto the device? My Netflix queue is different from others.

2. If you're loading a lot of content (like scrolling left/right to get new titles) maybe it makes sense to guess/decide what the next cells off the screen might be and load those up as the user scrolls versus loading them for sure, and what about the case where the user scrolls down/up but not left right? You wouldn't necessarily want to load all of that content unless you know the user is going to look at it? Also wondering how for Netfilx this would factor into their algorithms. If I "like" a show when I first load the app if they pre-load the cells then they'll have more compute to re-load all the cells to change their recommendations?

Curious about this.


Preloading in this case is "rendering a cell before you scroll to it".


Yes but presumably that cell has content fetched from a server right (title, actor names, title image which itself is dynamic based on Netflix A/B testing scheme, etc.)? Wouldn't it be faster to just get the content from the server ad hoc? I'm thinking you'd also potentially have a lot more network overhead if you preload the cells because then if a user doesn't scroll to one of these cells you've created them and they aren't used, and you've fetched server content too.

How many cells would you create? I guess maybe Netflix has a default limit per category of titles they would display?


> SwiftUI has no concept of cell re-use at all.

This is not true, `List` definitely uses the same approach internally as `UITableView` for cell reuse.


Author here! Aside from Lunar (https://lunar.fyi/) all my apps over at https://lowtechguys.com/ are 100% SwiftUI.

In my extensive experience, this is the only major disadvantage I found: UIs with hundreds of stylized elements can be too slow

I suspect this happens especially because adding style to an element makes the graph deeper (e.g. Text(“”).shadow().colorInvert() means SwiftUI will add three function calls in the stack on each render pass)

But if that case does not apply to your UI, the advantages are massive.

For example my Volum [1] app works on iOS, iPadOS and macOS and because of SwiftUI it shares 95% of the code between platforms. That’s hard enough with other frameworks that I couldn’t even consider starting such a project before SwiftUI.

[1] https://lowtechguys.com/volum


hey i also build mobile apps in swiftui. do you have more resources on why $binding could be slow?


Well, it's not @Binding that's slow, it's the fact that it's not immutable like @State, so on each `body` call, the binding's `get()` function has to be called again.


Your apps look really neat. :D


Thanks! ^_^


Apple keep pushing it incredibly hard, despite it being poorly implemented. The net effect is less thoughtful developers use it because Apple push it.

Eventually it will infect your project because those developers will start pushing it into new features. I regret not being more vocal in my current job about not using it in production and now we have a huge amount of bugs caused by it even between point releases of iOS.


It’s certainly a lower barrier of entry, which is both good and bad. I can mock up an app on my iPad or build some simple utilities quickly, but one things get complex it can become difficult to debug.


Apple is trying very hard to shove it down the developers’ throats. E.g. they make certain new OS frameworks SwiftUI-only, like the new “dynamic island” APIs for iPhone 14.

(Pretty misguided in my opinion, but it’s their platform to ruin…)


I am almost convinced that they will follow this path and I've made my mind about abandoning development for Apple platforms a while ago.

Objective-C is an awful mess but at least the toolchain is able to natively mix Objective-C, C and C++ code without much effort.

And the low-level parts of iOS are still using C API.

Google tried the same thing at first, Java was the only supported API for many years, they reluctantly added the NDK for "special uses".


Yes, it is quite saddening. iOS used to be the platform that destroyed Android in user experience in no small part due to its “bare metal” programming model. I still remember how laughably slow and laggy my friends’ Android devices were, with their top of the line Snapdragon whatever uber CPU, compared to my meager iPhone 4. If I wanted a device with 100ms input latency, passing all of my gestures through countless layers of indirections and declarative tree-diffing nonsense, I may as well use an Android that doesn’t require me to shell out $150 annually to install my own software…


Can someone provide explanation why SwiftUI is slow? My intuition was that it should be extremely fast, especially on the modern MacBooks. I write quite a lot of desktop apps in Flutter (or run mobile apps as MacOS builds, because it's easier to develop and debug this way), and I've never had issues like this, but it's hard to believe that Flutter is faster than SwiftUI. I'm confused by this article.


It comes down to understanding the reactivity model - how to define state that can change, and how efficiently the UI updates in response to those state changes.

I think the issue the author had with SwiftUI is not understanding the reactivity system, or frequently choosing an expedient option that caused over-rendering - unnecessary re-renders in response to state changes that aren't required for the UX to work correctly from the user's perspective. Even if your UI's actual rendering work takes 1ms to paint, you want to avoid re-calculating busy work, allocation, etc, which can drive poor experience.

With a framework like React that provides very few primitives, it's quite clear when the framework will re-render. With a complex framework like SwiftUI, there are many primitives and different ways to represent state, subscribe to updates, etc, and the framework is quite under-documented. After spending 15 hours with SwiftUI, I still don't have a clear understanding of how the system detects a subscription, or what the re-render semantics are.

React I would describe this way:

1. Every re-render starts when you setState to a value that does not (!==) equal the current value in a parent component, or a reducer stores a new value internally.

2. Child components of that component will re-render except in two cases:

2a. The child component uses React.memo, and the props passed by the parent are shallow-equal to the previous props

2b. The child component JSX nodes are strict-equal to those returned by the previous render of the parent component, eg a subtree in the parent wrapped in useMemo

I can't come up with a similar definition for SwiftUI after 15 hours building an app in it. I need to consider @Binding, @State, ObservableObject, @ObservedObject, projected values. When is the system creating fine-grained subscriptions automatically? When is the system subscribing to an entire object (deeply?). Hard to find a cogent explanation of all the options.

Because of my different understanding of these frameworks, I personally write React apps that perform better than most people expect web apps to perform, and SwiftUI apps that perform much worse than people expect Apple native apps to perform.


The talk "Demystifying SwiftUI" provides a great deal of insight into how SwiftUI performs diffs.

Basically, unless an identity is explicitly specified using `.id()`, it uses the static types of your views to determine their identity. Naturally, this has a huge effect on what is re-drawn, but also animations.

This is also why AnyView can lead to worse performance - it erases the static type information.

[1]: https://developer.apple.com/videos/play/wwdc2021/10022/


Thanks for the reply. One thing I love about Flutter is that it's doing tremendous job under the hood to decide what parts of the widget tree is actually need rerendering. I might be wrong, but that's probably the major aspect of their widget tree system design. As a developer I focus mostly on the expressing of the UI via widget tree, and if I follow official examples and tutorials and do not invent the wheel, than it yield optimal performance.

If I understood your comment correctly, in SwiftUI developer should learn a lot of stuff to make re-rendering upon state change efficient?


> When is the system creating fine-grained subscriptions automatically?

With TCA you can side-step these issues to an extent, in that you can scope the store down to what the view needs. Then, the view body only re-renders when the data you scoped down to changes.


What is TCA?

I'm frustrated also because there's often times when we want to pass state from a parent view through an intermediate view to a grandchild view. How can we avoid excessive re-rendering in the intermediate view? Often I feel like I'm able to scope down subscriptions, but not eliminate them entirely, again probably because I don't understand the system well enough / the system documentation is lacking.


TCA stands for Composable Architecture. I don't understand the system well enough either, feeling like everything re-renders. That's why I just use TCA instead. The library also needs discipline in what data is made available locally and in how much changes the system can take before things get laggy due to architectural constraints. But it can show where and why TCA view bodies get re-rendered.


I don't know internals of SwiftUI that match but if it still using CoreGraphics then it should be slow.

CoreGraphics uses CPU rasterization of primitives and that's the problem on modern displays.

Retina grade displays have ~9 times more pixels on surface than old 96 ppi monitors. So you need 10 times more performant CPUs for rendering - CPU rasterization is O(Npix) complex - as better (resolution) monitor you have as slow your UI.

On the other side games use GPUs (Metal, OpenGL) to render primitives. In that case CPUs just generate lists of primitive definitions in vector forms (polygons, etc.). Rasterization of those primitives is done in parallel by crowd of GPU processors. Therefore, games UI is not tied with monitor resolution directly - same [display] lists are generated by CPU for any monitor resolution. In that respect GPU accelerated UI rendering is O(1) complex.


You can directly enable the use of Metal with the drawingGroup() API.

https://www.hackingwithswift.com/books/ios-swiftui/enabling-...

You can also embed a UIViewRepresentable to "manually" embed a metal kit view inside a SwiftUI hierarchy. Basically use SwiftUI to manage most of it, but use metal to handle a bottleneck somewhere. Here is an example:

https://developer.apple.com/forums/thread/119112


Very few SwiftUI apps are bottlenecked on CoreGraphics drawing on the CPU.


that sounds reasonable, thank you


haven't read all article, but few factors:

- swiftui for macos apps is slower (smaller teams at apple to optimize it, all focus is on ios)

- with appkit you'd use reusable views, and swiftui is having hard time with them

- with reusable views you model data on view presentation, whereas with swiftui you often need to generate view data ahead of time, for all views (so 400 views can take some time just for data formatting)


These declarative frameworks always work well for demo stuff, then fall flat for actual complex software.

I’m talking not just about declarative UIs, but also declarative business logic tools (Windows Workflow Foundation anyone?) too.

They always end up slow, incomplete, and in the end require a bunch of bolted-on imperative hacks to get an acceptable outcome. This is the case with React, this was the case with XAML, and this is the case with SwiftUI.

Yes, I know, in theory they could be so performant, just like garbage collectors and JITted interpreters. In practice they never are.

Hopefully the failing of Moore’s law will eventually put an end to all this inefficient declarative nonsense.


SwiftUI is easy to get results quickly but its actually quite hard to master. It's very easy to screw up the rendering lifecycle and bringing the UI to crawl as you cause full UI recalculation at a tiniest data change.


Yes, it's simple, but no easy (I love this quote). The View Model loop seems deceptively simple at first sight, but you quickly realize that it isn't really the case. You have a long road of learning ahead of you. Specially at the beginning was really buggy. It is undoubtedly improving with every release.


Given the powerhouses that are modern CPU, even on mobile, I find it difficult to imagine.

Imagine if we used the same kind of tech to build AAA games?


We hit performance issues when writing a dataflow graph editor in SwiftUI [1]. We couldn't express the graph as Views and instead used a Canvas to draw everything. So, we had to do manual layout of everything, and manual event processing. This also precludes putting custom views for user-defined UI inside the modules. The performance problem had to do with evaluating the positions of all the wires between the modules using many GeometryReaders.

[1] https://github.com/audiokit/flow


Could we just take a moment to acknowledge that this dude wrote an app to support his brother's college expenses?

Kudos to the app writer. It's a beautiful thing to tangibly support the people you love.


Declarative frameworks are deceptively simple. The promise is that "anyone can use it" - be it UIKit experts or entirely green developers. However, as this article makes clear, a deeper understanding is required to make SwiftUI views performant.

It's too easy to end up with accidental anti-patterns scattered throughout your views.

Plus, there are more ways to accomplish a given design now. Developers are left to figure out everything on their own without realizing there's a very specific intended approach.


As much as I like SwiftUI (and I hope they continue improving it), it still lacks so many features compared to the UIKit equivalents that I recently had to completely rewrite a SwiftUI app recently because half of it was just UIKit wrapped in representables, and Combine doesn't require SwiftUI.

It ended up simplifying things because we no longer had to deal with two UI frameworks instead of just one.


SwiftUI is amazing, but the amount of polish you can only apply via representables is what's holding it back for me at the moment.


Let’s slow down a minute here…

It looks like some are assuming the problem is with SwiftUI and immediately speculating on the reasons, however even the author admits they do nut understand the framework architecture well enough to be sure.

Some of the “problems” that needed to be optimized were using global state that triggered all kinds of unnecessary redraws. This is going to cause problems for any system.

Also the author endorses adding “animations just for flourish”, then laments that animations make things slow. The first point is not really a good idea. Beyond that synchronous animations by definition cause latency, that’s not a performance problem. How much of the problem was due to this isn’t mentioned.

The whole thing is not well argued and the conclusion of the title is suspect.


If SwiftUI makes it that easy to fall into anti-patterns and the magic behind it is difficult to reason about, then I think performance is going to be a common issue for years to come.


I find SwiftUI to be fast on my projects, even in very complicated interfaces, but I'm always careful to make UI updates correctly target only the necessary views, otherwise the whole screen updates.


I'm learning SwiftUI now and would love to talk shop with other SwiftUI and Swift developers. IS there a discord channel, a Twitter group and or something where Swift developers hang out and or follow?

Overall love to find a mixture of a tutor and someone to help (hire) me build one of my many super crazy (lol) ideas while I learn through developing it together.


I just shipped my first SwiftUI app and have more queued up. Don't know of any Discords but happy to join one or start one with you.


Hi Bill nice to meet you im Ryan (@ryanspahn & @speakerblast_ap on Twitter) and great to hear of our mutual interests! Congrats on releasing your first app.

What's the best way to reach out to you? Email, Twitter or ?


Email is most reliable since I try to moderate social media but Ill ping you on Twitter


Thanks for the follow and sending an email. I have lost access to my 2006/2007 Twitter account (@ryanspahn followed you via one of my web apps Twitter) yet hoping one day I can pay to have access back.


SwiftUI is the worst thing not just because it’s bad. The non stop shilling from Apple has led too many wwdc scholars astray and they are twisting and turning their way filling up stack overflow with weird questions even Apple has no answers for lol. Too much marketing, not enough guidance or documentation from Apple.


Can't wait till SwiftUI is ready for complex application on macOS. Until then I'm trapped in Cocoaland.


Can you please elaborate on what you mean by complex application? Having just started getting into SwiftUI, what are the limitations?


Only marginally related, but… I am not sure if I would pay actual money for a calendar app that’s read only…

The UX of the app seems nice, but.. why can’t I write there? Do I need to have a separate app for editing?


Author here! You probably just don’t have the same need as me, I was expecting this to not be a popular app because of this :)

My need is to be able to check when I’ll be able to visit parents, plan trips with friends, answer my wife about when’s our next dentist appointment before she gets tired of waiting and checks it herself, when’s our niece’s birthday, what is the next religious holiday etc.

I find I have to check the calendar way more than I have to add something into it. And when I need to add something, I usually like to take the time and fill in useful info like location, travel time, useful URLs and notes. And Calendar.app is already good enough for that.


Ah! You mentioned in the article needing single button keyboard shortcuts. You can actually do this in SwiftUI, by passing an empty array on for the modifiers. I have code up right now so I'll copy a line:

.keyboardShortcut(KeyEquivalent("e"), modifiers: [])

That's for using just the "e" key, with no modifiers.


Have you tried using EquatableViews? That would let you customize if you want the view to get updated when the diffing occurs. Seems like a perfect fit for your issue.


It feels that GUI generation is a cache coherency problem.

What's rasterized and not re-rendered is part of the dirty cache, anything that renders on top of it is dirty.


What's so special about SwiftUI compared to e.g Flutter/Jetpack Compose that I see posts about it frequently on the front page?


It's from Apple and it's the endorsed UI framework for native development in their ecosystem (more or less). Flutter is not for native development and Jetpack Compose is the equivalent of SwiftUI for Android.

It's been the shiny new thing in iOS development since it's launch and of course there's a lot of division about in the community (it's good, it's bad, it's the future, it will never be the future, etc.)


There are probably just more users of SwiftUI here than Flutter or Jetpack Compose (I think Apple in general is a bit over-represented on HN).


They’re all about improving dev experience, and after all, there’s a large dev audience here wanting to write less and produce more.


It's Apple. Anything Apple is interesting/relevant to Hackernews.


Are there any non-Apple UI toolkits like this?


I’ve been down this road, a lot.

We use Flutter now. And are quite happy.


react-native, native script, flutter. xamarin (does that still exist?)


Sciter here too. On MacOS it renders UI primitives using Skia/Metal.


RxSwift


>This container is lazy so it takes more time get the view rendered because SwiftUI has to check if each row is visible before rendering it.

There is absolutely no way a lazy grid ends up being "slower" than a huge list of VStack and HStacks

This is a terrible post


Maybe try it before jumping to conclusions? In that specific case a lazy container is slower because the views are visible at all times so non-lazy views can render immediately without any boundary checks.


For this kind of an app wouldn’t it be easier to implement it in html/css (probably don’t even need JavaScript) and host it in a webview inside a native wrapper?


> how hard could it be?

The programmer's Credo:

    We do this, not because it is easy, but because we thought it would be easy.
> These damn animations man.. they're so fluid and springy and easy to use in SwiftUI, that it lures you into adding them everywhere.

That's the case with many UI frameworks. Built-in animations are a big draw. For myself, I eschew frameworks, so I have to write the animations by hand. It's a fair bit of work, but they are all supported in UIKit/WatchKit/AppKit.

Looks like the performance bottlenecks are from deep recursion. That's probably something that might be code-around-able. UIKit doesn't have as deep a recursion, but it has many other default behaviors that can result in slow performance, and a lot of my "tuning" is about finding these, and determining more optimal code solutions (like redrawing, as opposed to triggering new layouts, etc.; which isn't as easy as you might think. Sometimes, setting a variable in an instance can trigger a new layout).

I think that the idea of "code once, deploy everywhere," which SwiftUI promises, is pretty much a smoke seller's[0] dream. It's a classic. C was supposed to do that. Java was supposed to do that, etc.

I have found that I really need to write native for each platform, because the user experience is so different, that it's actually impossible to really cater to it, without delivering some lowest-common-denominator-something-just-doesn't-feel-right solution.

So, even if I was using SwiftUI, I'd still release an iOS version, a Watch version, a Mac version, etc., with specializations for each.

The thing that I like about the idea of SwiftUI (I haven't used it to create a shipping product, yet, so I don't have much more authority to declaim, than as an interested bystander), is that it allows great flexibility in the software development methodology.

If I write AppKit/UIKit/WatchKit, it really should be MVC, and not just MVC, but NSObject-based MVC. I know, I know, "But what about MVVM?". I feel that MVVM is an excellent model, but that it is not how these frameworks were designed, so they need to be sort of "kludged" to support it. I work in MVC, because they are totally meant for that model.

I'm looking forward to seeing how SwiftUI evolves, but I have yet to be convinced that it is a viable model for me to write shipping software.

It's really no hurry. Lots of major shops still write a lot of their stuff in ObjC/UIKt/AppKit/WatchKit (including Apple). It will be a number of years before we're required to use SwiftUI.

[0] https://www.tell-a-tale.com/nasreddin-hodja-story-smoke-sell...


> Looks like the performance bottlenecks are from deep recursion. That's probably something that might be code-around-able. UIKit doesn't have as deep a recursion, but it has many other default behaviors that can result in slow performance, and a lot of my "tuning" is about finding these, and determining more optimal code solutions (like redrawing, as opposed to triggering new layouts, etc.; which isn't as easy as you might think. Sometimes, setting a variable in an instance can trigger a new layout).

SwiftUI is designed in a way where deep recursion is a "happens once" problem. There is a lot more compiler trickery going on than people realize, and SwiftUI actually has a very good idea of the entire hierarchy and attempts to efficiently work on said hierarchy -- the cleverness of their algos may not always be clever enough however.

> I think that the idea of "code once, deploy everywhere," which SwiftUI promises, is pretty much a smoke seller's[0] dream. It's a classic. C was supposed to do that. Java was supposed to do that, etc.

this is a very common misconception I see passed around a lot, SwiftUI is more like react-native "learn once, write anywhere". You can have universal codebases with SUI but that is more the exception than the rule ime.

> So, even if I was using SwiftUI, I'd still release an iOS version, a Watch version, a Mac version, etc., with specializations for each.

this is what most people do, you can have a common logical core and entirely different SUI frontends.

> If I write AppKit/UIKit/WatchKit, it really should be MVC, and not just MVC, but NSObject-based MVC. I know, I know, "But what about MVVM?". I feel that MVVM is an excellent model, but that it is not how these frameworks were designed, so they need to be sort of "kludged" to support it. I work in MVC, because they are totally meant for that model.

this is a strange addition to the entire subject. you can write things as MVC with SUI, that is essentially what observable objects do. You can also easily bend them into MVVM. I don't use either approach, and instead go the Flux route.

> I'm looking forward to seeing how SwiftUI evolves, but I have yet to be convinced that it is a viable model for me to write shipping software.

I have had apps featured by Apple that are SUI only, I have shipped SUI only apps to literally 300,000 users.. and I have also shipped UIKit apps to millions. A few things I have noticed is:

1. We have far less crashes than any UIKit app we have ever shipped. 99.8% crash free range with my current work.

2. Developer velocity is insane, between two people we have no issue with shipping everything that was asked for and more which leads me to...

3. It has lead to far more collaboration with design teams since we can jam on design together!

I chose react-native for similar reasons when I worked at Chime. But the moment Apple dropped their own.. I knew it was the only way forward.

Edit: also wanna add that many things in SwiftUI are simply bridged to UIKit or AppKit anyway. Maybe that explains some performance issues. But from literally all of my experience it just has been people with a poor understanding of efficient declarative UI development. A problem that affects all declarative UI dev.


> this is a strange addition to the entire subject. you can write things as MVC with SUI

That's basically what I was saying. SUI is a lot more flexible, than the "classic" SDKs. They require MVC, but SUI lets you use whatever model you want.

Thanks for the experienced contribution!

Still not confident that SUI would give me the app I'm developing now. It's a fairly ambitious one. Lots of moving parts.

But I am often wrong, so I'll definitely be watching this space.


well, no. you can use MVVM with UIKit.. or VIPER.. or CLEAN. I have seen them all.

And on the note of the app you are developing now.. you would be surprised! I don't often plug my own apps.. but if you look up snap web studios in the app store both of those apps are purely SwiftUI with maybe one or two UIKit bridges for the hacker news app. Eggy is ambitious from a UX perspective, and the HN app is just straight-up impressive and demonstrates that you can make complex and very fast SUI apps.

edit: also, not sure if you were aware -- but you can use observable objects & such with UIKit :P


> you can use MVVM

But they are still a deviation from the model the SDK was designed for. The original Apple SDKs are totally bog-standard MVC models. If I try anything other than that, the code complexity increases a lot (especially with VIPER).

I think that a lot of these models were developed to allow projects that were really supposed to be doable by one experienced person, to be broken into something for a team of relatively inexperienced people. That's not necessarily a bad thing; but it's not how I work.

I like keeping things simple; even though it never ends up simple (See "Programmer's Credo," above).

> but you can use observable objects & such with UIKit

Yeah, but it's not really "native" support, like AppKit. SwiftUI has it baked into its DNA.


it is a really well established pattern now a day tho and most iOS eng i come across know how to work with it.

also totally agree on keeping it simple, but I spend about 6 months designing a codebase we could bring anyone up to speed on and it has worked!


Your hn app looks sharp I'll definitely be trying it out. Bug report though, under "Interface" in settings the word "Appearance" is displayed twice, similarly under "Browser" it says both "Open Links" and "Open Link."

iPhone 12 Pro, iOS 16.1.1


thank you! there is a literal multi-year delay in 1.1 which has so many nice things in it.. i will try to get it out.

edit: would also like to note that I have been thinking about this problem for like 10+ years lol.


I can see how creating a calendar with hundreds of little interactive elements could be challenging in any UI framework. Like Angular with its digest cycles and React with its DOM reconciliation.


Look at any AAA game. 10s of millions of textured polygons every 16ms. A render graph with several thousand nodes should not be visibly slow on any modern hardware.

I’m always disappointed that people are willing to accept the immense performance penalty of modern frameworks and libraries. Having just finished installing Windows XP on a 2.4GHz P4, its sad to be reminded how wasted current hardware is.


I just updated my ultra-fast, ultra-expensive, magical ARM M1 MacBook Pro 16" to Ventura and it takes 2+ seconds to change a tab in Settings. Two. Frigging. Seconds.


> 10s of millions of textured polygons every 16ms

But at what cost? 100% CPU and GPU usage, tons of system/video memory.

Also, rendering text with full capabilities (arbitrary font/size/color, emoji, ...) is much more complicated than drawing a textured polygon.

And how easy is to use a modern 3D engine versus HTML/CSS?

You are right that modern frameworks are slow, but there is a reason we don't write Notepads in Unreal Engine.


> But at what cost? 100% CPU and GPU usage, tons of system/video memory.

Games use lots of CPU/GPU resources because they're rerendering all the time. You don't do that in GUI apps. You also don't have huge swaths of 3d assets.

> Also, rendering text with full capabilities (arbitrary font/size/color, emoji, ...) is much more complicated than drawing a textured polygon.

That's exactly how text is rendered: Glyphs cached into textures that are blitted. Filling the cache is a little more involved, but you're not doing that all the time.

> And how easy is to use a modern 3D engine versus HTML/CSS?

You don't need to use a game engine; traditional GUI toolkits did this stuff out of necessity due to much slower hardware available at the time.


Games also often render text.

> And how easy is to use a modern 3D engine versus HTML/CSS?

CSS transforms include a little 3D "engine"! https://polypane.app/css-3d-transform-examples/


Modern computers are incredibly fast. We're just used to layers upon layers of inefficient abstractions. The GPU can animate many millions of vertices per second, the CPU can crunch GB/s of data, but on the high level it ends up so absurdly inefficient that 365 boxes sounds like a lot.


No, it should barely be challenging on a 8-bit CPU.


AppKit + CALayers makes this trivial (performance wise) on the Mac. It only gets challenging when you have no granular control over the UI framework. If it forces you to make every little interactive element its own view ... well, good luck optimizing performance.


It's very different due to how the languages work. In React, you're creating JS objects. Naively, each of those is a heap allocation and a bunch of initialisation. With Swift on the other hand, you're creating Views which are structs, which are value types, allocated on the stack, don't have much if any initialisation to do, and are much cheaper to work with.

I say naively, because JS interpreters are probably doing smart things to try to minimise the impact of this sort of allocation of many small objects, but they still come at a cost that just isn't present in Swift.


"Developer doesn't read best practices documents, thinks he can outperform lazy views and shoots himself in the foot". Feels like a remake of last year's React version, and a sequel is coming out with Compose & Flutter.

SwiftUI & AppKit just moves complexity around. Simple UIs take up a lot of work with AppKit just to set things up, but are easier to maintain when it gets complex, and vice versa. In the case of the calendar view rerendering everything, maybe don't pass world changing updates to your top level component. In Compose world, this is using a lambda for state reads to delay them as much as possible.


Author here! My best selling app Lunar (https://lunar.fyi/) is done in AppKit with Storyboards and it’s a pain to maintain (source here: https://github.com/alin23/Lunar).

So much that I finally created a SwiftUI menubar menu for it where I can add the more dynamic bits easily, and just leave the AppKit UI for static preferences stuff.

Both AppKit and SwiftUI are good though, Lunar was the first time I was able to create a native UI from scratch and also release it without remorse.

But I felt that the dynamic parts were much harder to reason about in AppKit, mostly because of the Delegate paradigm.

I also have a preference for a not-so-native look which is much simpler to style using SwiftUI modifiers like clipShape, shadow etc.


> My best selling app Lunar (https://lunar.fyi/) is done in AppKit with Storyboards and it’s a pain to maintain (source here: https://github.com/alin23/Lunar)

I just wanted to say that you can and most likely should use UIKit without touching interface builder. It's been over 5 years since I've used interface builder and I have no regrets. Just build your UI programatically relying mostly on UIStackViews, constraints and multiple levels of views embedded in views. Write your own helpers on top of UIKit to help you compose views in a more compact way. I basically have a dozen functions I re-use over and over and over to build complex UI from these simple primitives.


"just" is doing a lot of heavy lifting here


I'm probably 2 to 5x times more productive building UIs now than I ever was using interface builder. To say nothing about having proper version control and being able to abstract views much better.

Will it be painful when you start? likely, but since you are exposed to everything your programmer brain will figure out how to solve the repetitive tasks very fast by building a helper function.


To me, writing UIs in code is one of the more terrible ideas to come out of modern iOS development.


Can you point, please, to these best practices documents that would have helped the author avoid the problems?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: