UIComponent Documentation

repository·main·Indexed 19 days ago

https://github.com/lkzhao/uicomponent

A declarative framework for building UIKit-based user interfaces using a syntax and mental model similar to SwiftUI. It features a multi-stage rendering pipeline (Component, RenderNode, RenderNodeChild, and Renderable), built-in layout components like VStack and HStack, and support for SwiftUI integration in v5.0+. The framework includes an Animator system with TransformAnimator and FadeAnimator for handling view transitions, as well as a ComponentEngine to manage UI reloads and rendering.

Tokens
15.5K
Snippets
38
Records
56
Agent score
55%

What's inside UIComponent

  1. Overview of UIComponent

    main

    UIComponent is a modern, declarative framework for building user interfaces using UIKit. It leverages @resultBuilder and @dynamicMemberLookup to provide a syntax similar to SwiftUI, allowing for easy construction of UIKit interfaces.

    Key characteristics include:

    • Declarative Syntax: Build UIKit interfaces using a SwiftUI-like DSL.
    • Unidirectional Data Flow: Embraces a single direction of data flow instead of traditional two-way binding.
    • SwiftUI Integration: Since version 5.0, UIComponent can render SwiftUI Views alongside standard UIViews and other components.
    • Performance: Designed with optimization opportunities in mind.
  2. Perform conditional and list rendering in Components

    main

    UIComponent supports standard Swift control flow structures within its result builders. You can use if or switch statements for conditional rendering and for-in loops to render lists of items.

    VStack {
        for item in items {
            if let image = item.image {
                Image(image)
            }
            switch item.type {
            case .fruit:
                Text("Fruit")
            case .vegetable:
                Text("Vegetable")
            }
        }
    }
  3. Built-in Animator types: TransformAnimator and FadeAnimator

    main

    UIComponent provides two built-in animator types:

    1. TransformAnimator: Handles insertion, deletion, and frame updates using CATransform3D. Note that TransformAnimator does not animate the very first reload; it only animates subsequent updates.
    2. FadeAnimator: Handles insertion and deletion transitions using opacity, but does not perform transform or frame update animations.
  4. Performance characteristics of UIComponent

    main

    Recreating the component tree on every state change is efficient due to several architectural design choices:

    • Value Types: Components are Swift value types, making them extremely cheap to construct on the stack.
    • Smart Reconciliation: UIComponent compares the old component tree with the new one and only applies necessary changes to the UIView hierarchy (similar to React's Virtual DOM).
    • Lazy View Creation: Expensive UIViews are only instantiated when they become visible.
    • View Recycling: UIComponent recycles views that are no longer visible, similar to UITableView behavior.
  5. How Swift Observation works with UIComponent

    main

    Starting in iOS 26, UIComponent integrates with the Swift Observation system. When you access properties of an @Observable object inside UIViewController.updateProperties() or ComponentView.updateProperties(), UIComponent automatically tracks those dependencies. Whenever an observed property changes, UIComponent re-runs the corresponding updateProperties() method, allowing you to declaratively update your UI based on the latest model state, similar to SwiftUI.

    To ensure this works correctly:

    1. Access the observed values directly within the updateProperties() method.
    2. Always call super.updateProperties() to maintain internal UIKit bookkeeping.
    3. Assign the new component tree directly to view.componentEngine.component (for UIViewController) or component (for ComponentView).
    // Concept: Accessing @Observable data inside updateProperties triggers automatic re-renders
    override func updateProperties() {
        super.updateProperties()
        // Accessing viewModel.count here registers it for observation
        component = Text("Count: \(viewModel.count)")
    }
  6. How flex layout modifiers work in version 5.0

    main

    The .flex() modifier has been simplified. It no longer needs to be the outermost modifier; you can apply other modifiers (like .size()) after calling .flex(). Additionally, .flex() can now be used inside a ComponentBuilder implementation, and it will be effective immediately without needing to be applied externally.

    // After version 5.0
    VStack {
        Text("Hello").flex().size(height: 50)
        Image("icon").flex().size(width: 100)
    }
    
    // Using .flex() inside a ComponentBuilder
    struct MyComponent: ComponentBuilder {
        func build() -> some Component {
            Text("Hello").flex()
        }
    }
  7. Implement custom layout components and RenderNodes

    main

    To implement a custom layout component, use the Component/layout(_:) method to calculate the frames of children. You must return a RenderNode that encapsulates the layout information, including the component's size, the children (RenderNodes), and their positions.

    Built-in Layout RenderNode Types

    UIComponent provides four specialized RenderNode types to optimize rendering based on visibility:

    RenderNode TypeBehaviorOptimization/Requirements
    VerticalRenderNodeRenders children inside the visible frame.Optimized for vertical lists. Uses binary search. Children must be sorted by y position. Requires StackRenderNode/mainAxisMaxValue (max height).
    HorizontalRenderNodeRenders children inside the visible frame.Optimized for horizontal lists. Uses binary search. Children must be sorted by x position. Requires StackRenderNode/mainAxisMaxValue (max width).
    SlowRenderNodeRenders children inside the visible frame.Slow; loops through all children to check visibility.
    AlwaysRenderNodeRenders all children at all times.No visibility optimization.

    Required RenderNode Data

    All layout RenderNodes must provide:

    • RenderNode/size: The total size of the component.
    • RenderNode/children-42h1l: The RenderNodes of the children.
    • RenderNode/positions-6f59e: The positions of the children.
  8. Understand the UIComponent rendering architecture

    main

    UIComponent uses a multi-stage pipeline to transform a declarative UI definition into actual UIView instances. The architecture consists of four main stages:

    1. Component: A tree structure defining the UI. It uses Component/layout(_:) to produce a RenderNode tree.
    2. RenderNode: A tree structure containing layout information such as RenderNode/size, RenderNode/children-85mp2, and RenderNode/positions-34087.
    3. RenderNodeChild: An intermediate tree structure representing only the nodes that should be visible within the current frame. This is used to generate a list of Renderable objects.
    4. Renderable: The final stage representing a UIView that is inserted into the view hierarchy.

    Understanding this flow is essential for debugging layout issues or implementing custom rendering logic.

  9. Recommended architecture for UIComponent

    main

    Since UIComponent is unopinionated about state management, it is highly compatible with existing architectural patterns.

    For centralized state management, it is recommended to use a unidirectional data flow (Redux-like) architecture. This ensures a single source of truth for the application state, which can then be used to drive the reloadComponent() calls throughout the UI.

  10. UIComponent Advantages and Performance Features

    main

    UIComponent is optimized for performance and control, particularly in UIKit environments. Key advantages include:

    Performance

    • Optimized Lists: Features global cell reuse, renders only visible views, and supports background thread layout.
    • Layout Control: Provides more granular control over the layout and rendering process, making it easier to build advanced features or optimize performance-critical surfaces.

    Developer Experience & Architecture

    • Simpler Mental Model: Uses a unidirectional data flow. It focuses strictly on the UI layer, meaning it does not include state management or two-way binding, making it easier to reason about.
    • Advanced Syntax: Supports for-loop and switch statements directly within the resultBuilder.
    • UIKit Integration: Supports ViewController transitions and integrates seamlessly with existing UIKit codebases.
    • Stability & Maintenance: Offers support for lower iOS versions and can be updated independently of OS updates. It is designed to be less buggy than SwiftUI in complex scenarios.
  11. When to implement a custom RenderNode

    main
    Implementing a custom RenderNode is a complex task. For most UI requirements, you should prefer creating a custom Component or using an existing RenderNode instead. Only implement a custom RenderNode if your task requires direct control over the rendering lifecycle or layout logic that cannot be achieved through the standard component model. Before attempting this, ensure you understand the project's core architecture.
  12. Compare UIComponent and SwiftUI

    main

    UIComponent was designed to bridge the gap between SwiftUI's declarative resultBuilder syntax and UIKit. While SwiftUI is Apple's primary declarative framework, UIComponent offers specific advantages for UIKit-based projects and performance-critical surfaces.

    Platform Support Comparison

    FeatureUIComponentSwiftUI
    Platform SupportiOS, macCatalyst, tvOS, visionOSAll Apple platforms
    Widget SupportNoYes

    Core Similarities

    • Both are declarative UI frameworks.
    • Both utilize resultBuilders for defining UI structure.
    • Both use value types (structs) to define UI components.

    Choosing the Right Framework

    • Use SwiftUI if: You are building a new app from scratch, need full platform support (including macOS/watchOS), require local state management, or want to leverage built-in views and custom shaders (iOS 17+).
    • Use UIComponent if: You need seamless UIKit integration, require high-performance lists (global cell reuse, background thread layout), need to support older iOS versions, or want more control over the layout and rendering process without being tied to OS updates.

    Note: Both frameworks can coexist within a single project. It is recommended to use the tool best suited for the specific task at hand.