diff options
author | Jason Smith <jason.smith@xamarin.com> | 2016-04-24 12:25:26 -0400 |
---|---|---|
committer | Rui Marinho <me@ruimarinho.net> | 2016-04-24 12:25:26 -0400 |
commit | 5907152c50ee2c658b266f2804e6b383bb15a6f1 (patch) | |
tree | 9beb907623359723456c5c03b08922bebc4f41f3 /Xamarin.Forms.Platform.iOS | |
parent | feac1ba3ed6df5e27b3fa2076bd15c190cbacd1c (diff) | |
download | xamarin-forms-5907152c50ee2c658b266f2804e6b383bb15a6f1.tar.gz xamarin-forms-5907152c50ee2c658b266f2804e6b383bb15a6f1.tar.bz2 xamarin-forms-5907152c50ee2c658b266f2804e6b383bb15a6f1.zip |
Evolve feature branch (#117)
* Initial import of evolve features
* [Android] Add Xamarin.Forms.Platform.Android.AppLinks project
* [iOS] Fix issues with c# 6 features on iOS AppLinks
* Added naive stanza to update-docs-windows.bat to produce Pages docs. Not tested. (#69)
* Update packages
* Add AppLinks android nuspec and fix linker issues
* Fix build
* Fix nusepc
* Fix nuspec
* Update android support nugets to 23.2.1
* Update Xamarin.UITest
* Add CardView
* [iOS] Fix app link for CoreSpotlight
* [Android] Update AppLinks android support libs
* Add Newtonsoft.Json dependency to nuspec
* Fix NRE when setting ControlTemplate to null
* Move to ModernHttpClient for download
* Try fix build
* Preserve android app links
* Fix margin issue
* General coding and simple fixes
Diffstat (limited to 'Xamarin.Forms.Platform.iOS')
5 files changed, 231 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs b/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs index a4342473..78dc6dca 100644 --- a/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs +++ b/Xamarin.Forms.Platform.iOS/FormsApplicationDelegate.cs @@ -5,10 +5,12 @@ using System.Text; #if __UNIFIED__ using Foundation; using UIKit; +using CoreSpotlight; #else using MonoTouch.Foundation; using MonoTouch.UIKit; +using MonoTouch.CoreSpotlight; #endif namespace Xamarin.Forms.Platform.iOS @@ -25,6 +27,7 @@ namespace Xamarin.Forms.Platform.iOS public override bool ContinueUserActivity(UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler) { + CheckForAppLink(userActivity); return true; } @@ -77,6 +80,7 @@ namespace Xamarin.Forms.Platform.iOS public override void UserActivityUpdated(UIApplication application, NSUserActivity userActivity) { + CheckForAppLink(userActivity); } // from background to foreground, not yet active @@ -121,6 +125,7 @@ namespace Xamarin.Forms.Platform.iOS Application.Current = application; _application = application; + (application as IApplicationController)?.SetAppIndexingProvider(new IOSAppIndexingProvider()); application.PropertyChanged += ApplicationOnPropertyChanged; } @@ -131,6 +136,29 @@ namespace Xamarin.Forms.Platform.iOS UpdateMainPage(); } + void CheckForAppLink(NSUserActivity userActivity) + { + var strLink = string.Empty; + + switch (userActivity.ActivityType) + { + case "NSUserActivityTypeBrowsingWeb": + strLink = userActivity.WebPageUrl.AbsoluteString; + break; + case "com.apple.corespotlightitem": + if (userActivity.UserInfo.ContainsKey(CSSearchableItem.ActivityIdentifier)) + strLink = userActivity.UserInfo.ObjectForKey(CSSearchableItem.ActivityIdentifier).ToString(); + break; + default: + if (userActivity.UserInfo.ContainsKey(new NSString("link"))) + strLink = userActivity.UserInfo[new NSString("link")].ToString(); + break; + } + + if (!string.IsNullOrEmpty(strLink)) + _application.SendOnAppLinkRequestReceived(new Uri(strLink)); + } + void SetMainPage() { UpdateMainPage(); diff --git a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj index cc6aa44c..1fc4340b 100644 --- a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj +++ b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.Classic.csproj @@ -144,6 +144,8 @@ <Compile Include="$(MSBuildThisFileDirectory)Renderers\ToolbarRenderer.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Renderers\WebViewRenderer.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Properties\AssemblyInfo.cs" /> + <Compile Include="iOSAppIndexingProvider.cs" /> + <Compile Include="iOSAppLinks.cs" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="Resources\StringResources.ar.resx" /> diff --git a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj index 62993b4a..dde9103e 100644 --- a/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj +++ b/Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.iOS.csproj @@ -144,6 +144,8 @@ <DependentUpon>StringResources.resx</DependentUpon> </Compile> <Compile Include="ViewInitializedEventArgs.cs" /> + <Compile Include="IOSAppIndexingProvider.cs" /> + <Compile Include="IOSAppLinks.cs" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="Resources\StringResources.ar.resx" /> diff --git a/Xamarin.Forms.Platform.iOS/iOSAppIndexingProvider.cs b/Xamarin.Forms.Platform.iOS/iOSAppIndexingProvider.cs new file mode 100644 index 00000000..88e43e54 --- /dev/null +++ b/Xamarin.Forms.Platform.iOS/iOSAppIndexingProvider.cs @@ -0,0 +1,9 @@ +using System; + +namespace Xamarin.Forms.Platform.iOS +{ + public class IOSAppIndexingProvider : IAppIndexingProvider + { + public IAppLinks AppLinks => new IOSAppLinks(); + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.iOS/iOSAppLinks.cs b/Xamarin.Forms.Platform.iOS/iOSAppLinks.cs new file mode 100644 index 00000000..de5876b4 --- /dev/null +++ b/Xamarin.Forms.Platform.iOS/iOSAppLinks.cs @@ -0,0 +1,190 @@ +using System; +using System.Threading.Tasks; +#if __UNIFIED__ +using Foundation; +using CoreSpotlight; +using UIKit; + +#else +using MonoTouch.Foundation; +using MonoTouch.UIKit; +using MonoTouch.CoreSpotlight; +#endif + +namespace Xamarin.Forms.Platform.iOS +{ + internal class IOSAppLinks : IAppLinks + { + public async void DeregisterLink(IAppLinkEntry appLink) + { + if (string.IsNullOrWhiteSpace(appLink.AppLinkUri?.ToString())) + throw new ArgumentNullException("AppLinkUri"); + await RemoveLinkAsync(appLink.AppLinkUri?.ToString()); + } + + public async void DeregisterLink(Uri uri) + { + if (string.IsNullOrWhiteSpace(uri?.ToString())) + throw new ArgumentNullException(nameof(uri)); + await RemoveLinkAsync(uri.ToString()); + } + + public async void RegisterLink(IAppLinkEntry appLink) + { + if (string.IsNullOrWhiteSpace(appLink.AppLinkUri?.ToString())) + throw new ArgumentNullException("AppLinkUri"); + await AddLinkAsync(appLink); + } + + public async void DeregisterAll() + { + await ClearIndexedDataAsync(); + } + + static async Task AddLinkAsync(IAppLinkEntry deepLinkUri) + { + var appDomain = NSBundle.MainBundle.BundleIdentifier; + string contentType, associatedWebPage; + bool shouldAddToPublicIndex; + + //user can provide associatedWebPage, contentType, and shouldAddToPublicIndex + TryGetValues(deepLinkUri, out contentType, out associatedWebPage, out shouldAddToPublicIndex); + + //our unique identifier will be the only content that is common to spotlight search result and a activity + //this id allows us to avoid duplicate search results from CoreSpotlight api and NSUserActivity + //https://developer.apple.com/library/ios/technotes/tn2416/_index.html + var id = deepLinkUri.AppLinkUri.ToString(); + + var searchableAttributeSet = await GetAttributeSet(deepLinkUri, contentType, id); + var searchItem = new CSSearchableItem(id, appDomain, searchableAttributeSet); + //we need to make sure we index the item in spotlight first or the RelatedUniqueIdentifier will not work + await IndexItemAsync(searchItem); + +#if __UNIFIED__ + var activity = new NSUserActivity($"{appDomain}.{contentType}"); +#else + var activity = new NSUserActivity (new NSString($"{appDomain}.{contentType}")); +#endif + activity.Title = deepLinkUri.Title; + activity.EligibleForSearch = true; + + //help increase your website url index rating + if (!string.IsNullOrEmpty(associatedWebPage)) + activity.WebPageUrl = new NSUrl(associatedWebPage); + + //make this search result available to Apple and to other users thatdon't have your app + activity.EligibleForPublicIndexing = shouldAddToPublicIndex; + + activity.UserInfo = GetUserInfoForActivity(deepLinkUri); + activity.ContentAttributeSet = searchableAttributeSet; + + //we don't need to track if the link is active iOS will call ResignCurrent + if (deepLinkUri.IsLinkActive) + activity.BecomeCurrent(); + + var aL = deepLinkUri as AppLinkEntry; + if (aL != null) + { + aL.PropertyChanged += (sender, e) => + { + if (e.PropertyName == AppLinkEntry.IsLinkActiveProperty.PropertyName) + { + if (aL.IsLinkActive) + activity.BecomeCurrent(); + else + activity.ResignCurrent(); + } + }; + } + } + + static Task<bool> ClearIndexedDataAsync() + { + var tcs = new TaskCompletionSource<bool>(); + if (CSSearchableIndex.IsIndexingAvailable) + CSSearchableIndex.DefaultSearchableIndex.DeleteAll(error => tcs.TrySetResult(error == null)); + else + tcs.TrySetResult(false); + return tcs.Task; + } + + static async Task<CSSearchableItemAttributeSet> GetAttributeSet(IAppLinkEntry deepLinkUri, string contentType, string id) + { + var searchableAttributeSet = new CSSearchableItemAttributeSet(contentType) + { + RelatedUniqueIdentifier = id, + Title = deepLinkUri.Title, + ContentDescription = deepLinkUri.Description, + Url = new NSUrl(deepLinkUri.AppLinkUri.ToString()) + }; + + var source = deepLinkUri.Thumbnail; + IImageSourceHandler handler; + if (source != null && (handler = Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null) + { + UIImage uiimage; + try + { + uiimage = await handler.LoadImageAsync(source); + } + catch (OperationCanceledException) + { + uiimage = null; + } + searchableAttributeSet.ThumbnailData = uiimage.AsPNG(); + uiimage.Dispose(); + } + + return searchableAttributeSet; + } + + static NSMutableDictionary GetUserInfoForActivity(IAppLinkEntry deepLinkUri) + { + //this info will only appear if not from a spotlight search + var info = new NSMutableDictionary(); + info.Add(new NSString("link"), new NSString(deepLinkUri.AppLinkUri.ToString())); + foreach (var item in deepLinkUri.KeyValues) + info.Add(new NSString(item.Key), new NSString(item.Value)); + return info; + } + + static Task<bool> IndexItemAsync(CSSearchableItem searchItem) + { + var tcs = new TaskCompletionSource<bool>(); + if (CSSearchableIndex.IsIndexingAvailable) + { + CSSearchableIndex.DefaultSearchableIndex.Index(new[] { searchItem }, error => tcs.TrySetResult(error == null)); + } + else + tcs.SetResult(false); + return tcs.Task; + } + + static Task<bool> RemoveLinkAsync(string identifier) + { + var tcs = new TaskCompletionSource<bool>(); + if (CSSearchableIndex.IsIndexingAvailable) + CSSearchableIndex.DefaultSearchableIndex.Delete(new[] { identifier }, error => tcs.TrySetResult(error == null)); + else + tcs.SetResult(false); + return tcs.Task; + } + + //Parse the KeyValues because user can provide associatedWebPage, contentType, and shouldAddToPublicIndex options + static void TryGetValues(IAppLinkEntry deepLinkUri, out string contentType, out string associatedWebPage, out bool shouldAddToPublicIndex) + { + contentType = string.Empty; + associatedWebPage = string.Empty; + shouldAddToPublicIndex = false; + var publicIndex = string.Empty; + + if (!deepLinkUri.KeyValues.TryGetValue(nameof(contentType), out contentType)) + contentType = "View"; + + if (deepLinkUri.KeyValues.TryGetValue(nameof(publicIndex), out publicIndex)) + bool.TryParse(publicIndex, out shouldAddToPublicIndex); + + deepLinkUri.KeyValues.TryGetValue(nameof(associatedWebPage), out associatedWebPage); + } + } +}
\ No newline at end of file |