4 ms·
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 experi
by iamcalledrob 4y ago
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.
- jarjoura 4y agoExcept 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"
- hbn 4y agoI 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
- saghm 4y ago> 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?
- lilyball 4y agoThe 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.
- saghm 4y agoOh, 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&edition=2021&gist=59e0f0049d2b3d0b676ffda2e512ccf8 https://play.rust-lang.org/?version=stable&mode=debug&editio...).
- lilyball 4y agoAccording to https://github.com/apple/swift/blob/6d2a3bbf9d518c2ff8c637313d1a459f40e5d6bc/docs/TypeChecker.md 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.