module UI::Native::Populator

Overview

SwiftKit override populator.

Phase 3's renderer migration moves overrides population OUT of each visit method and INTO this module so the populator can be exercised in plain crystal spec runs (no -Dmacos / -Dios flag, no AppKit / UIKit / SwiftKit frameworks linked).

The contract:

Populator.populate_button(overrides_ptr, view) walks the UI::Button's common-view properties (background, corner_radius, padding, opacity, hidden, border_, shadow_, min/max_*, accessibility_label, test_id) PLUS the button-specific properties (role, style, disabled, symbol) and ONLY sends the setter when the property differs from its type default. This is the "default-detection" invariant — implementation.md §11 calls it "the single most important behavioral invariant of phase 3."

The populator does not call objc_msgSend directly. It calls into an injected Sender interface. In production builds the sender is LibObjCBridge-backed (renderer integration); in spec builds it is FakeLibObjCBridge-backed and merely records each setter invocation.

This indirection lets the renderer's visit method shrink from ~230 lines to ~5: populate_button(ovr, view); make_button(...).

Defined in:

ui/native/swiftkit_overrides.cr

Class Method Summary

Class Method Detail

def self.objc_setter_selector(setter : Symbol) : String #

Symbol-to-ObjC-selector helper. The Populator emits setter symbols without a trailing colon (:setStyle, :setBackgroundColor) because that's the shape the spec recording sender asserts against. ObjC selectors for single- argument setters need the colon; the production SwiftKitObjCSender routes through this helper at the boundary so the populator contract and the spec recording remain symmetric.

Idempotent: passing a Symbol that already ends in : returns its String form unchanged so a future caller that knows the ObjC convention can pass the canonical Symbol directly.


[View source]
def self.populate_alert(target : String, view : UI::Alert, sender : Sender) #

[View source]
def self.populate_button(target : String, view : UI::Button, sender : Sender) #

Populate an APSKButtonOverrides instance from a UI::Button.

The target parameter is the Crystal-side identifier for the overrides object (in production it's a stringified pointer; in specs it's the sentinel from FakeLibObjCBridge.next_sentinel_pointer).


[View source]
def self.populate_card(target : String, view : UI::Card, sender : Sender) #

[View source]
def self.populate_checkbox(target : String, view : UI::Checkbox, sender : Sender) #

[View source]
def self.populate_color_picker(target : String, view : UI::ColorPicker, sender : Sender) #

[View source]
def self.populate_confirmation_dialog(target : String, view : UI::ConfirmationDialog, sender : Sender) #

[View source]
def self.populate_date_picker(target : String, view : UI::DatePicker, sender : Sender) #

[View source]
def self.populate_divider(target : String, view : UI::Divider, sender : Sender) #

[View source]
def self.populate_form(target : String, view : UI::Form, sender : Sender) #

[View source]
def self.populate_glass_background(target : String, view : UI::GlassBackground, sender : Sender, apple_step : Symbol = view.material) #

Glass — the Phase 3 "headline visual differentiator". On iOS 26 / macOS 26 the facade routes through .glassEffect() for real Liquid Glass; on pre-26 OSes it falls back to .background(<Material>).

material mirrors the Crystal UI::GlassBackground.material symbol (:regular | :thin | :ultra_thin | :thick | :chrome). The facade switch normalises camelCase keys (ultraThin) on the Swift side; we emit them in the same shape so the facade dispatch stays simple. Glass populator. apple_step is the Apple-quantized step Symbol the renderer has resolved via tokens.material.apple_step(view.material). The populator emits the facade setMaterial: key derived from the resolved Symbol, not from view.material directly, so brand intensity overrides cascade onto the SwiftUI Material enum case per the Phase 5 brief's adapter_cardinality row 1 contract.

apple_step defaults to view.material so spec-level callers that don't have a renderer can still exercise the populator without threading tokens through the test fixtures.


[View source]
def self.populate_grid(target : String, view : UI::Grid, sender : Sender) #

[View source]
def self.populate_icon_button(target : String, view : UI::IconButton, sender : Sender) #

[View source]
def self.populate_image(target : String, view : UI::Image, sender : Sender) #

[View source]
def self.populate_label(target : String, view : UI::Label, sender : Sender) #

[View source]
def self.populate_link_button(target : String, view : UI::LinkButton, sender : Sender) #

[View source]
def self.populate_list_view(target : String, view : UI::ListView, sender : Sender) #

UI::ListView (§6 #25). The Crystal side carries a sectioned list (Array(Section)) with optional headers / footers per section and a list-wide ListStyle enum. The Swift facade rebuilds the SwiftUI List { Section { ... } } hierarchy from a flat childViews array sliced by setSectionItemCounts.

selection_mode is not a Crystal-side property today (verified against src/ui/views/list_view.cr); the facade therefore uses SwiftUI's default selection model. The on_item_tap Proc lives on the view itself and is wired by the renderer's visit method via callback-token registration (not through overrides).


[View source]
def self.populate_menu_button(target : String, view : UI::MenuButton, sender : Sender) #

[View source]
def self.populate_navigation_link(target : String, view : UI::NavigationLink, sender : Sender) #

[View source]
def self.populate_navigation_split_view(target : String, view : UI::NavigationSplitView, sender : Sender) #

[View source]
def self.populate_navigation_stack(target : String, view : UI::NavigationStack, sender : Sender) #

[View source]
def self.populate_picker(target : String, view : UI::Picker, sender : Sender) #

[View source]
def self.populate_popover(target : String, view : UI::Popover, sender : Sender) #

[View source]
def self.populate_radio_group(target : String, view : UI::RadioGroup, sender : Sender) #

[View source]
def self.populate_search_field(target : String, view : UI::SearchField, sender : Sender) #

[View source]
def self.populate_secure_field(target : String, view : UI::SecureField, sender : Sender) #

[View source]
def self.populate_segmented_control(target : String, view : UI::SegmentedControl, sender : Sender) #

[View source]
def self.populate_sheet(target : String, view : UI::Sheet, sender : Sender) #

[View source]
def self.populate_slider(target : String, view : UI::Slider, sender : Sender) #

[View source]
def self.populate_spacer(target : String, view : UI::Spacer, sender : Sender) #

[View source]
def self.populate_stepper(target : String, view : UI::Stepper, sender : Sender) #

[View source]
def self.populate_surface(target : String, view : UI::Surface, sender : Sender) #

[View source]
def self.populate_tab_view(target : String, view : UI::TabView, sender : Sender) #

[View source]
def self.populate_text_area(target : String, view : UI::TextArea, sender : Sender) #

[View source]
def self.populate_text_editor(target : String, view : UI::TextEditor, sender : Sender) #

[View source]
def self.populate_text_field(target : String, view : UI::TextField, sender : Sender) #

[View source]
def self.populate_time_picker(target : String, view : UI::TimePicker, sender : Sender) #

[View source]
def self.populate_toggle(target : String, view : UI::Toggle, sender : Sender) #

[View source]
def self.populate_toggle_button(target : String, view : UI::ToggleButton, sender : Sender) #

[View source]
def self.populate_toolbar(target : String, view : UI::Toolbar, sender : Sender) #

[View source]
def self.populate_view_common(target : String, view : UI::View, sender : Sender) #

Populate the common APSKViewOverrides fields from any UI::View.

Every per-widget populator calls this helper first, then layers widget-specific setters on top. Keeps each per-widget populator to the minimal "what is unique about THIS widget?" surface.

All setters are skipped when the matching property is at its type default — that is the default-detection invariant in §11.


[View source]
def self.swiftui_font_weight_rawvalue(weight : Symbol) : Int32 #

Map a Crystal UI::Font.weight Symbol to the SwiftUI Font.Weight rawValue Int the Swift facade init reads. The mapping mirrors ButtonFacade.swift's private Font.Weight extension. ultraLight = -3, thin = -2, light = -1, regular = 0, medium = 1, semibold = 2, bold = 3, heavy = 4, black = 5.


[View source]