Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS runtime using uikit. #631

Closed
wants to merge 49 commits into from
Closed

Conversation

simlay
Copy link
Contributor

@simlay simlay commented Nov 24, 2020

I started this prototype back in March with no real end in sight. I used it as the goal runtime for uikit-sys. I hope to soon write a blog post on some of the design decisions around this PR. Maybe Adding UIKit support to iced. It would be one of the continued posts from Building an (almost entirely) rust iOS app using uikit-sys..

Anyway, I recently got asked if I was still working on this by someone who just happened to find my fork of iced and then found me on discord and was curious if I was still working on it so I figured I should actually submit this work as a demonstration for how much work it is and how much of a commitment you'd need from the community.

I originally got started on this whole area of the rust ecosystem because I disliked how brittle the react-native build system was (and I like rust more than JS). After spending ~6 months off-and-on playing with this and adding tons of objective-c support to rust-bindgen, it's not clear to me if there is enough iOS rust people in the community. I hope that publishing this PR could show some light on to how difficult it is.

To be clear, I've not really done any additional work on this since maybe august and it's definitely a mess (sorry). I recently got a job and am not sure I'll have the time to clean this up so I'd like to at least share the (very messy) work and maybe either I or someone else will come along and pick up the reins.

simlay added 30 commits May 28, 2020 13:55
@hecrj hecrj added the help wanted Extra attention is needed label Nov 25, 2020
@mtsr
Copy link
Contributor

mtsr commented Nov 25, 2020

I'm the one who pinged @simlay . In the context of ReactNative, Flutter, SvelteNative (powered by NativeScript), etc, this seems like a good step in establishing Rust as a tool for crossplatform development without needing to resort to JS based UIs.

Hopefully there will be some developers with iOS (and/or FFI) experience who are interested in helping move this forward. I've done a little FFI, but not enough to confidently help create a solid Rust wrapper for uikit-sys.

Maybe while this is still a work in progress, getting iced working well on iOS with input etc while rendering to a CAMetalLayer would be a good first step. Maybe it would even be possible to incrementally replace components with native ones, starting from top-level components while still rendering intermediate and leaf components to CAMetalLayer.

@matthieugayon
Copy link

matthieugayon commented Nov 26, 2020

Maybe while this is still a work in progress, getting iced working well on iOS with input etc while rendering to a CAMetalLayer would be a good first step. Maybe it would even be possible to incrementally replace components with native ones, starting from top-level components while still rendering intermediate and leaf components to CAMetalLayer.

Is it how bevy is managing to render on IOS ? Would bevy be a good inspiration for such an implementation in iced ? @simlay you seem to be pretty well acquainted to bevy's IOS support, would you have any tip or a simple explanation at how we could do that for iced ?

Actually i just found out about #57, is that a step in that direction ?

@justinmoon
Copy link

Just want to make sure you've seen Polyhorn. Might find some inspiration there!

@mtsr
Copy link
Contributor

mtsr commented Dec 2, 2020

Just want to make sure you've seen Polyhorn. Might find some inspiration there!

I came across its slick-looking marketing site. It looks promising, but it seems to be a fairly young project (first commit October 10th) with currently only a single contributor (@cutsoy). I also couldn't find any reddit, users.rust-lang.org, etc posts about it, although that could just mean it's not ready for primetime yet. I noticed the spectrum chat link, but that's yet another chat service, that I wasn't interested in adding to the growing list of similar services quite yet.

From what I can tell it would be more of an alternative to iced, rather than a library that could be used with iced. Although modular, it's currently not implemented in a way that would allow code reuse in another context. Even though there seem to be parts that would be really useful for the larger cross-platform-gui community, such as polyhorn-ios-sys and the pieces of polyhorn-ios under raw.

I also couldn't quite tell where the design was going with the central Manager. But I have to admit I didn't work with React since the introduction of hooks.

@cutsoy
Copy link

cutsoy commented Dec 2, 2020

Hi, thanks for mentioning Polyhorn! Indeed, it's not yet finished. The iOS implementation is relatively comprehensive in terms of rendering (see examples), but not yet in terms of events (e.g. click events don't yet have coordinates, etc.).

On iOS, a Polyhorn app in Rust initially compiles to a single static library that mounts a user-given component onto an UIView, UIWindow or UIApplication (the default), etc. By using the polyhorn-cli, applications are then wrapped into a .app or .apk (along with icons, etc.). Therefore, by default it takes over responsibility of the entire UIApplication (which is the most common use case), but it should be possible to use Polyhorn only for a single UIView too.

One thing to keep in mind is that Polyhorn mostly runs on its own thread to render virtual representations of the UI and only commits them on the main thread after it has completely finished. As such, there are a few platform-specific tricks to integrate message passing between the UI thread and the "worker" thread (libdispatch on iOS, Looper on Android) and to ensure that UI objects only get allocated and deallocated on the UI thread (with the exception of UIImage and Bitmap).

These (and many other) aspects are very similar to React and React Native.

But indeed, it's not yet ready for production use. And I want to wait until the Android implementation is more complete before sharing it on Reddit and users.rust-lang.org, since cross-platform support is one of the main USPs with respect to e.g. SwiftUI.

I also couldn't quite tell where the design was going with the central Manager. But I have to admit I didn't work with React since the introduction of hooks.

Hooks are really nice in React (despite my general distaste for JS):

function SignupForm() {
    const [email, setEmail] = useState("");

    return <View>
        <TextInput value={ email } onChange={ setEmail } />
    </View>;
};

The problem is: React's hooks use globals and this only works because JS is single-threaded. I don't like globals, so instead I pass a "manager" to each render function. In JS, that would be equivalent to doing this:

// Pseudo-code: this doesn't actually exist in React today.
function SignupForm(manager) {
    const [email, setEmail] = manager.useState("");

    return <View>
        <TextInput value={ email } onChange={ setEmail } />
    </View>;
};

Equivalently, in Polyhorn, this would be:

#[derive(Default)]
pub struct SignupForm {}

impl SignupForm {
    fn render(&self, manager: Manager) {
        let email = use_state!(manager, "");
        let on_change = manager.bind(move |link, value| {
            email.replace(link, value));
        };

        poly!(<View>
            <TextInput value={email.get(manager)} on_change=on_change />
        </View>)
    }
}

The reason for using manager.bind(closure) is to ensure that the closure is only called when the component is still alive. The ground rule is: if you can acquire a link to a component, any operation on that link will always succeed, guaranteed. (Manager also implements Link.) As to why this is useful, try doing this in React:

class OldStyleComponent extends React.Component {
    state = { marker: false };

    componentWillUnmount() {
        // Try to modify the state of a component after it has been unmounted.
        setTimeout(() => {
            this.setState({ marker: true });
        }, 1000);
    }

    render() { return null; }
}

This will result in a runtime exception in JS, whereas in Rust you will not even be able to compile code without a valid Link.

@cryptoquick
Copy link

cryptoquick commented Dec 4, 2020

I actually prefer Iced's approach for establishing a way to describe a user interface that doesn't rely on macros and event-handling techniques and data flow patterns that might make it easier to adopt for React developers, but instead, Iced prioritizes technical decisions that make the framework and apps built in it more versatile, while also maintaining idiomatic synchronicity with the broader Rust ecosystem. This goes beyond a surface-level criticism of using React-style idioms for UI description (JSX), but also how remarkably well The Elm Architecture (TEA) integrates with Rust, compared to the borrowing gymnastics I've done in similar React-likes like percy.

I will acknowledge, there are still a lot of questions Iced leaves unanswered, but I have been impressed by the community that has sprung up around this fledgling project in a way I would expect of projects with fancier marketing pages. I think the degree of feature-completion, stability, versatility, and ease of use (after working to disabusing oneself of React-style approaches) is actually quite compelling, and I suspect there might be another way to approach, inspired by @matthieugayon 's suggestion on this issue:
#57 (comment)

I, too, suspect cargo-mobile, which is used by Bevy, which was already mentioned, might facilitate integration better since it advertises its support for both wgpu and winit integrations. I'm not currently qualified to be able to make the assessment which approach would be better, but I'd probably suggest looking into winit integration first. I can't take this work on at the moment, however, due to having other responsibilities (I'm currently building an Iced project, but mobile support is not currently planned), but I'd be curious what others think, or could do.

I also have to say, I'm very excited to hear that others are seeing the promise inherent to this project. Rust has been described as a sort of "Goldilocks Language"; the way I'd put it, succinctly, is it's not too difficult, but also, not too awful. I see much of the same promise in Iced. Things that have superior balance often are most successful... as all things should be. Heh. I also foresee there might be a point where SSR could perhaps be added to Iced, to the degree where it could compile to a serverside WASM binary that targets serverless solutions. Realistic WASI-oriented approaches could also be easier to achieve.

And of course, mobile support is probably the most critical missing piece currently, but that can't be left up to a single developer to plan, coordinate, develop and support. The Rust community is remarkably engaged, in both breadth (diversity of projects, allowing for practical exploration of the problem space), and depth (how large the community around any single project winds up being, including activity in the codebase, issues handled, diversity in individual contributors, and of course, adoption, etc.). @hecrj would be the best to confirm this, but I suspect that this project is approaching the degree of critical mass required to resolve concerns around the bus problem. Similar to how Rust is succeeding without the active support of a sole Benevolent Corporation For Life (Mozilla), other than maintaining critical community infrastructure (Rustup, Crates, Docs.rs, BORS, etc.). Rust's staying power is because of its distributed approach to contributions, rather than mandating a prescriptive "solved problem" approach to some problems. Heck, even tokio has promising competition. It's been impressive, the amount that Rust has grown in the past 3 years I've personally been using it, without diminishing the things that made it popular in the first place.

Regardless of my own immediate needs, however, I see it as critically important that Iced see aarch64-native support for mobile platforms (not just in a webview, like Capacitor or Cordova). It will reduce perceived obstacles to adoption, and also let application developers to solve more problems, which (usually) benefits all other users of a project trying to solve such a complex, multifaceted problem (multiplatform native UI), and who knows when you'd like to reuse the same skills you developed in replacing an Electron app with poor performance, to also replace a React-Native app, and then eventually consuming the entirety of whatever project is being worked on, replacing the JS (or compile-to-JS app) it was modeled off of in the first place, into not just a monorepo, but a single cohesive project.

  • Sincerely, a Rust hipster and Iced cheerleader

@simlay simlay mentioned this pull request Jun 1, 2021
@hecrj
Copy link
Member

hecrj commented Aug 12, 2021

As I mentioned in #302, I believe we should keep mobile out of the scope of the project. The scope is already ambitious enough and the core maintainers of the library would not be able to maintain this code if we were to merge this.

If anyone wants to take the lead here, I think the best path forward would be to maintain a fork of iced. I'd love to see that happen!

@hecrj hecrj closed this Aug 12, 2021
@hecrj hecrj mentioned this pull request Oct 20, 2021
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants