Notes from the SwiftUI Lab:
- Avoid glass overlapping glass
- Avoid glass in anything that scrolls
- Use glass when you want to grab attention
- Remember to put glass in appropriate GlassEffectContainers
- Move tinting away from NavBar into the content that scrolls behind the NavBar glass
- LazyVStack in ScrollView: ForEach should return a static number of Views so the system knows what a "row" is
- Use new performance instrument to find scrolling issues
- "New design is still in beta"; issues like dark background behind glass being unreadable should be filed as feedback
- Generally embrace SwiftUI for the app lifecycle even when using UIKit/AppKit for core navigation/structure
- MatchedTransitionSource: Use to connect different Views and presentations
- Singleton or EnvObject? Prefer Environment in SwiftUI (also helpful for Preview context)
- System uses less colors now; avoid arbitrary tinting - use to draw attention or indicate states
- Use ContainerValues to pass data up the hierarchy (not Bindings) or PreferenceKeys or Closures (for actions/UIKit)
- Animations are available for widgets on visionOS (same as since iOS 17)
- Known issue in beta where link gestures don't work
- Use standard system controls
- SafeAreaBar API for custom TabBar
- NavLink uses trailing chevron SF Symbol (useful for custom buttons)
- Fluidly draggable component like TabBar: Currently no API for that, file feedback if interested in this
- Unit test SwiftUI Views: Bugs are mostly in business logic -> catch them there instead of testing SwiftUI Views, e.g. test that the NavPath of a NavModel contains .home instead of a snapshot test for login
- Keep Views as small as possible and use Models
- "Whatever architecture you pick, have a testable architecture"
- Consider what work can be done outside the main thread (for performance optimization)
- Consider scaling high-res images down to display size before showing them
- Watch out for View inits & bodies being too expensive -> break up the body into multiple Views
- Color.random in the background of list rows -> if its flicking during scrolling, your Views are invalidating
- DatePicker disrespecting dynamic size is a known issue
- Common SwiftUI antipatterns: conditional modifiers using if statements; public static let shared; if statements for NavStack in NavSplitView detail column (instead: ContentUnvailableView inside NavStack); Not using a11y representations instead of label & value; if statements "loose state" on change
- Architecture depends on the app/developer preference -> SwiftUI is architecture-agnostic; lean into Observable (also compatible with UIKit)
- ObservableObject led to needlessly invalidated Views, which is "fixed" with Observable
- Debug SwiftUI: Knowing why the View updated/was invalidated -> let _ = Self._printChanges(); conditional breakpoints; even they use print debugging :) ; comment-out-debugging to narrow down on issues