Skip to main content

BoundState

Struct BoundState 

Source
pub struct BoundState<T> { /* private fields */ }
Expand description

Rust-side state a SwiftUI control can both read and write.

This is not @State. SwiftUI’s own property wrappers need the framework to reach into a Swift type’s storage through reflection metadata, which a Rust type does not have. What this gives instead is the half that matters from Rust: a real Binding<Value> backed by Rust closures, so a control renders from Rust state and writes edits straight back into it.

Redisplay is driven explicitly, because SwiftUI cannot observe a Rust value: a Binding’s getter is read only when something else causes the body to be evaluated. Register on_write to be told when a control writes, and answer it by bumping a @State (crate::state::set_state) or by handing the hosting controller a new tree (crate::HostingView::set_root). Without that, an edit moves the control and updates the Rust value while a Text showing that value stays as it was.

Implementations§

Source§

impl<T: Clone> BoundState<T>

Source

pub fn new(initial: T) -> Self

Examples found in repository?
examples/todo.rs (line 37)
35fn draft() -> &'static views::BoundState<String> {
36    static DRAFT: OnceLock<views::BoundState<String>> = OnceLock::new();
37    DRAFT.get_or_init(|| views::BoundState::new(String::new()))
38}
More examples
Hide additional examples
examples/ios_app.rs (line 85)
83fn slider_state() -> &'static views::BoundState<f64> {
84    static S: std::sync::OnceLock<views::BoundState<f64>> = std::sync::OnceLock::new();
85    S.get_or_init(|| views::BoundState::new(0.65))
86}
87
88/// The stepper's value, owned by Rust.
89fn stepper_state() -> &'static views::BoundState<f64> {
90    static S: std::sync::OnceLock<views::BoundState<f64>> = std::sync::OnceLock::new();
91    S.get_or_init(|| views::BoundState::new(3.0))
92}
93
94/// The toggle's state, owned by Rust and read back after every tap.
95fn switch_state() -> &'static views::BoundState<bool> {
96    static S: std::sync::OnceLock<views::BoundState<bool>> = std::sync::OnceLock::new();
97    S.get_or_init(|| views::BoundState::new(false))
98}
examples/scenes.rs (line 45)
43fn counter() -> &'static BoundState<i64> {
44    static C: std::sync::OnceLock<&'static BoundState<i64>> = std::sync::OnceLock::new();
45    C.get_or_init(|| Box::leak(Box::new(BoundState::new(0))))
46}
examples/inbox.rs (line 38)
35fn inbox() -> &'static Inbox {
36    static I: OnceLock<Inbox> = OnceLock::new();
37    I.get_or_init(|| Inbox {
38        query: views::BoundState::new(String::new()),
39        searching: views::BoundState::new(false),
40    })
41}
examples/common/spec.rs (line 442)
436fn flag_slot(name: &str, initial: bool) -> views::BoundState<bool> {
437    slots()
438        .lock()
439        .expect("slots")
440        .flags
441        .entry(name.to_string())
442        .or_insert_with(|| views::BoundState::new(initial))
443        .clone()
444}
445
446fn number_slot(name: &str, initial: f64) -> views::BoundState<f64> {
447    slots()
448        .lock()
449        .expect("slots")
450        .numbers
451        .entry(name.to_string())
452        .or_insert_with(|| views::BoundState::new(initial))
453        .clone()
454}
455
456fn string_slot(name: &str, initial: &str) -> views::BoundState<String> {
457    slots()
458        .lock()
459        .expect("slots")
460        .strings
461        .entry(name.to_string())
462        .or_insert_with(|| views::BoundState::new(initial.to_string()))
463        .clone()
464}
examples/settings.rs (line 32)
29fn settings() -> &'static Settings {
30    static S: OnceLock<Settings> = OnceLock::new();
31    S.get_or_init(|| Settings {
32        name: views::BoundState::new("Ada".into()),
33        notify: views::BoundState::new(true),
34        volume: views::BoundState::new(0.6),
35        theme: views::BoundState::new(0),
36        refresh: views::BoundState::new(5.0),
37        about: views::BoundState::new(false),
38    })
39}
Source

pub fn get(&self) -> T

Examples found in repository?
examples/ios_app.rs (line 41)
38fn on_tap() {
39    TAPS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
40    let s = switch_state();
41    s.set(!s.get());
42    refresh();
43}
44
45/// The one piece of SwiftUI-owned state in the application.
46///
47/// Everything the interface shows lives in Rust. SwiftUI cannot observe a Rust
48/// value, so redisplay needs something it *does* track: a real `@State` on a
49/// type synthesised at run time. Bumping it re-runs the body, which rebuilds
50/// the whole tree from whatever Rust now holds.
51///
52/// The view itself is main-thread only — it holds raw metadata pointers — but
53/// its `id` is just a number, and that is all [`refresh`] needs.
54fn root_view() -> views::ViewHandle {
55    thread_local! {
56        static ROOT: std::cell::OnceCell<swiftui_native::state::StatefulView> =
57            const { std::cell::OnceCell::new() };
58    }
59    ROOT.with(|cell| {
60        let sv = cell.get_or_init(|| {
61            let sv = swiftui_native::state::StatefulView::new(0, |_generation| ui());
62            ROOT_ID.store(sv.id, std::sync::atomic::Ordering::Release);
63            sv
64        });
65        sv.as_view()
66    })
67}
68
69static ROOT_ID: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(-1);
70
71/// Tell SwiftUI that Rust state changed, so it re-runs the body.
72fn refresh() {
73    use std::sync::atomic::Ordering;
74    static GENERATION: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
75    let id = ROOT_ID.load(Ordering::Acquire);
76    if id >= 0 {
77        let n = GENERATION.fetch_add(1, Ordering::Relaxed) + 1;
78        swiftui_native::state::set_state(id, n);
79    }
80}
81
82/// The slider's value, owned by Rust and written on every drag.
83fn slider_state() -> &'static views::BoundState<f64> {
84    static S: std::sync::OnceLock<views::BoundState<f64>> = std::sync::OnceLock::new();
85    S.get_or_init(|| views::BoundState::new(0.65))
86}
87
88/// The stepper's value, owned by Rust.
89fn stepper_state() -> &'static views::BoundState<f64> {
90    static S: std::sync::OnceLock<views::BoundState<f64>> = std::sync::OnceLock::new();
91    S.get_or_init(|| views::BoundState::new(3.0))
92}
93
94/// The toggle's state, owned by Rust and read back after every tap.
95fn switch_state() -> &'static views::BoundState<bool> {
96    static S: std::sync::OnceLock<views::BoundState<bool>> = std::sync::OnceLock::new();
97    S.get_or_init(|| views::BoundState::new(false))
98}
99
100fn ui() -> views::ViewHandle {
101    views::zstack(&[
102        views::background_linear_gradient(
103            &views::color(0.0, 0.0, 0.0, 0.0),
104            &[Color::new(0.06, 0.07, 0.16, 1.0), Color::new(0.24, 0.07, 0.33, 1.0)],
105            (0.0, 0.0),
106            (1.0, 1.0),
107        ),
108        views::scroll(&views::padding(
109            &views::vstack_spacing(
110                &[
111                    header(),
112                    section("Typography", typography()),
113                    section("Shapes", shapes()),
114                    section("Controls", controls()),
115                    section("Symbols", symbols()),
116                    section("Effects", effects()),
117                    section("Layout", layout()),
118                ],
119                18.0,
120            ),
121            16.0,
122        )),
123    ])
124}
125
126/// White text at a given size and weight — the showcase's workhorse.
127fn label_text(s: &str, size: f64, weight: Option<views::Weight>, alpha: f64) -> views::ViewHandle {
128    views::foreground_style_color(
129        &views::font_system(&views::text(s), size, weight, None),
130        1.0,
131        1.0,
132        1.0,
133        alpha,
134    )
135}
136
137fn header() -> views::ViewHandle {
138    views::vstack_spacing(
139        &[
140            label_text("SwiftUI from Rust", 30.0, Some(views::Weight::Bold), 1.0),
141            label_text(entry(), 13.0, None, 0.75),
142            label_text(
143                &format!("taps: {}", TAPS.load(std::sync::atomic::Ordering::Relaxed)),
144                13.0,
145                Some(views::Weight::Semibold),
146                0.9,
147            ),
148        ],
149        4.0,
150    )
151}
152
153/// A titled group: a caption, then the content, over a rounded panel.
154fn section(title: &str, content: views::ViewHandle) -> views::ViewHandle {
155    views::vstack_spacing(
156        &[
157            label_text(title, 12.0, Some(views::Weight::Semibold), 0.55),
158            views::padding(
159                &views::corner_radius(
160                    &views::background(&views::padding(&content, 12.0), 1.0, 1.0, 1.0, 0.07),
161                    14.0,
162                ),
163                0.0,
164            ),
165        ],
166        6.0,
167    )
168}
169
170fn typography() -> views::ViewHandle {
171    views::vstack_spacing(
172        &[
173            views::hstack_spacing(
174                &[
175                    views::font_text_style(&views::text("Title"), views::TextStyle::Title3),
176                    views::italic(&views::text("Italic")),
177                    views::monospaced(&views::text("mono")),
178                ],
179                14.0,
180            ),
181            views::kerning(&label_text("K E R N E D", 15.0, None, 0.9), 2.0),
182            views::foreground_style_linear_gradient(
183                &views::font_system(&views::text("Gradient fill"), 22.0, Some(views::Weight::Bold), None),
184                &[
185                    views::color_value_of(Color::new(1.0, 0.55, 0.25, 1.0)),
186                    views::color_value_of(Color::new(1.0, 0.25, 0.6, 1.0)),
187                ],
188                (0.0, 0.0),
189                (1.0, 0.0),
190            ),
191        ],
192        10.0,
193    )
194}
195
196fn shapes() -> views::ViewHandle {
197    let swatch = |shape: views::Shape, c: Color| {
198        views::frame(&shape.fill(views::color_value_of(c)), 58.0, 44.0)
199    };
200    views::hstack_spacing(
201        &[
202            swatch(views::Shape::circle(), Color::new(0.95, 0.35, 0.30, 1.0)),
203            swatch(views::Shape::capsule(), Color::new(0.98, 0.70, 0.20, 1.0)),
204            swatch(views::Shape::rounded_rectangle(10.0, true), Color::new(0.30, 0.65, 1.0, 1.0)),
205            swatch(views::Shape::ellipse(), Color::new(0.35, 0.85, 0.55, 1.0)),
206        ],
207        10.0,
208    )
209}
210
211fn controls() -> views::ViewHandle {
212    views::vstack_spacing(
213        &[
214            // Two toggles, both starting `false`: one bound to Rust-owned
215            // state, one a constant. If they ever disagree on screen, the
216            // binding's getter is at fault rather than the control.
217            views::toggle_bound("Bound to Rust state", switch_state()),
218            views::toggle("Constant false", false),
219            views::hstack_spacing(
220                &[
221                    label_text(&format!("{:.2}", slider_state().get()), 14.0, None, 0.9),
222                    views::slider_bound(slider_state(), 0.0, 1.0),
223                ],
224                10.0,
225            ),
226            views::stepper_bound(
227                &format!("Stepper: {:.0}", stepper_state().get()),
228                stepper_state(),
229                0.0,
230                10.0,
231                1.0,
232            ),
233            // Reads the stepper's Rust value. A `ProgressView` takes a plain
234            // number rather than a binding, so it can only ever show what it
235            // was built with — which is fine now that every write rebuilds the
236            // body from current state.
237            views::progress(stepper_state().get(), 10.0),
238            views::hstack_spacing(
239                &[
240                    views::button_style(
241                        &views::button("Bordered", on_tap),
242                        views::ButtonStyle::Bordered,
243                    ),
244                    views::button_style(
245                        &views::button("Glass", on_tap),
246                        views::ButtonStyle::Glass,
247                    ),
248                ],
249                12.0,
250            ),
251            views::textfield("A text field", ""),
252        ],
253        12.0,
254    )
255}
More examples
Hide additional examples
examples/todo.rs (line 41)
40fn add() {
41    let text = draft().get();
42    if text.trim().is_empty() {
43        return;
44    }
45    items().lock().unwrap().push(Item { title: text, done: false });
46    draft().set(String::new());
47    state::refresh_animated(state::ease_in_out(0.25));
48}
examples/common/spec.rs (line 391)
387pub fn slot_value(name: &str) -> Option<String> {
388    let g = slots().lock().expect("slots");
389    g.flags
390        .get(name)
391        .map(|v| v.get().to_string())
392        .or_else(|| g.numbers.get(name).map(|v| format!("{:.2}", v.get())))
393        .or_else(|| g.strings.get(name).map(|v| format!("{:?}", v.get())))
394}
395
396/// What every slot currently holds, for a status line or a test.
397pub fn slot_summary() -> Vec<(String, String)> {
398    let g = slots().lock().expect("slots");
399    let mut out: Vec<(String, String)> = g
400        .flags
401        .iter()
402        .map(|(k, v)| (k.clone(), v.get().to_string()))
403        .chain(g.numbers.iter().map(|(k, v)| (k.clone(), format!("{:.2}", v.get()))))
404        .chain(g.strings.iter().map(|(k, v)| (k.clone(), format!("{:?}", v.get()))))
405        .collect();
406    out.sort();
407    out
408}
examples/inbox.rs (line 46)
45fn matching() -> Vec<usize> {
46    let q = inbox().query.get().to_lowercase();
47    MESSAGES
48        .iter()
49        .enumerate()
50        .filter(|(_, m)| {
51            q.is_empty()
52                || m.from.to_lowercase().contains(&q)
53                || m.subject.to_lowercase().contains(&q)
54        })
55        .map(|(i, _)| i)
56        .collect()
57}
examples/scenes.rs (line 54)
48fn main_window() -> ViewHandle {
49    vstack(&[
50        text("A SwiftUI app, no Swift source")
51            .font_size(20.0)
52            .padding(8.0),
53        divider(),
54        text(&format!("count: {}", counter().get()))
55            .font_size(34.0)
56            .padding(12.0),
57        hstack(&[
58            button("−", || counter().set(counter().get() - 1)),
59            button("+", || counter().set(counter().get() + 1)),
60        ]),
61        spacer(),
62        text("⌘, for Settings · the ★ in the menu bar has a menu")
63            .font_size(11.0)
64            .opacity(0.6),
65    ])
66    .padding(24.0)
67    .frame(420.0, 300.0)
68    .focused_scene_value(counter_focus_key(), counter().get())
69    .into_handle()
70}
71
72/// The focused-value key the window publishes its count under.
73///
74/// `.focusedSceneValue(\.key, value)` is how a window tells the commands above
75/// it what is selected. Both halves are synthesised at run time: the
76/// `FocusedValueKey` conformance *and* the `WritableKeyPath<FocusedValues, Int?>`
77/// — `FocusedValues` exports no properties, so unlike `EnvironmentValues` there
78/// is no SwiftUI modifier holding a key path to borrow.
79///
80/// The menu cannot *read* it yet: that needs `@FocusedValue`, a `DynamicProperty`
81/// SwiftUI instantiates itself, which is a different mechanism from anything
82/// here. What this demonstrates is the publishing half, in a real app.
83fn counter_focus_key() -> &'static views::FocusKey {
84    static K: std::sync::OnceLock<views::FocusKey> = std::sync::OnceLock::new();
85    K.get_or_init(|| views::focus_key("sceneCounter"))
86}
87
88fn settings_pane() -> ViewHandle {
89    vstack(&[
90        text("Preferences").font_size(18.0),
91        divider(),
92        text("This pane is a `Settings` scene — the one ⌘, opens."),
93        text(&format!("It sees the same state: {}", counter().get()))
94            .opacity(0.7),
95    ])
96    .padding(24.0)
97    .frame(360.0, 200.0)
98    .into_handle()
99}
100
101fn utility_window() -> ViewHandle {
102    vstack(&[
103        text("Utility").font_size(16.0),
104        text("A `Window`, not a `WindowGroup`: one of it, addressed by id.")
105            .opacity(0.7),
106    ])
107    .padding(20.0)
108    .frame(320.0, 140.0)
109    .into_handle()
110}
111
112fn menu_bar_item() -> ViewHandle {
113    vstack(&[
114        text(&format!("count: {}", counter().get())),
115        button("Reset", || counter().set(0)),
116    ])
117    .padding(10.0)
118    .into_handle()
119}
120
121/// The whole app, as one scene.
122///
123/// Built afresh every time SwiftUI reads `App.body`, which is what makes the
124/// numbers in it live rather than a snapshot taken at launch.
125///
126/// macOS only, and not by choice: `Settings`, `Window`, `MenuBarExtra` and the
127/// whole of `Commands` are `@available(iOS, unavailable)`. iOS gets
128/// [`ios_body`], which is the part of this that exists there.
129#[cfg(target_os = "macos")]
130fn body() -> SceneHandle {
131    scenes! {
132        window_group { main_window() }
133        settings { settings_pane() }
134        window("Utility", "utility") { utility_window() }
135        menu_bar_extra("Count", "star.fill") { menu_bar_item() }
136        commands {
137            // A menu of its own, beside File and Edit.
138            menu("Counter") {
139                vstack(&[
140                    button("Increment", || counter().set(counter().get() + 1)),
141                    button("Decrement", || counter().set(counter().get() - 1)),
142                    divider(),
143                    button("Reset", || counter().set(0)),
144                ])
145            }
146            // In place of the standard File ▸ New.
147            replacing(NewItem) { button("New Count", || counter().set(0)) }
148            // Added after the standard Help entry.
149            after(Help) {
150                button("About this Rust app", || {
151                    eprintln!("built with no Swift compiler in sight");
152                })
153            }
154        }
155    }
156}
examples/settings.rs (line 62)
43fn body() -> View {
44    let s = settings();
45
46    form![
47        section![header: text("Account");
48            hstack![
49                text("Name"),
50                spacer(),
51                textfield_bound("Your name", &s.name).text_field_style(
52                    views::TextFieldStyle::RoundedBorderTextFieldStyle,
53                ),
54            ],
55            toggle_bound("Notifications", &s.notify),
56        ],
57        section![header: text("Playback");
58            vstack_aligned(&[
59                hstack![
60                    text("Volume"),
61                    spacer(),
62                    text!("{:.0}%", s.volume.get() * 100.0).fg_system(SystemColor::Gray),
63                ],
64                slider_bound(&s.volume, 0.0, 1.0),
65            ], HorizontalAlignment::Leading),
66            stepper_bound("Refresh every", &s.refresh, 1.0, 60.0, 1.0),
67        ],
68        section![header: text("Appearance");
69            picker_bound!(
70                "Theme",
71                &s.theme,
72                text(THEMES[0]).tag_int(0),
73                text(THEMES[1]).tag_int(1),
74                text(THEMES[2]).tag_int(2),
75            )
76            .picker_style(PickerStyle::InlinePickerStyle),
77        ],
78        section![
79            button("About", show_about)
80                .a11y_hint("Opens a panel describing this app"),
81        ],
82    ]
83    .form_style(FormStyle::GroupedFormStyle)
84    .sheet_bound(&s.about, about_panel())
85}
86
87/// The sheet, which the user can dismiss — the flag is Rust's, so closing it
88/// writes `false` back rather than leaving the two sides disagreeing.
89fn about_panel() -> View {
90    let s = settings();
91    vstack_spacing(
92        &[
93            system_image("gearshape.fill")
94                .symbol_rendering_mode(Some(views::SymbolRenderingMode::Hierarchical))
95                .view()
96                .font_size(48.0)
97                .fg_system(SystemColor::Blue),
98            text("Settings").text_style(TextStyle::Title).bold(),
99            text!("Signed in as {}", s.name.get()).fg_system(SystemColor::Gray),
100            spacer(),
101            button("Done", hide_about).button_style(views::ButtonStyle::BorderedProminent),
102        ],
103        16.0,
104    )
105    .padding(24.0)
106    .presentation_detents(&[views::PresentationDetent::Medium])
107    .presentation_drag_indicator(Visibility::Visible)
108}
Source

pub fn set(&self, v: T)

Examples found in repository?
examples/settings.rs (line 111)
110fn show_about() {
111    settings().about.set(true);
112    state::refresh();
113}
114
115fn hide_about() {
116    settings().about.set(false);
117    state::refresh();
118}
More examples
Hide additional examples
examples/ios_app.rs (line 41)
38fn on_tap() {
39    TAPS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
40    let s = switch_state();
41    s.set(!s.get());
42    refresh();
43}
examples/common/spec.rs (line 379)
372pub fn set_flag(name: &str, value: bool) {
373    slots()
374        .lock()
375        .expect("slots")
376        .flags
377        .get(name)
378        .unwrap_or_else(|| panic!("no flag slot called {name:?}"))
379        .set(value);
380}
examples/todo.rs (line 46)
40fn add() {
41    let text = draft().get();
42    if text.trim().is_empty() {
43        return;
44    }
45    items().lock().unwrap().push(Item { title: text, done: false });
46    draft().set(String::new());
47    state::refresh_animated(state::ease_in_out(0.25));
48}
examples/scenes.rs (line 58)
48fn main_window() -> ViewHandle {
49    vstack(&[
50        text("A SwiftUI app, no Swift source")
51            .font_size(20.0)
52            .padding(8.0),
53        divider(),
54        text(&format!("count: {}", counter().get()))
55            .font_size(34.0)
56            .padding(12.0),
57        hstack(&[
58            button("−", || counter().set(counter().get() - 1)),
59            button("+", || counter().set(counter().get() + 1)),
60        ]),
61        spacer(),
62        text("⌘, for Settings · the ★ in the menu bar has a menu")
63            .font_size(11.0)
64            .opacity(0.6),
65    ])
66    .padding(24.0)
67    .frame(420.0, 300.0)
68    .focused_scene_value(counter_focus_key(), counter().get())
69    .into_handle()
70}
71
72/// The focused-value key the window publishes its count under.
73///
74/// `.focusedSceneValue(\.key, value)` is how a window tells the commands above
75/// it what is selected. Both halves are synthesised at run time: the
76/// `FocusedValueKey` conformance *and* the `WritableKeyPath<FocusedValues, Int?>`
77/// — `FocusedValues` exports no properties, so unlike `EnvironmentValues` there
78/// is no SwiftUI modifier holding a key path to borrow.
79///
80/// The menu cannot *read* it yet: that needs `@FocusedValue`, a `DynamicProperty`
81/// SwiftUI instantiates itself, which is a different mechanism from anything
82/// here. What this demonstrates is the publishing half, in a real app.
83fn counter_focus_key() -> &'static views::FocusKey {
84    static K: std::sync::OnceLock<views::FocusKey> = std::sync::OnceLock::new();
85    K.get_or_init(|| views::focus_key("sceneCounter"))
86}
87
88fn settings_pane() -> ViewHandle {
89    vstack(&[
90        text("Preferences").font_size(18.0),
91        divider(),
92        text("This pane is a `Settings` scene — the one ⌘, opens."),
93        text(&format!("It sees the same state: {}", counter().get()))
94            .opacity(0.7),
95    ])
96    .padding(24.0)
97    .frame(360.0, 200.0)
98    .into_handle()
99}
100
101fn utility_window() -> ViewHandle {
102    vstack(&[
103        text("Utility").font_size(16.0),
104        text("A `Window`, not a `WindowGroup`: one of it, addressed by id.")
105            .opacity(0.7),
106    ])
107    .padding(20.0)
108    .frame(320.0, 140.0)
109    .into_handle()
110}
111
112fn menu_bar_item() -> ViewHandle {
113    vstack(&[
114        text(&format!("count: {}", counter().get())),
115        button("Reset", || counter().set(0)),
116    ])
117    .padding(10.0)
118    .into_handle()
119}
120
121/// The whole app, as one scene.
122///
123/// Built afresh every time SwiftUI reads `App.body`, which is what makes the
124/// numbers in it live rather than a snapshot taken at launch.
125///
126/// macOS only, and not by choice: `Settings`, `Window`, `MenuBarExtra` and the
127/// whole of `Commands` are `@available(iOS, unavailable)`. iOS gets
128/// [`ios_body`], which is the part of this that exists there.
129#[cfg(target_os = "macos")]
130fn body() -> SceneHandle {
131    scenes! {
132        window_group { main_window() }
133        settings { settings_pane() }
134        window("Utility", "utility") { utility_window() }
135        menu_bar_extra("Count", "star.fill") { menu_bar_item() }
136        commands {
137            // A menu of its own, beside File and Edit.
138            menu("Counter") {
139                vstack(&[
140                    button("Increment", || counter().set(counter().get() + 1)),
141                    button("Decrement", || counter().set(counter().get() - 1)),
142                    divider(),
143                    button("Reset", || counter().set(0)),
144                ])
145            }
146            // In place of the standard File ▸ New.
147            replacing(NewItem) { button("New Count", || counter().set(0)) }
148            // Added after the standard Help entry.
149            after(Help) {
150                button("About this Rust app", || {
151                    eprintln!("built with no Swift compiler in sight");
152                })
153            }
154        }
155    }
156}
Source

pub fn is_persistent(&self) -> bool

Is this state written through to UserDefaults?

Source§

impl<T: Persisted> BoundState<T>

Source

pub fn persistent(key: &str, default: T) -> Self

State backed by UserDefaults, which is what @AppStorage is for.

The stored value wins over default when there is one — including a stored false or 0, which is why defaults::has exists rather than leaning on the typed readers.

Writes go through on set, so a control bound to this persists as the user changes it and comes back to the same value in the next process. SwiftUI is not involved in the storage at all: the value lives in Rust and is mirrored to the same UserDefaults that @AppStorage would have used.

Trait Implementations§

Source§

impl<T> Clone for BoundState<T>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

§

impl<T> !RefUnwindSafe for BoundState<T>

§

impl<T> !UnwindSafe for BoundState<T>

§

impl<T> Freeze for BoundState<T>
where Arc<Mutex<T>>: Freeze, Option<Arc<dyn Fn(&T) + Send + Sync>>: Freeze,

§

impl<T> Send for BoundState<T>
where Arc<Mutex<T>>: Send, Option<Arc<dyn Fn(&T) + Send + Sync>>: Send,

§

impl<T> Sync for BoundState<T>
where Arc<Mutex<T>>: Sync, Option<Arc<dyn Fn(&T) + Send + Sync>>: Sync,

§

impl<T> Unpin for BoundState<T>
where Arc<Mutex<T>>: Unpin, Option<Arc<dyn Fn(&T) + Send + Sync>>: Unpin,

§

impl<T> UnsafeUnpin for BoundState<T>
where Arc<Mutex<T>>: UnsafeUnpin, Option<Arc<dyn Fn(&T) + Send + Sync>>: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.