Inject: a thin SwiftUI and UIKit wrapper around InjectionIII for hot reloading
Hot Reloading for Swift applications!
At a glance
- What is it?
- Inject is a Swift package that wraps InjectionIII so a running iOS, macOS or SwiftUI app picks up saved source changes without a full rebuild. It is for developers who already accept the InjectionIII setup cost, and it is a no-op in non-debug builds.
- Who is it for?
- Adopt Inject if your team already runs InjectionIII and wants to stop writing reload plumbing by hand: the SwiftUI path is two lines per view, and the UIKit path is a wrapper at the parent callsite. Do not adopt it if you cannot add -Xlinker -interposable and EMIT_FRONTEND_COMMAND_LINES to every Debug target, or if your team will not run a separate macOS injection app.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 154 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The rebuild loop Inject is trying to shorten
Changing one line in a view controller normally costs a compile, a relaunch, and a manual walk back to the screen you were editing. Inject targets that loop. The README frames the goal as getting rid of whole-application compiles and deploy/restart cycles so edits to a running app show up close to real time. The scope is narrow on purpose: this is a workflow helper for UIKit, AppKit and SwiftUI, not a general build system. The README says the heavy lifting is done by InjectionIII and describes Inject itself as a thin wrapper that provides the best developer experience possible while requiring minimum effort. That framing matters when you evaluate it. If you already use InjectionIII directly, Inject is mostly an ergonomics layer over the same mechanism. If you have never set up InjectionIII, Inject does not remove that setup; it only changes what you write in your own source once the tooling is running.
How the wrapper decides to reload a view
The mechanism differs between the two UI styles. For SwiftUI, the README gives two steps: call .enableInjection() at the end of your body definition and add @ObserveInjection var inject to the view struct. For UIKit and AppKit, Inject introduces the concept of hosts, ViewControllerHost and ViewHost, which exist to clean up state between injection phases. The integration point is deliberately not the class you are iterating on. You wrap the callsite in the parent, so if a SplitViewController creates PaneA and PaneB and you want to iterate on PaneA, you change the line in SplitViewController that constructs it. The README states that after this change you can edit anything in PaneAView except its initialiser API. The reason for wrapping at the parent is stated plainly: Inject relies on @autoclosure to reload views when hot reload happens, so the initialiser must sit inside Inject.ViewControllerHost(...) or Inject.ViewHost(...). The README shows the failure mode directly. Creating the view controller first and then wrapping the variable is marked WRONG, because the autoclosure never gets the chance to re-run the initialiser. There is also an onInjectionHook on the hosted view, which the README describes as running each time a controller is reloaded, with re-binding a presenter to the new controller as the example.
Installing Inject and enabling one SwiftUI view
Inject ships as a Swift package. In Xcode the README says to use File, then Swift Packages, then Add Package Dependency, and enter the repository URL. In a Package.swift manifest the dependency is declared with a version range, and the README example starts from 1.2.4.
dependencies: [
.package(
url: "https://github.com/krzysztofzablocki/Inject.git",
from: "1.2.4"
)
]CocoaPods users get a different product name, which is easy to get wrong when copying from the SPM example.
pod 'InjectHotReload'The per-machine setup is the part that decides whether any of this works. The README lists it as: add -Xlinker -interposable to Other Linker Flags of all targets for the Debug configuration, add a user-defined EMIT_FRONTEND_COMMAND_LINES setting with value YES in Debug, download the Xcode Injection app from its GitHub releases, unpack it under /Applications, keep Xcode itself at /Applications/Xcode.app, run the injection application, and pick your workspace from its open project menu. Then launch the app. A correct setup prints two lines to the console, one confirming the connection and one confirming the watched directory.
đź’‰ InjectionIII connected /Users/merowing/work/SourceryPro/App.xcworkspace
đź’‰ Watching files under /Users/merowing/work/SourceryProFor SwiftUI, the two code changes are the whole integration. The README notes you do not need to remove them later, because they are no-op inlined code stripped by LLVM in non-debug builds. An optional animation can be attached to injected source.
InjectConfiguration.animation = .interactiveSpring()Where the setup breaks and when Inject is the wrong tool
The per-machine prerequisites are the real cost, and they are not per-developer in a soft sense: the README states that the linker flag must be added to all targets in the project for the Debug configuration, qualified by the simulator SDK to avoid complications with bitcode. A team that cannot change every target's build settings cannot use Inject as documented. The hard dependency on an external macOS application is the second constraint. InjectionIII must be downloaded, placed under /Applications, and kept running with the correct workspace selected. That is a desktop tool, so this workflow does not carry over to a Linux or cloud build environment, and the README does not describe any CI usage. There is also a correctness trap in the UIKit wrapper: the autoclosure requirement means the order of construction matters, and the README's own WRONG example is a plausible thing to write. If a view is created before it is wrapped, reloads silently will not pick up the new initialiser. Finally, the README is explicit that only the initialiser API of the wrapped view is off limits; changes to it will not be reflected. Treat that as the boundary of the technique rather than a bug. One more caveat from the README: the optional automatic injection script modifies your Swift source code, and the README warns to review the changes carefully and says it might not suit all projects or coding styles.
Inject versus driving InjectionIII directly
The honest alternative is InjectionIII on its own. It is the same underlying engine, and the README says so. The difference is in what you write in your app. With InjectionIII alone, a UIKit codebase typically needs its own notification handling or reload plumbing to tear down and rebuild a view when a file changes, and that code has to be written per screen or per pattern. Inject replaces that with ViewHost and ViewControllerHost wrappers at the callsite, plus onInjectionHook for re-binding a presenter. For SwiftUI the comparison is starker: Inject reduces the work to .enableInjection() on the body and @ObserveInjection on the struct, where a hand-rolled approach means writing your own observation and invalidation. The trade is control. If your project already has a reloading mechanism tuned to its architecture, adding a second one at the callsite may duplicate work. If you have no mechanism and want one that the README describes as no-op in production builds, Inject is the shorter path. It is not a replacement for InjectionIII and cannot be used without it.
Maintenance, licence and what an upgrade costs
Inject is MIT licensed, which permits commercial and closed-source use; the LICENSE file sits at the repository root alongside Package.swift, the podspec and Sources. That is the whole licence implication here, and it is not legal advice. On maintenance, the last push to the default branch was on 2026-04-29, and the most recent release is 1.6.0 from 2026-04-14. The release history is not uniform: 1.5.2 is dated 2024-07-04 and 1.5.1 two days earlier, so there was a long gap before the 1.6.0 line. The repository is not archived. The upgrade cost is mostly external. Inject's own surface is small, a package dependency plus per-view annotations, so moving between versions is unlikely to be the hard part. The harder part is InjectionIII, which is a separate project with its own releases and its own compatibility with the Xcode version you have installed. The README points readers to the InjectionIII documentation for linker flag issues and notes that the Xcode version used to compile must be under the default /Applications/Xcode.app location. If your toolchain moves, that is where the breakage will appear first, not in Inject's API.
Editorial conclusion
Adopt Inject if your team already runs InjectionIII and wants to stop writing reload plumbing by hand: the SwiftUI path is two lines per view, and the UIKit path is a wrapper at the parent callsite. Do not adopt it if you cannot add -Xlinker -interposable and EMIT_FRONTEND_COMMAND_LINES to every Debug target, or if your team will not run a separate macOS injection app. Verify first that your Xcode sits at /Applications/Xcode.app, because the README states InjectionIII expects that path, and check the -Xlinker -interposable flag on each target rather than only the app target.
Frequently asked questions
Does Inject add manual overhead to my workflow?
The README states that once the project is configured initially it is practically free, and that you do not need conditional compilation or to remove Inject code for production because it is designed as no-op inlined code stripped by LLVM in non-debug builds.
Do I have to remove Inject code before shipping?
No. The README repeats for both SwiftUI and UIKit that you do not need to remove the code when you are done, because it is a no-op in production builds. The only setup items are the Debug configuration changes.
Which UI frameworks does Inject support?
The README names UIKit, AppKit and SwiftUI. SwiftUI uses .enableInjection() and @ObserveInjection, while UIKit and AppKit use the ViewControllerHost and ViewHost wrappers at the parent callsite.
What does Inject depend on to actually reload code?
The README says the heavy lifting is done by InjectionIII and describes Inject as a thin wrapper. InjectionIII must be downloaded, placed under /Applications and run with your workspace selected.
Can I change a view's initialiser while hot reloading?
The README states that after wrapping PaneAView you can change anything in it except its initialiser API, and those changes will be reflected almost immediately. The initialiser must also be called inside the host wrapper, because Inject relies on @autoclosure.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/krzysztofzablocki-inject)