Jump to content
Sign in to follow this  
vfbb

iOS handle incoming url

Recommended Posts

Goal: I have configured my app to be opened by some links in iOS, like myapp://lala (using custom scheme), or https://myapp.com/c/lala using universal links, and is already opening my app, but i need to know which url that opened my app. (There is even a tutorial in FMX Express)

Problem: I'm trying to handle incoming url from my ios app unsuccessfully.

 

On iOS this should be done by capturing event TApplicationEvent.OpenURL like:

procedure TipUrlHandler.ApplicationEventMessageHandler(const ASender: TObject;
  const AMessage: TMessage);
begin
  case TApplicationEventData(TApplicationEventMessage(AMessage).Value).Event of
    TApplicationEvent.OpenUrl: HandleUrl(TiOSOpenApplicationContext(TApplicationEventData(TApplicationEventMessage(AMessage).Value).Context).URL);
  else
  end;
end;

Mas o TApplicationEvent.OpenUrl nunca é chamado. So I checked the class TApplicationDelegate in the unit FMX.Platform.iOS.pas in which the TApplicationDelegate.applicationOpenURLWithOptions function should be called every time the app is opened by a url as Apple documents: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application?language=objc

class function TApplicationDelegate.applicationOpenURLWithOptions(self: id; _cmd: SEL; application: PUIApplication;
  url: Pointer; options: PNSDictionary): Boolean;

So I went to check if the applicationOpenURLWithOptions method is being added correctly in TApplicationDelegate.CreateDelegateMetaClass:

class procedure TApplicationDelegate.CreateDelegateMetaClass;
begin
  ...
  // Opening a URL-Specified Resource
  if TOSVersion.Major >= 9 then
    class_addMethod(DelegateClass, sel_getUid('application:openURL:options:'),
      @applicationOpenURLWithOptions, 'B@:@@@@')
  else
  ...
end;

And then I was able to prove it wrong! The number of arguments declared is wrong, as the correct one should be B@:@@@

Then I made the modification to FMX.Platform.iOS.pas, being as follows:

class procedure TApplicationDelegate.CreateDelegateMetaClass;
begin
  ...
  // Opening a URL-Specified Resource
  if TOSVersion.Major >= 9 then
    class_addMethod(DelegateClass, sel_getUid('application:openURL:options:'),
      @applicationOpenURLWithOptions, 'B@:@@@')
  else
  ...
end;

But the TApplicationDelegate.applicationOpenURLWithOptions function is not yet called.

What is wrong?

 

 

 

@EDIT

Sorry! It is working for custom schemes like "myapp://lala" (with or without the correction in FMX.Platform.iOS.pas) but it is not working for universal links like https://myapp.com/c/lalala.

 

Although I have already configured the universal link correctly and when I click on this universal link, the iOS open my app, but the url can't be handle with TApplicationEvent.OpenUrl, but I discovered that the handling of incoming url of a universal link is different: Handling Universal Links

Edited by vfbb

Share this post


Link to post
10 hours ago, vfbb said:

the handling of incoming url of a universal link is different

Yes, the application delegate needs to implement this method:

 

https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623072-application?language=objc

 

This means class_addmethod will need to be called to add it to the delegate (DelphiAppDelegate). It will also need an import for NSUserActivity, one of which follows (no guarantees as to being 100% accurate):

  NSUserActivityPersistentIdentifier = NSString;

  TNSUserActivityBlockMethod1 = procedure(inputStream: NSInputStream; outputStream: NSOutputStream; error: NSError) of object;
  TNSUserActivityBlockMethod2 = procedure of object;

  NSUserActivityClass = interface(NSObjectClass)
    ['{412EAEBF-5927-4D01-B83F-69D3B5DFE7B5}']
    {class} procedure deleteAllSavedUserActivitiesWithCompletionHandler(handler: TNSUserActivityBlockMethod2); cdecl;
    [MethodName('deleteSavedUserActivitiesWithPersistentIdentifiers:completionHandler:')]
    {class} procedure deleteSavedUserActivitiesWithPersistentIdentifiers(persistentIdentifiers: NSArray; handler: TNSUserActivityBlockMethod2); cdecl;
  end;

  NSUserActivity = interface(NSObject)
    ['{B8C2F6C9-31FE-4282-B7CA-98C96E163033}']
    function activityType: NSString; cdecl;
    procedure addUserInfoEntriesFromDictionary(otherDictionary: NSDictionary); cdecl;
    procedure becomeCurrent; cdecl;
    function delegate: Pointer; cdecl;
    function expirationDate: NSDate; cdecl;
    procedure getContinuationStreamsWithCompletionHandler(completionHandler: TNSUserActivityBlockMethod1); cdecl;
    function initWithActivityType(activityType: NSString): Pointer; cdecl;
    procedure invalidate; cdecl;
    function isEligibleForHandoff: Boolean; cdecl;
    function isEligibleForPrediction: Boolean; cdecl;
    function isEligibleForPublicIndexing: Boolean; cdecl;
    function isEligibleForSearch: Boolean; cdecl;
    function keywords: NSSet; cdecl;
    function needsSave: Boolean; cdecl;
    function persistentIdentifier: NSUserActivityPersistentIdentifier; cdecl;
    function referrerURL: NSURL; cdecl;
    function requiredUserInfoKeys: NSSet; cdecl;
    procedure resignCurrent; cdecl;
    procedure setDelegate(delegate: Pointer); cdecl;
    procedure setEligibleForHandoff(eligibleForHandoff: Boolean); cdecl;
    procedure setEligibleForPrediction(eligibleForPrediction: Boolean); cdecl;
    procedure setEligibleForPublicIndexing(eligibleForPublicIndexing: Boolean); cdecl;
    procedure setEligibleForSearch(eligibleForSearch: Boolean); cdecl;
    procedure setExpirationDate(expirationDate: NSDate); cdecl;
    procedure setKeywords(keywords: NSSet); cdecl;
    procedure setNeedsSave(needsSave: Boolean); cdecl;
    procedure setPersistentIdentifier(persistentIdentifier: NSUserActivityPersistentIdentifier); cdecl;
    procedure setReferrerURL(referrerURL: NSURL); cdecl;
    procedure setRequiredUserInfoKeys(requiredUserInfoKeys: NSSet); cdecl;
    procedure setSupportsContinuationStreams(supportsContinuationStreams: Boolean); cdecl;
    procedure setTargetContentIdentifier(targetContentIdentifier: NSString); cdecl;
    procedure setTitle(title: NSString); cdecl;
    procedure setUserInfo(userInfo: NSDictionary); cdecl;
    procedure setWebpageURL(webpageURL: NSURL); cdecl;
    function supportsContinuationStreams: Boolean; cdecl;
    function targetContentIdentifier: NSString; cdecl;
    function title: NSString; cdecl;
    function userInfo: NSDictionary; cdecl;
    function webpageURL: NSURL; cdecl;
  end;
  TNSUserActivity = class(TOCGenericImport<NSUserActivityClass, NSUserActivity>) end;

The method implementation should probably be (again no guarantees):

class function TApplicationDelegate.applicationContinueUserActivityRestorationHandler(self: id; _cmd: SEL; application: PUIApplication;
  userActivity: Pointer; restorationHandler: Pointer): Boolean;

As far as I know, it doesn't necessarily have to be patched in FMX.Platform.iOS since you should be able to call class_addmethod anywhere, as long as you pass it the correct class function.

 

Hopefully this will give you head start.

  • Like 1
  • Thanks 1

Share this post


Link to post

@Dave Nottage

Hi Dave, I tested and works perfectly!! Thanks you so much, you are the man!!

 

Just to register here the complete solution. (I preferred to patch the delphi source files because I already have others patchs in that files)

 

 

iOS - Handle incoming url (custom schemes or universal links)

 

In the file iOSapi.Foundation.pas put the code:

  // Dave Nottage code (https://www.delphiworlds.com/)
  NSUserActivityPersistentIdentifier = NSString;

  TNSUserActivityBlockMethod1 = procedure(inputStream: NSInputStream; outputStream: NSOutputStream; error: NSError) of object;
  TNSUserActivityBlockMethod2 = procedure of object;

  NSUserActivityClass = interface(NSObjectClass)
    ['{412EAEBF-5927-4D01-B83F-69D3B5DFE7B5}']
    {class} procedure deleteAllSavedUserActivitiesWithCompletionHandler(handler: TNSUserActivityBlockMethod2); cdecl;
    [MethodName('deleteSavedUserActivitiesWithPersistentIdentifiers:completionHandler:')]
    {class} procedure deleteSavedUserActivitiesWithPersistentIdentifiers(persistentIdentifiers: NSArray; handler: TNSUserActivityBlockMethod2); cdecl;
  end;

  NSUserActivity = interface(NSObject)
    ['{B8C2F6C9-31FE-4282-B7CA-98C96E163033}']
    function activityType: NSString; cdecl;
    procedure addUserInfoEntriesFromDictionary(otherDictionary: NSDictionary); cdecl;
    procedure becomeCurrent; cdecl;
    function delegate: Pointer; cdecl;
    function expirationDate: NSDate; cdecl;
    procedure getContinuationStreamsWithCompletionHandler(completionHandler: TNSUserActivityBlockMethod1); cdecl;
    function initWithActivityType(activityType: NSString): Pointer; cdecl;
    procedure invalidate; cdecl;
    function isEligibleForHandoff: Boolean; cdecl;
    function isEligibleForPrediction: Boolean; cdecl;
    function isEligibleForPublicIndexing: Boolean; cdecl;
    function isEligibleForSearch: Boolean; cdecl;
    function keywords: NSSet; cdecl;
    function needsSave: Boolean; cdecl;
    function persistentIdentifier: NSUserActivityPersistentIdentifier; cdecl;
    function referrerURL: NSURL; cdecl;
    function requiredUserInfoKeys: NSSet; cdecl;
    procedure resignCurrent; cdecl;
    procedure setDelegate(delegate: Pointer); cdecl;
    procedure setEligibleForHandoff(eligibleForHandoff: Boolean); cdecl;
    procedure setEligibleForPrediction(eligibleForPrediction: Boolean); cdecl;
    procedure setEligibleForPublicIndexing(eligibleForPublicIndexing: Boolean); cdecl;
    procedure setEligibleForSearch(eligibleForSearch: Boolean); cdecl;
    procedure setExpirationDate(expirationDate: NSDate); cdecl;
    procedure setKeywords(keywords: NSSet); cdecl;
    procedure setNeedsSave(needsSave: Boolean); cdecl;
    procedure setPersistentIdentifier(persistentIdentifier: NSUserActivityPersistentIdentifier); cdecl;
    procedure setReferrerURL(referrerURL: NSURL); cdecl;
    procedure setRequiredUserInfoKeys(requiredUserInfoKeys: NSSet); cdecl;
    procedure setSupportsContinuationStreams(supportsContinuationStreams: Boolean); cdecl;
    procedure setTargetContentIdentifier(targetContentIdentifier: NSString); cdecl;
    procedure setTitle(title: NSString); cdecl;
    procedure setUserInfo(userInfo: NSDictionary); cdecl;
    procedure setWebpageURL(webpageURL: NSURL); cdecl;
    function supportsContinuationStreams: Boolean; cdecl;
    function targetContentIdentifier: NSString; cdecl;
    function title: NSString; cdecl;
    function userInfo: NSDictionary; cdecl;
    function webpageURL: NSURL; cdecl;
  end;
  TNSUserActivity = class(TOCGenericImport<NSUserActivityClass, NSUserActivity>) end;
  
...  
function NSUserActivityTypeBrowsingWeb: NSString;
...
implementation
...
function NSUserActivityTypeBrowsingWeb: NSString;
begin
  result := CocoaNSStringConst(FoundationFwk, 'NSUserActivityTypeBrowsingWeb');
end;

 

In the file FMX.Platform.iOS.pas, in the TApplicationDelegate class, in the private section, put the code:

    class function applicationContinueUserActivityRestorationHandler(self: id; _cmd: SEL; application: PUIApplication;
      userActivity: Pointer; restorationHandler: Pointer; restorableObjects: Pointer): Boolean; cdecl; static;

 

In the file FMX.Platform.iOS.pas, in the implementation of the method TApplicationDelegate.CreateDelegateMetaClass, before the line "objc_registerClassPair(DelegateClass);", put the code:

  class_addMethod(DelegateClass, sel_getUid('application:continueUserActivity:restorationHandler:'),
      @applicationContinueUserActivityRestorationHandler, 'B@:@@@@');

 

In the file FMX.Platform.iOS.pas, in the TApplicationDelegate implementation, put the code:

class function TApplicationDelegate.applicationContinueUserActivityRestorationHandler(
  self: id; _cmd: SEL; application: PUIApplication; userActivity,
  restorationHandler, restorableObjects: Pointer): Boolean;
var
  LUserActivity: NSUserActivity;
  LURLString: string;
begin
  Result := False;
  if Assigned(userActivity) then
  begin
    LUserActivity := TNSUserActivity.Wrap(userActivity);
    if NSStrToStr(LUserActivity.activityType) = NSStrToStr(NSUserActivityTypeBrowsingWeb) then
    begin
      if Assigned(LUserActivity.webpageURL) then
        LURLString := NSStrToStr(LUserActivity.webpageURL.absoluteString)
      else
        LURLString := string.Empty;

      Result := PlatformCocoaTouch.HandleApplicationEvent(TApplicationEvent.OpenURL,
        TiOSOpenApplicationContext.Create(string.Empty, LURLString, nil));
    end;
  end;
end;

 

 

Usage

 

uses
  System.Messaging,
  FMX.Platform,
  FMX.Platform.iOS,
  FMX.Dialogs;

constructor TipUrlHandler.Create;
begin
  inherited Create;
  TMessageManager.DefaultManager.SubscribeToMessage(TApplicationEventMessage, ApplicationEventMessageHandler);
end;

destructor TipUrlHandler.Destroy;
begin
  TMessageManager.DefaultManager.Unsubscribe(TApplicationEventMessage, ApplicationEventMessageHandler, True);
  inherited;
end;

procedure TipUrlHandler.ApplicationEventMessageHandler(const ASender: TObject;
  const AMessage: TMessage);
begin
  case TApplicationEventData(TApplicationEventMessage(AMessage).Value).Event of
    TApplicationEvent.OpenUrl:
      begin
        Showmessage(TiOSOpenApplicationContext(TApplicationEventData(TApplicationEventMessage(AMessage).Value).Context).URL);
      end;
  else
  end;
end;

 

Edited by vfbb
  • Like 3

Share this post


Link to post

@vfbb

 

I'm trying to do the same thing for my app, but I need to use Universal Links because I'm using the same technique for the Android version of my app and a custom scheme under Android does not work inside GMail app (which is primarily where the link to the application will be, inside an e-mail), so I decided to use Universal Links, and while it works fine and its easy in the Android part, I'm struggling with Apple..

 

I understand that I have to handle the the incoming activity but I could not even reach that part... I have no idea on how to setup the application itself to be able to receive universal links. There are thousands of tutorials for XCode with native development but I didn't find anything when using Delphi

 

Since you have done the same thing would you explain how did you add the link support in your application? Do we need to change the info.plist.TemplateiOS.xml in some way?

 

I know there is a file that I have to host in my website, but I'm still struggling with the first step, that is to prepare the app to enable universal links

 

Any tips are appreciated!

 

Thanks

Share this post


Link to post
Posted (edited)

@Eric Bonilha

I know you! You are Eric of Digifort. I am Vinícius, Paulo's brother. We started programming at the same time, about 15 years ago, at the time of intertruco, lncb, ydm. 😂😂😂. I send you my contact in private.

 

Eric, this tutorial that I posted is just to detect which url opened your app, to parsing the URL. This is useful just when you have many Universal Links or wildcard in the Universal Link.

 

I made today a tutorial explain how to configure the Universal Link, You can see here: https://github.com/viniciusfbb/fmx_tutorials/blob/master/delphi_ios_universal_links/README.md

Edited by vfbb

Share this post


Link to post

Right now you can not handle universal link in ios APP, embarcadero need to implement 

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool



 

Share this post


Link to post

@loki5100

This implementation has not been done by Embarcadero yet, this is why you will need to implement them manually in Delphi source. The step by step is here. This work perfectly for me.

Share this post


Link to post

@vfbb @Dave Nottage

 

 

Thank you very much for both of you for your invaluable help.

 

I was able to successfully make the Universal Links within my application. Indeed I had to change Delphi files but I made a local copy of the files and attached to my application (so FMX and RTL source is not changed) and when compiling, the compiler will use my local copy of the modified files and it works like a charm.

 

Hopefully Embarcadero will implement these classes within the RTL in the future

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×