johnno1962 / InjectionIII

Re-write of Injection for Xcode in (mostly) Swift
MIT License
4.08k stars 319 forks source link

Does it work with WatchOS simulator? #519

Closed Maximization closed 1 month ago

Maximization commented 1 month ago

I've tried to make it work on WatchOS simulator without any luck. Can InjectionIII work with WatchOS simulator somehow?

johnno1962 commented 1 month ago

Hi, it should be possible but no-one has asked before now. I've updated the https://github.com/johnno1962/InjectionNext project (which is similar but a bit easier to work on) to support this if you want to try it out.

Maximization commented 1 month ago

@johnno1962 Thank you for the quick reply! And making those changes. It's a bit unclear to me how I should enable hot reloading with SwiftUI. What I've done:

  1. Installed InjectionNext
  2. Ran XCode from InjectionNext
  3. Added https://github.com/johnno1962/InjectionNext.git as a package dependency
  4. Added -Xlinker and -interposable to Other Linker Flags

I see a green injection icon in the menu bar but hot reloading seems to not be working. I think I'm missing .enableInjection() and @ObserveInjection var forceRedraw for each view. But when I add them I get compiler errors. Do I need to add HotSwiftUI as a package dependency too? Even though watchOS is not included as a platform platforms: [.macOS("10.15"), .iOS("13.0"), .tvOS("13.0")],

johnno1962 commented 1 month ago

You're nearly there. You need the @ObserveInjection to force redraws and the .enableInjection() to make them reliable. You add either HotSwiftUI or Inject which contain these functions as a package dependency and @_exported import them. I didn't have a problem using the missing platform.

Maximization commented 1 month ago

I tried using Inject without avail. I think Inject doesn't include the property wrapper and modifier for watchOS

Landmarks — LandmarkList swift - 2024-09-23 at 15 02 48

Those errors go away when using HotSwiftUI, but one still remains

Landmarks — Reloader swift - 2024-09-23 at 15 03 26

I'm using the Landmarks App from Apple's SwiftUI tutorial in case you want to test locally.

johnno1962 commented 1 month ago

Sorry, I should have said use the branch main for the InjectionNext package. I haven't tagged it yet. The other problems you're seeing will probably relate to Inject/HotSwiftUI only being added to one target.

Maximization commented 1 month ago

Ok so after ownloading the main branch zip of InjectionNext, building the app, and dragging to applications folder, I run the watchOS app and get the following error in the console:

"⚠️ Could not load injection bundle from /Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle. Have you downloaded the InjectionIII.app from either https://github.com/johnno1962/InjectionIII/releases or the Mac App Store? Build clean if you have been using the HotReloading Swift Package from github."

What am I doing wrong?

johnno1962 commented 1 month ago

You don't need to update the InjectionNext.app but switch the branch on the package dependency of your app to be main. But it sounds like you might have a different problem where The InjectionNext package is not linked with your main bundle or watch extension.

johnno1962 commented 1 month ago

I downloaded the project you mentioned and did the following: add https://github.com/johnno1962/InjectionNext, branch main as SPM dependency to target "WatchLandmarks Watch App", finally add -Xlinker -interopsable as "Other Linker flags" to that target. Run the app and see that it connects showing an orange state. View a source file in Xcode and use menu item "Prepare SwiftUI/Entire Project" in the InjectionNext.app on the menu bar to prepare the project and run it again. If you use the HotSwiftUI package make sure it is added to the same target as InjectionNext.

Maximization commented 1 month ago

I removed everything and followed your steps and it magically worked! Don't know what went wrong previously. Thank you very much for taking the time to help me out.

Do you think it's possible to make the same changes to InjectionIII as well, or is it a ton of extra work? I'd like to use Cursor editor (AI integration) with the simulator side-by-side. I tried running the app with XCode and changing a file in Cursor, but changes aren't picked up unfortunately. Also happy to open a PR if you could point me in the right direction.

johnno1962 commented 1 month ago

Yeah watchOS is a bit tricky as there are multiple targets. I've created a new release candidate of InjectionIII for wtachOS which should work if you also update HotSwiftUI to 1.1.11. It only works "standalone" where you don't run the app at all for various reasons. See how you get on.

Maximization commented 1 month ago

@johnno1962 Thanks for providing a release for InjectionIII so quickly! I've started with a clean setup, followed the steps as before, and this time using HotSwiftUI 1.1.11. I'm getting this error after a view change:

💉 InjectionIII connected /Users/maxim/Code/Landmarks/Landmarks.xcodeproj
💉 Watching files under the directory /Users/maxim/Code/Landmarks
💉 Compiling /Users/maxim/Code/Landmarks/Landmarks/Views/Landmarks/LandmarkList.swift
💉 Loading .dylib ...
💉 ⚠️ dlopen() error: dlopen(/Users/maxim/Library/Developer/CoreSimulator/Devices/8B2A5033-A94E-42E7-9811-F6F44FB290CA/data/Containers/Data/Application/F7841159-7329-4ABD-AFA3-391044DEFFD4/tmp/eval101.dylib, 0x0002): tried: '/Users/maxim/Library/Developer/Xcode/DerivedData/Landmarks-dvsetkchrlunrfhbmumbcwfoswdv/Build/Products/Debug-watchsimulator/eval101.dylib' (no such file), '/Users/maxim/Library/Developer/Xcode/DerivedData/Landmarks-dvsetkchrlunrfhbmumbcwfoswdv/Build/Products/Debug-iphonesimulator/eval101.dylib' (no such file), '/Library/Developer/CoreSimulator/Volumes/watchOS_21T575/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS 10.5.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/introspection/eval101.dylib' (no such file), '/Library/Developer/CoreSimulator/Volumes/watchOS_21T575/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS 10.5.simruntime/Contents/Resources/RuntimeRoot/Users/maxim/Library/Developer/CoreSimulator/Devices/8B2A5033-A94E-42E7-9811-F6F44FB290CA/data/Containers/Data/Application/F7841159-7329-4ABD-AFA3-391044DEFFD4/tmp/eval101.dylib' (no such file), '/Users/maxim/Library/Developer/CoreSimulator/Devices/8B2A5033-A94E-42E7-9811-F6F44FB290CA/data/Containers/Data/Application/F7841159-7329-4ABD-AFA3-391044DEFFD4/tmp/eval101.dylib' (mach-o file (/Users/maxim/Library/Developer/CoreSimulator/Devices/8B2A5033-A94E-42E7-9811-F6F44FB290CA/data/Containers/Data/Application/F7841159-7329-4ABD-AFA3-391044DEFFD4/tmp/eval101.dylib), but incompatible platform (have 'iOS-sim', need 'watchOS-sim'))
💉 ⚠️ Clean build folder when switching platform

Cleaning the build folder didn't fix the issue. Something I can do on my end perhaps? Maybe this offers a clue: but incompatible platform (have 'iOS-sim', need 'watchOS-sim')

johnno1962 commented 1 month ago

Hi, that isn't "standalone" injection if it is connecting. Quit the InjectionIII.app and let the bundle do all the work for you.

Maximization commented 1 month ago

Ah I was trying to make it work with XCode first before doing it standalone. Just tried standalone and got a similar error:

💉 Unable to connect to InjectionIII app, falling back to standalone HotReloading.
💉 Standalone InjectionIII available for sources under ["/Users/maxim"]
💉 Using logs: /Users/maxim/Library/Developer/Xcode/DerivedData/Landmarks-dvsetkchrlunrfhbmumbcwfoswdv/Logs/Build/2DD2BE0F-C37D-49E3-8A28-EA96BFC5E574.xcactivitylog.
💉 Compiling /Users/maxim/Code/Landmarks/Landmarks/Views/Landmarks/LandmarkList.swift
💉 Loading .dylib ...
💉 ⚠️ dlopen() error: dlopen(/Users/maxim/Library/Developer/CoreSimulator/Devices/8B2A5033-A94E-42E7-9811-F6F44FB290CA/data/Containers/Data/Application/33F6EAEB-D61C-4DAC-953C-9B3BD1E85420/tmp/eval101.dylib, 0x0002): tried: '/Library/Developer/CoreSimulator/Volumes/watchOS_21T575/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS 10.5.simruntime/Contents/Resources/RuntimeRoot/Users/maxim/Library/Developer/CoreSimulator/Devices/8B2A5033-A94E-42E7-9811-F6F44FB290CA/data/Containers/Data/Application/33F6EAEB-D61C-4DAC-953C-9B3BD1E85420/tmp/eval101.dylib' (no such file), '/Users/maxim/Library/Developer/CoreSimulator/Devices/8B2A5033-A94E-42E7-9811-F6F44FB290CA/data/Containers/Data/Application/33F6EAEB-D61C-4DAC-953C-9B3BD1E85420/tmp/eval101.dylib' (mach-o file (/Users/maxim/Library/Developer/CoreSimulator/Devices/8B2A5033-A94E-42E7-9811-F6F44FB290CA/data/Containers/Data/Application/33F6EAEB-D61C-4DAC-953C-9B3BD1E85420/tmp/eval101.dylib), but incompatible platform (have 'iOS-sim', need 'watchOS-sim'))
💉 ⚠️ Clean build folder when switching platform
johnno1962 commented 1 month ago

Standalone means separate from the app rather than Xcode, though it works without Xcode as well once you've used Xcode for the initial build. It's probably cached the wrong compile command now. Can you introduce a syntax error into the file you're injecting and inject it again. This will fail and should flush the cache, then remove the syntax error and try again.

Maximization commented 1 month ago

It works! Amazing. Thank you so much 🙏

For anyone reading this in the future, here's how to make hot reload work with WatchOS in standalone mode (without Injection app):

  1. Download InjectionIII.app.zip from Injection Github Releases (>= 5.0.3RC1)
  2. Unzip and drag to Applications folder (no need to open the app!)
  3. Open your project with XCode and add HotSwiftUI (>= 1.1.11) as a package dependency (File > Add Package Dependencies...). Choose your watch app as the target. You may quit XCode now
  4. In your main app file import HotSwiftUI for the entire project:
    @_exported import HotSwiftUI
  5. For each view you want to apply hot reloading to, add .enableInjection() modifier to the body and @ObserveInjection property wrapper:
    
    import SwiftUI

struct ContentView: View { var body: some View { // Some view { // ... // } .enableInjection() } }

if DEBUG

@ObserveInjection var inject

endif

}


6. Build & Run your project and you should see hot reloading work after you make a change in a view that has enabled injection

My setup is using [Cursor](https://www.cursor.com/) with [Swift lang](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang) and [SweetPad](https://marketplace.visualstudio.com/items?itemName=sweetpad.sweetpad) extensions working on a WatchOS companion app for an iOS app built with React Native.

A huge thanks to @johnno1962 for his incredible work in making hot reloading in Swift possible, and for his quick response and support in getting it to work for watchOS! 🙌