krzysztofzablocki / Inject

Hot Reloading for Swift applications!
MIT License
2.1k stars 114 forks source link

SwiftUI + UIKit project causes crashes #70

Closed amsam0 closed 1 year ago

amsam0 commented 1 year ago

Hi, we have a UIKit project that we are currently working on migrating to SwiftUI. The default view is SwiftUI, and we use a UIHostingController to display it. I’ve added inject into the SwiftUI view, and editing already added elements works fine (like changing text). However, if I add a new element, it crashes the app with Thread 1: EXC_BAD_ACCESS (code=1, address=…). Is there any way I can fix this? I’ve tried using Inject.ViewControllerHost on the UIHostingController, and also using Inject.ViewControllerHost without having inject in the SwiftUI part, but neither helped.

Our project is open source, and I can give you a link to the branch with the SwiftUI stuff if needed. (I haven’t pushed adding Inject yet.) It’s a pretty complex project, and uses libraries written in Objective-C and Rust, so it might take a little while to setup.

johnno1962 commented 1 year ago

Hi, Can you post an image of the stack trace or the link to the branch please?

amsam0 commented 1 year ago

Here’s the branch: https://github.com/SideStore/SideStore/tree/naturecodevoid/swiftui-improvements As I said, it is a decently complex project and setup might take a while (you will need Rust installed, and it will take 5-10 minutes to compile the rust dependencies). There are some compilation instructions: https://github.com/SideStore/SideStore/tree/naturecodevoid/swiftui-improvements#compilation-instructions (they are slightly outdated, you should be able to skip some of the steps to run on simulator, and I’m not sure if you need to manually build the rust dependencies with cargo build) I will push adding inject now.

You should be able to trigger a crash by adding Text("test") around line 192 in AltStore/Views/Settings/SettingsView.swift and saving.

Here's the stack trace (not sure if this is what you wanted):

image
johnno1962 commented 1 year ago

Your problem is probably because you are not calling .eraseToAnyView() at the end of your SwiftUI property method. You need to do this if you are going to add or remove elements to the View over an injection as it changes the concrete type of the some View which the containing view won't be expecting and will crash. You need to do this ahead of time for any view you want to be able to inject. There is more information about this in the https://github.com/johnno1962/HotSwiftUI Swift Package or in the README https://github.com/johnno1962/InjectionIII#swiftui-injection

amsam0 commented 1 year ago

Doesn't Inject already do this? https://github.com/krzysztofzablocki/Inject/blob/main/Sources/Inject/Integrations/SwiftUI.swift#L12

johnno1962 commented 1 year ago

Inject contains an implementation of .eraseToAnyView() (where it is called enableinjection()) but it needs to be used as part of your SwiftUI view implementation. Have a look at https://github.com/johnno1962/SwiftUI-Kit/blob/master/Shared/ContentView.swift for example.

amsam0 commented 1 year ago

https://github.com/SideStore/SideStore/blob/naturecodevoid/swiftui-improvements/AltStore/Views/RootView.swift#L32

I'm calling enableInjection, do I need to be doing something else?

johnno1962 commented 1 year ago

Unfortunately you can't just do it at the top level. You need to add it to each component you want to be injectable to erase the type and isolate it from it's parent view.

amsam0 commented 1 year ago

I see. If I want it on one view, do I need to add it to all child view/components?

johnno1962 commented 1 year ago

Just the view(s) you want to iterate over.

amsam0 commented 1 year ago

Ok, thanks! Do I also need to add the observer to every view, or just the root?

amsam0 commented 1 year ago

I'll close this now, tysm for the help!

johnno1962 commented 1 year ago

NP. You need and injectionObserver per view you want to auto-redraw.