1

We are extending our Xamarin.iOS app (by Xamarin.Forms) for CarPlay. When open the CarPlay simulator, the app is showing on the screen of CarPlay, but crashed when launching from CarPlay simulator.

Below is the Info.plist Scene configuration:

    <key>UIApplicationSceneManifest</key>
    <dict>
        <key>UISceneConfigurations</key>
        <dict>
            <key>CPTemplateApplicationSceneSessionRoleApplication</key>
            <array>
                <dict>
                    <key>UISceneClassName</key>
                    <string>CPTemplateApplicationScene</string>
                    <key>UISceneConfigurationName</key>
                    <string>ParkingPlus-Car</string>
                    <key>UISceneDelegateClassName</key>
                    <string>ParkingPlus.AppSceneDelegateImp</string>
                </dict>
            </array>
        </dict>
    </dict>

"ParkingPlus" is the app bundle name!

The class AppSceneDelegateImp (under the root folder of the iOS project

    public class AppSceneDelegateImp : UIResponder, ICPTemplateApplicationSceneDelegate
    {
        private CPInterfaceController _interfaceController;

        public async void DidConnect(CPTemplateApplicationScene templateApplicationScene, CPInterfaceController interfaceController)
        {
            _interfaceController = interfaceController;
           .....
        }

        public void DidDisconnect(CPTemplateApplicationScene templateApplicationScene, CPInterfaceController interfaceController)
        {
            _interfaceController.Dispose();
            _interfaceController = null;
        }
   }

When I override the AppDelegate.GetConfiguration as below

public override UISceneConfiguration GetConfiguration(UIApplication application, UISceneSession connectingSceneSession, UISceneConnectionOptions options)
{
...
}

When tapping the app icon on CarPlay, the method will be called. But when I inspected the connectingSceneSession, I found there are some exceptions inside the variable members. "CPTemplateApplicationSceneSessionRoleApplication has no associated enum value on this platform".

Screenshot for connectingSceneSession inspection

If continue, then the app will throw an exception which seems advise that the SceneDelegate is not being loaded properly: Exception

My envorinment: Visual studio for mac Version 8.7.8 Xamarin.iOS 14.0.0 Xcode 12.0

It seems like Xamarin.ios 14 missing something when binding the iOS library. Anyone has the similar issue. Did I do anything wrong or Is there any other way I can implement the CarPlay part feature by Xcode/swift while keeping the mobile app on Xamarin.Forms/Xamarin.iOS?

Appreciate any comments or help.

Kevin Hu
  • 80
  • 1
  • 11
  • Is that crash only happen in iOS 14? `It seems like Xamarin.ios 14 missing something when binding the iOS library.`, You can open an issue in Github with those information if you think it is caused by binding iOS library. – nevermore Oct 07 '20 at 08:08
  • @JackHua-MSFT, Thanks. I did raise a bug one week before I asked the question here. The team will fix the bug at Xamarin.iOS. And I was advised an approach. When I get it working fine, I will answer it at here. – Kevin Hu Oct 08 '20 at 02:43

1 Answers1

1

With the help from Xamarin.ios team, below is the full solution to this issue: https://github.com/xamarin/xamarin-macios/issues/9749

  1. The AppDelegate to create the scene configuration for two cases (CarPlay and phone)

        [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
        public extern static IntPtr IntPtr_objc_msgSend_IntPtr_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);

        public static UISceneConfiguration Create(string? name, NSString sessionRole)
        {
            global::UIKit.UIApplication.EnsureUIThread();
            var nsname = NSString.CreateNative(name);

            UISceneConfiguration sceneConfig;
            sceneConfig = Runtime.GetNSObject<UISceneConfiguration>(IntPtr_objc_msgSend_IntPtr_IntPtr(Class.GetHandle("UISceneConfiguration"), Selector.GetHandle("configurationWithName:sessionRole:"), nsname, sessionRole.Handle));
            NSString.ReleaseNative(nsname);
            //Because only the CarPlay scene will be here to create a scene configuration
            //We need manually assign the CarPlay scene delegate here!
            sceneConfig.DelegateType = typeof(AppCarSceneDelegateImp);
            return sceneConfig!;
        }

        [Export("application:configurationForConnectingSceneSession:options:")]
        public UISceneConfiguration GetConfiguration(UIApplication application, UISceneSession connectingSceneSession, UISceneConnectionOptions options)
        {
            UIWindowSceneSessionRole sessionRole;
            bool isCarPlaySceneSession = false;
            try
            {
                //When the connecting scene is a CarPlay scene, an expected exception will be thrown
                //Under this moment from Xamarin.iOS.
                sessionRole = connectingSceneSession.Role;
            }
            catch (NotSupportedException ex)
            {
                if (!string.IsNullOrEmpty(ex.Message) &&
                    ex.Message.Contains("CPTemplateApplicationSceneSessionRoleApplication"))
                {
                    isCarPlaySceneSession = true;
                }
            }

            if (isCarPlaySceneSession && UIDevice.CurrentDevice.CheckSystemVersion(14,0))
            {
                return Create("Car", CarPlay.CPTemplateApplicationScene.SessionRoleApplication);
            }
            else
            {
                //If it is phone scene, we need the regular UIWindow scene
                UISceneConfiguration phoneScene = new UISceneConfiguration("Phone", UIWindowSceneSessionRole.Application);
                //And assign the scene delegate here.
                phoneScene.DelegateType = typeof(AppWindowSceneDelegateImp);
                return phoneScene;
            }
        }
  1. Create a UIWindowScene delegate to handle the regular mobile scene window
    public class AppWindowSceneDelegateImp : UISceneDelegate
    {
        public override void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
        {
            var windowScene = scene as UIWindowScene;
            if (windowScene != null)
            {
                //Assign the Xamarin.iOS app window to this scene 
                UIApplication.SharedApplication.KeyWindow.WindowScene = windowScene;
                UIApplication.SharedApplication.KeyWindow.MakeKeyAndVisible();
            }
        }
    }
Kevin Hu
  • 80
  • 1
  • 11
  • Great +1, can you show the two AppDelegates (phone/car) and how Xamarin is loading the "main" AppDelegate which decide which one would be loaded? – Suplanus Nov 25 '20 at 10:31
  • @Suplanus, The AppDelegate.GetConfiguration() will be called and determine the corresponding UIScene (CarPlay or Phone) will be loaded and UISceneConfiguration will be returned. In that method before you return the UISceneConfiguration, you have to assign the Scene delegate implementation. And that is all you need to do. The above code snippet contains all the essential code. – Kevin Hu Dec 22 '20 at 23:57
  • Thanks. I made some tests in this repo: https://github.com/Suplanus/Xamarin.Demo.Carplay/tree/dev But its not working as expected. – Suplanus Dec 23 '20 at 06:28
  • @Suplanus, did you have the correct CarPlay entitlement and setup in the info.plist file? – Kevin Hu Dec 24 '20 at 07:43
  • I think so, you can see it in the repo. I am also think that the simulator don't need the entitlement for the app (only devices need this). – Suplanus Jan 03 '21 at 10:48