-
-
Save timothycosta/a43dfe25f1d8a37c71341a1ebaf82213 to your computer and use it in GitHub Desktop.
struct ViewControllerHolder { | |
weak var value: UIViewController? | |
init(_ value: UIViewController?) { | |
self.value = value | |
} | |
} | |
struct ViewControllerKey: EnvironmentKey { | |
static var defaultValue: ViewControllerHolder { return ViewControllerHolder(UIApplication.shared.windows.first?.rootViewController ) } | |
} | |
extension EnvironmentValues { | |
var viewController: ViewControllerHolder { | |
get { return self[ViewControllerKey.self] } | |
set { self[ViewControllerKey.self] = newValue } | |
} | |
} | |
extension UIViewController { | |
func present<Content: View>(presentationStyle: UIModalPresentationStyle = .automatic, transitionStyle: UIModalTransitionStyle = .coverVertical, animated: Bool = true, completion: @escaping () -> Void = {}, @ViewBuilder builder: () -> Content) { | |
let toPresent = UIHostingController(rootView: AnyView(EmptyView())) | |
toPresent.modalPresentationStyle = presentationStyle | |
toPresent.rootView = AnyView( | |
builder() | |
.environment(\.viewController, ViewControllerHolder(toPresent)) | |
) | |
if presentationStyle == .overCurrentContext { | |
toPresent.view.backgroundColor = .clear | |
} | |
self.present(toPresent, animated: animated, completion: completion) | |
} | |
} |
Sure. This solution still seems to leak memory though and I haven’t been able to figure out why or where/how to fix it yet, regrettably. It’s on my mental list to try again at some point but maybe you understand this enough better than I do to see and fix it?
@haikusw Thanks again for the reminder! It leaks because EnvironmentValues
is only used once when you actually create the view. After that the view maintains a strong reference to the view controller itself.
Updated the gist with my current code.
Thanks. Are you using Xcode 11.3 beta perhaps?
The new code isn't building for me in the latest release Xcode (11.2.1) with newest possible target of iOS 13.2. doesn't know .anyView
on line 26.
hi, we have tried to use this approach to present iPad full screen modal views but we are getting memory leaks too :/ did you find any solution or alternative?
I did not look into it further as I couldn't get Timothy's updated code to build and our design changed enough that we didn't need it. Sorry. Please post here if you do find a solution because I'm sure this will come up again (!).
I put this code into a simple project, presented a UIHostingController
from window.rootViewController
and then did self.viewControllerHolder.value?.dismiss(...)
. I don't see any leaks in the memory graph after dismissal.
If you didn't directly copy and paste this, ensure that you're declaring extension EnvironmentValues { var viewController: ViewControllerHolder { ... } }
and not var viewController: UIViewController? { ... }
as the latter definitely will leak.
Hi! Thanks so much for the solution, I'm fairly new to swift and I was also running into the memory leak problem but once I had updated the code it seems like there is a problem while building. I ran into a key path value type mismatch error on the @Environment(.viewController) private var viewControllerHolder: UIViewController? line
do you have any idea wha could be the problem? Thanks!
@bobshoemaker wrote:
Hi! Thanks so much for the solution, I'm fairly new to swift and I was also running into the memory leak problem but once I had updated the code it seems like there is a problem while building. I ran into a key path value type mismatch error on the @Environment(.viewController) private var viewControllerHolder: UIViewController? line
do you have any idea wha could be the problem? Thanks!
The .viewController
in @Environment(\.viewController)
refers to the EnvironmentValues
extension's var definition on line 14 of the gist. If you refer to that you will see that it is defined as a ViewControllerHolder
, not a UIViewController
.
(Also you are missing the keypath starting "" in your comment above, but I assume that is just local to this comment and not your code).
So set the type of the var to ViewControllerHolder
and that may make the compiler happy (it's a compiler, so who knows? ;)).
Something like:
@Environment(\.viewController) private var viewControllerHolder: ViewControllerHolder?
Thanks! As you said it's just a compiler issue! I've just changed it to:
@Environment(\.viewController) private var viewControllerHolder: ViewControllerHolder?
private var viewController: UIViewController? {
self.viewControllerHolder?.value
}
The memory leak is gone as well! Thanks again!
Thanks for this! I have a couple of questions
Is there a way to make the modal only as big as the content you put in it? I don’t want the white box that appears when using .formSheet/pageSheet. (I know I can just set toPresent.view.backgroundColor = .clear
) preferredContentSize
seems like an option but haven’t had a chance to test
Also when I tried to add a button to the body of ModalContentView to dismiss (experimenting with full screen), the button appeared behind the content, not above it like I expected given its a VStack. I can’t therefore see how to dismiss full screen
@Environment(\.viewController) private var viewControllerHolder: ViewControllerHolder?
private var viewController: UIViewController? {
self.viewControllerHolder?.value
}
with this code I'm getting error like
Key path value type 'ViewControllerHolder' cannot be converted to contextual type 'ViewControllerHolder?
@Environment(\.viewController) private var viewControllerHolder: ViewControllerHolder? private var viewController: UIViewController? { self.viewControllerHolder?.value }
with this code I'm getting error like
Key path value type 'ViewControllerHolder' cannot be converted to contextual type 'ViewControllerHolder?
Don't have time to test this but maybe try:
@Environment(\.viewController) private var viewControllerHolder: ViewControllerHolder
private var viewController: UIViewController? {
self.viewControllerHolder.value
}
Reading the gist, Line 14 defines viewController
to return a non-optional ViewControllerHolder
so I'm guessing that's the issue you're running into.
and how to dismiss it
its support for RTL?
Using UIViewControllerRepresentable to manage the make/update lifetime will fix the leak.
@haikusw good catch. Thanks!