As the title states, I am trying to build an app for my car. I only see a brief mention of CarPlay in Xamarin docs.
So is it possible to develop a CarPlay app with Xamarin and Visual Studio tools?
P.S. I did some more research and while you could develop apps for CarPlay, Apple only allows navigation and streaming apps as time of writing. So it's a complete non-starter for what I wanted to do.
Yes its possible.
I made a blog post and an example on GitHub.
Here the answer in short:
In our iOS project we init the delegates in the AppDelegate.cs with CarIntegrationBridge.Init();.
The delegates are registered as follow:
public class CarIntegrationBridge : ICarIntegrationBridge
{
public static void Init()
{
PlayableContentDelegate playableContentDelegate = new PlayableContentDelegate();
MPPlayableContentManager.Shared.Delegate = playableContentDelegate;
PlayableContentDataSource playableContentDataSource = new PlayableContentDataSource();
MPPlayableContentManager.Shared.DataSource = playableContentDataSource;
}
}
Now we define our datasource. In my example I added radio stations with name and url. We have to define the menu items count and how a menu item is displayed (name, icon, …):
internal class PlayableContentDataSource : MPPlayableContentDataSource
{
public static List<Station> Stations = new List<Station>
{
new Station{Name = "Rainbow radio", Url = "https://stream.rockantenne.de/rockantenne/stream/mp3"},
new Station{Name = "Unicorn radio", Url = "http://play.rockantenne.de/heavy-metal.m3u"}
};
public override MPContentItem ContentItem(NSIndexPath indexPath)
{
var station = Stations[indexPath.Section];
var item = new MPContentItem(station.Url);
item.Title = station.Name;
item.Playable = true;
item.StreamingContent = true;
var artWork = GetImageFromUrl("station.png");
if (artWork != null)
{
item.Artwork = artWork;
}
return item;
}
public override nint NumberOfChildItems(NSIndexPath indexPath)
{
if (indexPath.GetIndexes().Length == 0)
{
return Stations.Count;
}
throw new NotImplementedException();
}
private MPMediaItemArtwork GetImageFromUrl(string imagePath)
{
MPMediaItemArtwork result = null;
try
{
using (var nsUrl = new NSUrl(imagePath))
{
using (var data = NSData.FromUrl(nsUrl))
{
var image = UIImage.LoadFromData(data);
result = new MPMediaItemArtwork(image);
}
}
}
catch
{
UIImage image = UIImage.FromBundle(imagePath);
if (image != null)
{
result = new MPMediaItemArtwork(image);
}
}
return result;
}
}
Now we have to decide what is todo, if an item is taped.
The simulator have an other behavior than a real device. So I hacked a solution for calling the NowPlayingScene.
internal class PlayableContentDelegate : MPPlayableContentDelegate
{
public override void InitiatePlaybackOfContentItem(
MPPlayableContentManager contentManager, NSIndexPath indexPath, Action<NSError> completionHandler)
{
Execute(contentManager, indexPath);
completionHandler?.Invoke(null);
}
private void Execute(MPPlayableContentManager contentManager, NSIndexPath indexPath)
{
DispatchQueue.MainQueue.DispatchAsync(async () => await ItemSelectedAsync(contentManager, indexPath));
}
private async Task ItemSelectedAsync(MPPlayableContentManager contentManager, NSIndexPath indexPath)
{
// Play
var station = PlayableContentDataSource.Stations[indexPath.Section];
await CrossMediaManager.Current.Play(station.Url);
// Set playing identifier
MPContentItem item = contentManager.DataSource.ContentItem(indexPath);
contentManager.NowPlayingIdentifiers = new[] { item.Identifier };
// Update on simulator
if (DeviceInfo.DeviceType == DeviceType.Virtual)
{
InvokeOnMainThread(() =>
{
UIApplication.SharedApplication.EndReceivingRemoteControlEvents();
UIApplication.SharedApplication.BeginReceivingRemoteControlEvents();
});
}
}
}
To reload the data (e.g. if you change the stations), you have to call this:
public void ReloadStations()
{
MPPlayableContentManager.Shared?.ReloadData();
}
I managed to build an Apple CarPlay app with Xamarin, although I used quite a different approach to what Suplanus did because I didn't create a music app, my approach followed the Apple docs approach more closely.
First make sure your provisioning profile has the correct CarPlay entitlement.
Add the entitlement to Entitlements.plist file.
In your info.plist like suggested in Apple's docs:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UISceneConfigurations</key>
<dict>
<!-- Device Scene -->
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>DeviceSceneDelegate</string>
</dict>
</array>
<!-- Carplay Scene -->
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>CPTemplateApplicationScene</string>
<key>UISceneConfigurationName</key>
<string>YourAppName-Car</string>
<key>UISceneDelegateClassName</key>
<string>YourAppName.CarPlaySceneDelegate</string>
</dict>
</array>
</dict>
</dict>
I then used the solution suggested in this thread to send CarPlay to the correct delegate, with the following changes:
On my CarPlaySceneDelegate class inherit directly from CPTemplateApplicationSceneDelegate.
Implement override methods for DidConnect and DidDisconnect instead.
UIWindowSceneSessionRole.CarTemplateApplication has now been added to the enum, so use that instead of checking in the catch if it’s a CarPlay app in AppDelegate class's GetConfiguration override.
Return default configuration in GetConfiguration override if it's not a CarPlay app.
You'll notice all the classes mentioned in Apple's developer docs are available in the CarPlay library that lives in Xamarin.iOS, so it's not too difficult to translate the swift code to C# to create and display the required templates.
Finally, use this solution to launch your Xamarin.Forms app from the device scene delegate instead of seeing a blank screen on your phone when you open the app.
Hope this helps a bit that you don't have to struggle as much as I did to get it working!
Maybe this https://forums.xamarin.com/discussion/144790/android-auto-ios-car-play answers the question ?
Car Play seems to be a private framework on Apple's side. And there is no relative documentation for XCode.
For Android Auto, go to github, search project Xamarin_Android_Auto_Test
Related
I have followed all steps mentioned here for Siri:
https://learn.microsoft.com/en-us/xamarin/ios/platform/sirikit/implementing-sirikit?tabs=macos
Also followed: https://developer.apple.com/documentation/sirikit/media/managing_audio_with_sirikit
IntentHandler.cs:
[Register("IntentHandler")]
public class IntentHandler : INExtension, IINPlayMediaIntentHandling, IINAddMediaIntentHandling
{
protected IntentHandler(IntPtr handle) : base(handle)
{
}
public override NSObject GetHandler(INIntent intent)
{
return this;
}
//INPlayMediaIntent
public void resolveMediaItems(INPlayMediaIntent intent, Action<INPlayMediaMediaItemResolutionResult> completion)
{
var mediaItemData = new INMediaItem(identifier: SessionService.LastPlayedEpisode.PodcastId, title: SessionService.LastPlayedEpisode.Title, type: INMediaItemType.PodcastEpisode, artwork: null);
MessagingCenter.Send<object, string>(this, "PlayRecentEpisodeSiriEvent", "ResolveMediaItems");
completion(INPlayMediaMediaItemResolutionResult.GetSuccess(mediaItemData));
}
public void HandlePlayMedia(INPlayMediaIntent intent, Action<INPlayMediaIntentResponse> completion)
{
var userActivity = new NSUserActivity("INPlayMediaIntent");
var response = new INPlayMediaIntentResponse(INPlayMediaIntentResponseCode.HandleInApp, userActivity);
completion(response);
}
public void HandleAddMedia(INAddMediaIntent intent, Action<INAddMediaIntentResponse> completion)
{
var userActivity = new NSUserActivity("INAddMediaIntent");
var response = new INAddMediaIntentResponse(INAddMediaIntentResponseCode.Success, userActivity);
completion(response);
}
}
Now, when I give command to Siri to like: "Play The Daily in myappname" it show this:
So, I am not getting actually how siri will handle Media content to show my app with command. Anyone know:
How to handle Siri Media Intent in Xamarin.iOS ?
How to add Siri Support with Xamarin.iOS?
UPDATE: 29 Oct, 2020 - How to play particular episode of Podcast from
Playlist is siri?
I am trying to play particular episode of Podcast playlist but it's not working. I have given command to siri like: Play the Overlap by audiochuck from CounterClock podcast in siridemo
here siridemo = my app's name, Overlap = episode name, counterclock = Podcast name.
I am getting only CounterClock in mediasearch.medianame object when control comes to HandleIntent method in appdelegate. So it is possible to achieve this with Siri?
How to fix app hasn't added support for that with siri error?
I have got the solution and it's here.
1. Intent Handler in your IntentExtension project:
First mistake I have done was I have used INPlayMediaIntentResponseCode.Success instead of HandleInApp, which I have corrected here. Why HandleInApp? Hold on :)
[Register("IntentHandler")]
public class IntentHandler : INExtension, IINPlayMediaIntentHandling
{
protected IntentHandler(IntPtr handle) : base(handle) { }
public override NSObject GetHandler(INIntent intent)
{
return this;
}
[Export("handlePlayMedia:completion:")]
public void HandlePlayMedia(INPlayMediaIntent intent, Action<INPlayMediaIntentResponse> completion)
{
var response = new INPlayMediaIntentResponse(INPlayMediaIntentResponseCode.HandleInApp, null);
completion(response);
}
}
2. Now let's Handle it in AppDelegate:
As we have passed HandleInApp as a response in Intent handler's HandlePlayMedia() it will call HandleIntent() method in AppDelegate of your main iOS project. Where the magic will happen.
public async override void HandleIntent(UIApplication application, INIntent intent, Action<INIntentResponse> completionHandler)
{
var playMediaIntent = intent as INPlayMediaIntent;
// The command you will give to siri will come here in MediaSearch object
var mediaName = playMediaIntent?.MediaSearch?.MediaName;
// Check if the Searched Podcast and episode is there in your Podcasts list or
// not and handle it accordingly
var response = new INPlayMediaIntentResponse(INPlayMediaIntentResponseCode.Failure, userActivity);;
if(mediaName.Exist(Playlist))
{
response = new INPlayMediaIntentResponse(INPlayMediaIntentResponseCode.ContinueInApp, null);
}
else
{
response = new INPlayMediaIntentResponse(INPlayMediaIntentResponseCode.FailureUnknownMediaType, null);
}
completionHandler(response);
}
Give search command to siri like this -
Play [EpisodeName] of [PodcastName] in your app name
For Example: `Play Conclusion in Selling Girls of America in siridemo app`
Output:
Now in MediaSearch.MediaName you will get "Conclusion in Selling Girls of America" as search string.
Happy Coding!
Hello stackoverflowers,
I am working on a project that is built using MVVMCross and Xamarin for iOS and Android. I have found out that the project uses a quite old version of MVVMCross (4.4.0) and I am trying to bring it up to the current one (6.4). I thought it's a good idea to first upgrade to 5.7 and on a later stage, when I have the navigation switched to the new form etc, I will bump up to 6++. I have sucessfully run the android version to 5.7, however, the iOS version uses a customPresenter, that I don't quite know how to transform to the new Presenter introduced in 5.1. I think my custom presenter is based on https://github.com/MvvmCross/MvvmCross-Samples/tree/master/XPlatformMenus/XPlatformMenusTabs.iOS which hasn't been updated in a while.
In my MvxTabPresenter that subclasses MvxIosViewPresenter, the show function is no longer overridable. In addition IMvxModalIosView doesnt seem to exist anymore.
public override void Show(IMvxIosView view)
{
if (view is IMvxModalIosView)
{
if (this._currentModalViewController != null)
{
return;
}
var currentModalViewController = view as MvxViewController;
this._currentModalViewController = currentModalViewController;
currentModalViewController.ModalPresentationStyle = UIModalPresentationStyle.Popover;
CurrentTopViewController.AddChildViewController(currentModalViewController);
currentModalViewController.View.Frame = CurrentTopViewController.View.Bounds.Inset(10, 10);
currentModalViewController.View.Alpha = 0;
CurrentTopViewController.View.Add(currentModalViewController.View);
currentModalViewController.DidMoveToParentViewController(CurrentTopViewController);
UIView.Animate(0.25, () =>
{
currentModalViewController.View.Alpha = 1;
});
//this.PresentModalViewController(currentModalViewController, true);
return;
}
if (view is HomeView)
{
if (this.CurrentTopViewController is MvxTabBarViewController)
{
TabBarPresenter.SelectedIndex = 0;
return;
}
public override void CloseModalViewController()
{
if (this._currentModalViewController != null)
{
this._currentModalViewController.DismissModalViewController(true);
_currentModalViewController.WillMoveToParentViewController(null);
_currentModalViewController.View.RemoveFromSuperview();
_currentModalViewController.RemoveFromParentViewController();
this._currentModalViewController = null;
return;
}
base.CloseModalViewController();
}
}
Also this is no longer overridable from the superclass.
Any suggestions on how to approach this?
Kind regards,
V
As you may see in the MvxIosViewPresenter now the mvx attributes are registered with the action that should be called.
So, firstly you should inherit from MvxIosViewPresenter. Then, for modal you should override ShowModalViewController.
I suggest you to read the docs, the MvxIosViewPresenter and MvxAttributeViewPresenter files on the repo to check out how it works.
HIH
I'm trying to implement Keychain from start to finish for my Xamarin Application. At the moment I have the initial bit that will display the native prompt `Touch ID to Use Password'. That works successfully using the bit of code below.
public class TouchId : ITouchID
{
public Task<bool> AuthenticateUserIDWithTouchID()
{
var taskSource = new TaskCompletionSource<bool>();
var context = new LAContext();
if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError AuthError))
{
var replyHandler = new LAContextReplyHandler((success, error) => {
taskSource.SetResult(success);
});
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "Logging with touch ID", replyHandler);
};
return taskSource.Task;
}
}
My question is how do I display the native prompt asking if I would like to save to Keychain.
I've created a basic one using xamarin's Display Alert, but it doesn't quite look as nice as the native.
I am using the Facebook Auth SDK, with a Xamarin Forms C# example. However, the Facebook SDK has depreciated the method and replaced it with one which adds a fromViewController variable into the constructors. I am not particularly comfortable with the concept of ViewControllers in Xamarin, or indeed with this code as it is from a sample, so is there a way to gauge the current ViewController?
I have seen a few .net examples e.g. NSArray *viewContrlls=[[self navigationController] viewControllers];[viewContrlls lastObject]; However, this approach doesn't seem to work with Xamarin, as self doesn't contain definitions for navigationControllers.
Alternatively, is there any way of easily working out which variable my current ViewController is sotored in, using the sample code?
Update
This method has become a bit more complex in order to support newer iOS versions in race conditions. This is the new version of the utility with some improvements:
public static UIViewController? GetTopViewController()
{
var window = UIApplication.SharedApplication.GetKeyWindow();
var vc = window?.RootViewController;
while (vc is { PresentedViewController: { } })
vc = vc.PresentedViewController;
if (vc is UINavigationController { ViewControllers: { } } navController)
vc = navController.ViewControllers.Last();
return vc;
}
public static UIWindow? GetKeyWindow(this UIApplication application)
{
if (!UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
return application.KeyWindow; // deprecated in iOS 13
var window = application
.ConnectedScenes
.ToArray()
.OfType<UIWindowScene>()
.SelectMany(scene => scene.Windows)
.FirstOrDefault(window => window.IsKeyWindow);
return window;
}
Old answer
The accepted answer won´t give you the current view controller if it´s in the stack of a parent UINavigationController, so I came up with the following:
public static UIViewController GetTopViewController()
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
vc = vc.PresentedViewController;
if (vc is UINavigationController navController)
vc = navController.ViewControllers.Last();
return vc;
}
The best way to do this is to pass in a reference to the ViewController that is calling the Auth method.
However, you can also try this approach (courtesy of AdamKemp on the Xamarin Forums)
var window= UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
I was searching for the Xamarin implementation of How to open settings programmatically
Vito-ziv answered it for objective C - what is the correct way to do this in C# for iOS in Xamarin Studio?
For current devices this is only possible in ios8 (ios9 not available at time of writing) (It used to be possible before ios5 apparently - see this blog post from Adrian Stevens at Xamarin - shout out to him for the inspiration for this answer)
To do it in ios8, I did it like this:
var settingsString = UIKit.UIApplication.OpenSettingsUrlString;
var url = new NSUrl (settingsString);
UIApplication.SharedApplication.OpenUrl (url);
Where the above code was called from a click event via delegate class in a UIAlertView click.
Since I am supporting ios7 too, to handle ios7 devices I did this, where the HandleLocationAuthorisation method is called when deciding whether to present a view controller - the user on ios8 and above can choose to go to the settings directly, whereas the user on ios7 has to go there manually.
This example below is checking for location services, but with trivial changes could easily be changed to check for other types of settings.
public bool HandleLocationAuthorisation ()
{
if (CLLocationManager.Status == CLAuthorizationStatus.AuthorizedAlways) {
return true;
} else {
UIAlertView uiAlert;
//iOS 8 and above can redirect to settings from within the app
if (UIDevice.CurrentDevice.CheckSystemVersion(8,0)) {
uiAlert = new UIAlertView
("Location Services Required",
"",
null,
"Return To App","Open Settings");
uiAlert.Delegate = new OpenSettingsFromUiAlertViewDelegate();
uiAlert.Message = "Authorisation to use your location is required to use this feature of the app.";
//ios7 and below has to go there manually
} else {
uiAlert = new UIAlertView
("Location Services Required",
"Authorisation to use your location is required to use this feature of the app. To use this feature please go to the settings app and enable location services",
null,
"Ok");
}
uiAlert.Show ();
return false;
}
}
For completeness, here is the code for the event delgate referenced above:
public class OpenSettingsFromUiAlertViewDelegate : UIAlertViewDelegate {
public override void Clicked (UIAlertView alertview, nint buttonIndex)
{
if (buttonIndex == 1) {
var settingsString = UIKit.UIApplication.OpenSettingsUrlString;
var url = new NSUrl (settingsString);
UIApplication.SharedApplication.OpenUrl (url);
}
}
}
Hope this will help you. This is working in iPhone not sure about working on iPad.
var url = new NSUrl("prefs:root=Settings");
UIApplication.SharedApplication.OpenUrl(url);