Packages
mob_dev
0.5.15
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mob_dev/plugin/ios_bootstrap.ex
defmodule MobDev.Plugin.IOSBootstrap do
@moduledoc """
Code-generates the iOS plugin bootstrap Swift source from the activated
plugins' `:ui_components` declarations.
The generated file defines one C-callable function, `mob_register_plugins`,
that registers a factory closure with `MobNativeViewRegistry.shared` for
each component the host should expose. The host's `AppDelegate` calls
`mob_register_plugins()` once, before `mob_init_ui()`, and from that point
every component the BEAM mounts under a plugin's `view_module` key resolves
to the plugin's SwiftUI view.
Plain function, no state — call `swift_source/1` with the same shape
`MobDev.Plugin.activated/0` returns (`[{plugin_dir, manifest}]`) and you
get back the Swift source string ready to write to disk.
Pre-codegen, each plugin shipped its own `@objc class MobXxxPlugin { @objc
class func mob_register() }` and the host hand-wrote one
`[MobXxxPlugin mob_register];` line per plugin into AppDelegate.m. That's
the smoke-test pattern from the 2026-05-28 iOS plugin bring-up. This
module replaces both halves: plugins drop the @objc wrapper and just
ship the SwiftUI struct, and the host always calls a single generated
entry point regardless of how many plugins it has activated.
The mapping from a `ui_components` entry to a `register` call comes from
two manifest fields:
* `ui_components.ios.view_module` — the registry key (string match against
`MobNode.nativeViewModule`); the BEAM derives this from
`Mob.Component.module_name/1` so it must agree with the manifest.
* `ui_components.ios.swift_struct` — the Swift struct name to instantiate
(`StructName(props: props)`). Two plugins are free to register
different `view_module` keys that resolve to the same struct, and the
struct name need not be derivable from the registry key.
A component without `:swift_struct` is silently skipped (the
`MobDev.Plugin.Validator.validate_plugin/3` step is where authors get told
to add the field); the validator's job is to surface this before build
time. The generator stays purely transform-shaped so the build pipeline
never has to think about missing manifest fields.
"""
@doc """
Returns the full Swift source for the bootstrap file.
`plugins` is the activated-plugin list — `[{plugin_dir, manifest}]` —
same shape `MobDev.Plugin.activated/0` returns. Tier-0 (nil-manifest)
plugins contribute nothing. Components missing `:swift_struct` are
silently dropped: the validator is what nags about that.
The output is stable: components are emitted in the order
`MobDev.Plugin.Merge.ui_components/1` lists them (i.e. activation order,
then declaration order within a manifest). Stable output keeps the build
cache hit-rate high.
"""
@spec swift_source([MobDev.Plugin.Merge.plugin()]) :: String.t()
def swift_source(plugins) do
components = MobDev.Plugin.Merge.ui_components(plugins)
body = components |> Enum.flat_map(®ister_lines/1) |> Enum.join("\n")
header() <>
"@_cdecl(\"mob_register_plugins\")\n" <>
"public func mob_register_plugins() {\n" <> body <> "\n}\n"
end
defp header do
"""
// Auto-generated by MobDev.Plugin.IOSBootstrap.
//
// Registers the activated plugins' SwiftUI views with
// MobNativeViewRegistry.shared, one entry per ui_components manifest
// declaration. AppDelegate.m calls mob_register_plugins() once before
// mob_init_ui() so the registry is populated by the time the BEAM
// mounts any plugin-contributed node.
//
// Regenerated on every `mix mob.deploy --native`. Do not edit by hand.
import Foundation
import SwiftUI
"""
end
# One component maps to zero or one register lines: a missing :swift_struct
# means the component can't be registered, so we drop it. The validator
# surfaces this before the build runs.
defp register_lines(component) do
with view_module when is_binary(view_module) <- get_in(component, [:ios, :view_module]),
swift_struct when is_binary(swift_struct) <- get_in(component, [:ios, :swift_struct]) do
[
" MobNativeViewRegistry.shared.register(\"#{view_module}\") { props, _send in",
" AnyView(#{swift_struct}(props: props))",
" }"
]
else
_ -> []
end
end
end