Added IPlatformSingleViewApplicationLifetime
In our iOS app the user can navigate between multiple views, one of which is a native component overlaid by a second, partially transparent Avalonia view. We found that Avalonia doesn't support this arrangement, and that we had to hack it in by copying and altering AvaloniaAppDelegate so that we can create our own UINavigationController as the root view. While this works fine, it's not very maintainable and we are also unable to generate a functional application lifetime.
So this PR adds a new application lifetime where a platform-specific root view is controlled by the application. This allows Avalonia to be used alongside other native views created by external components in whatever way the application sees fit.
ControlCatalog.iOS can be started with the new lifetime by launching it with a "-PlatformView" argument...in principle. I've not actually worked out how to pass launch arguments to an iOS app, but altering the source code allows easy testing of the new behaviour even without this.
What is the current behavior?
Avalonia views can only coexist with native views by using NativeControlHost. This is always drawn over Avalonia's output, which doesn't meet our requirements. It's also not possible to use native view transition animations, because there is only ever one native view.
What is the updated/expected behavior with this PR?
By passing AppViewControllerKind.Platform as a constructor parameter to Avalonia.iOS.AvaloniaAppDelegate, a new lifetime type implementing the interface IPlatformSingleViewApplicationLifetime will be created.
The application is responsible for providing a platform-specific UI component to this lifetime. This could be an instance of the existing DefaultAvaloniaViewController type, or something entirely unrelated to Avalonia.
How was the solution implemented (if it's not obvious)?
This PR implements the new lifetime only for iOS. It will need to be extended to other relevant platforms (certainly Android, possibly browser too?) by other people more familiar with them. It isn't relevant for desktop platforms.
There are three new public interfaces:
IPlatformSingleViewApplicationLifetimeinAvalonia.Controls. This allows platform-agnostic code to identify that a platform lifetime is in use, but is otherwise entirely opaque.IPlatformSingleViewApplicationLifetime<T>inAvalonia.Controls. This defines thePlatformViewproperty and technically allows you to access it if you can provide the correct type forT.IUIViewControllerApplicationLifetimeinAvalonia.iOS. This definesTand is the interface which will probably see the most use. Each platform supporting this feature should have its own equivalent.
There is also a new enum value on iOS which can be passed to a new AvaloniaAppDelegate constructor to request a platform lifetime. The lifetime itself does nothing except forward the native window's RootViewController property to its own PlatformView property. It's up to the application to provide a value.
Breaking changes
None. This is an entirely additive change.
Obsoletions / Deprecations
None.
Avalonia views can only coexist with native views by using NativeControlHost.
Our iOS backend exposes AvaloniaView class which directly inherits from UIView. Given that your setup is fairly customized, it's probably better to just use that component directly instead of relying on introducing extra lifetime types.
This would be nice, but doesn't meet our requirements either. To my understanding, a view controller cannot be a child of a view (such as AvaloniaView) and:
- Navigation to/from our native view must be animated in the iOS style, which as far as I'm aware can only be achieved with a
UINavigationController. - I just looked more closely at our native "view" and I found that it's actually a view controller itself. Perhaps that can be refactored, but I'm no iOS expert and there may be a good reason for that choice.
@TomEdwardsEnscape Would it be possible to create a small example project showing what you're doing right now to achieve what you're doing? I'm trying to understand what your original issue is.
If you have a UIViewController you should be able to add AvaloniaView and as child to it and push it to the top of the stack. I wrote an example for demo it.
That adds the AvaloniaView as a child to the UINavigationController. The underling UIViewControllers are under it and operate independently.
I'm doing something very similar to you, but with more code copied from Avalonia's internals. Like I said this works, but is fragile because Avalonia doesn't expect a custom AppDelegate to be used. It's built on the assumption that AvaloniaAppDelegate is in control.
I apologize for the late response. We had an API Review last month, and we felt that, in the end, this API would be too specific to iOS and this case, which you already have ways to tackle without requiring this API to be implemented. We would also need to implement the other applicable platforms for this interface, but we're unsure if the work and cost are justified.
So ultimately, we're going to reject this change.