Skip to content

iOS: Picker(onChange) never fires — UISegmentedControl not wired to valueChanged (only geisterhand registration) #5201

@proggeramlug

Description

@proggeramlug

Perry version: 0.5.1161 (iOS)

Summary

Picker(onChange) on iOS never invokes its onChange callback when the user taps a segment. The picker is a UISegmentedControl, but create() only registers the callback with the geisterhand test harness — it never wires addTarget:action:forControlEvents:UIControlEventValueChanged. So for real users the picker is inert (selecting a value does nothing).

Cause

// crates/perry-ui-ios/src/widgets/picker.rs
pub fn create(_label_ptr: *const u8, _on_change: f64, _style: i64) -> i64 {
    ... let obj = UISegmentedControl ...
    // PICKER_ITEMS / PICKER_SELECTED set up
    #[cfg(feature = "geisterhand")] { perry_geisterhand_register(handle, 4, 1, _on_change, _label_ptr); }
    handle   // <-- _on_change is never connected to the control's valueChanged event
}

There's no target/action, so UIControlEventValueChanged never reaches _on_change.

Impact

Every on-device Picker (segmented control) is non-functional — selecting a segment does nothing. In our app this silently broke a sort picker and a 7d/28d/90d range picker.

Fix (applied locally, PR-able)

Add a target object (mirroring the toggle's PerryToggleTarget) whose action reads selectedSegmentIndex and dispatches _on_change async to the main queue (so it doesn't re-enter the JS runtime synchronously inside UIKit's valueChanged — the same pattern the button path uses to avoid iOS-26 crashes):

let target = PerryPickerTarget::new();
let addr = Retained::as_ptr(&target) as usize;
target.ivars().callback_key.set(addr);
PICKER_CALLBACKS.with(|c| c.borrow_mut().insert(addr, on_change));
let sel = Sel::register(c"segmentChanged:");
msg_send![&*view, addTarget: &*target, action: sel, forControlEvents: 1u64 << 12];
std::mem::forget(target);
// segmentChanged: reads selectedSegmentIndex, then dispatch_async_f → js_closure_call1(closure, index)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions