Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Swift iOS native plugins hurdles

Discussion in '2019.3 Beta' started by z000z, Jan 2, 2020.

  1. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
    With the iOS change to making all the Unity code now be in the UnityFramework target it's added some new hurdles for using swift to write a plugin to expose iOS functionality to the c# side.

    Previously you could create a bridging header to bridge swift and objective-c/c++ so that you can have your swift code and then c wrapper to expose to c#. Unfortunately frameworks are not allowed to have bridging headers.

    Mixing Swift and Objective-C/C/C++ code in a Framework requires you to have any code that the Swift code needs access to in the Public Headers section of the build target. So for Swift code to access the Unity "SendMessage" function you need access to the UnityInterface.h. So ideally you'd add UnityInterface.h to the headers and then be done, except that you can't because UnityInterface isn't a header that is allowed in the headers section.

    This is solvable though you can build an objective-c wrapper class for UnityInterface.h's SendMessage and then include your objective-c classes header in the headers section. And then also include it in the UnityFramework.h umbrella header. Also strangely you may need to re-order the build phases so that the the headers build phase is first for everything to work.

    So while you can get swift plugins to work with some manual manipulation of the Xcode project, these manual steps as far as I can tell are not available in the PBXProject class.

    It seems like at least some of that is "almost" part of the PBXProject class, I can locate the header I need using FindFileGuidByProjectPath, and it seems like AddFileToBuildSection ought to work if you give it the write guid for the header section, but interestingly if I copy the guid out of the pbxproject used for that section and use it it AddFileToBuildSection crashes. Also oddly calling GetBuildPhaseName on that guid causes a null reference, which is odd considering GetAllBuildPhasesForTarget does return that guid in it's array.

    But even if that did work there's no good way to re-order the build phases as far as I can see, and add your wrapper header to the UnityFramework.h.

    I think ideally UnityInterface.h would be made compatible or some equivalent compatible header would be in the headers section already and possibly the headers would be at the top of the build phases by default.
     
    le_duke, cloutiertyler and liamwalsh like this.
  2. n8zach

    n8zach

    Joined:
    Jan 15, 2020
    Posts:
    1
    I think I have a very similar issue... I am trying to enable adding the TestFairy SDK to my Unity project and build in Unity Cloud Build. It requires configuring a bridging header as described here https://docs.testfairy.com/iOS_SDK/Integrating_iOS_SDK.html#ios-swift but I cannot figure out how to script this as a post export step using the PBXProject API.
     
    Wixar172 likes this.
  3. Krusty666

    Krusty666

    Joined:
    Feb 24, 2020
    Posts:
    5
    @n8zach I think your problem is easy to solve. You just need the Bridging header and then include it in your Plugins/iOS folder in Unity no PBX needed.
     
  4. Krusty666

    Krusty666

    Joined:
    Feb 24, 2020
    Posts:
    5
    Hi zOOOz,
    could you elaborate how mixing Objective-C and Swift with the UnityFramework target? I have exactly the same problem and and find out how to achieve this. I tried everything Google had to offer but without success.
    The manual steps you are referring to would be enough for now. When it works I would drive deeper into the PBX-Stuff.
     
  5. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
    @Krusty666 Sure, there's a few steps we have to do:
    1. In your c interface .mm file you'll need to change your include from "projectname-swift.h" to
    #include "UnityFramework/UnityFramework-Swift.h"
    2. If you're using SendMessage in your swift code, you'll have to write an objective-c wrapper class, that you call SendMessage from, and then use your wrapper class in your swift code. Since you can't include the UnityInterface.h file in the swift code.

    Then if you do write that wrapper class, you'll have to do a few more steps for it to work:
    1. In the Xcode project go to Build Phases, select the UnityFramework Target, and then Drag the Headers section to the right under dependencies.
    2. Add your wrapper objective-c header to the Headers section, and then drag to public.
    3. Modify the UnityFramework.h to import your wrapper objective-c header, right above the UnityAppController import

    That should hopefully get you going.
     
    ryan_unity279 and cloutiertyler like this.
  6. aasiq

    aasiq

    Joined:
    Dec 29, 2016
    Posts:
    16
    @z000z, I've tried your steps. I can incluce Unityframework.h in .mm file. But, it doesn't contains headers for my swift code. So, it throws error like this.
    upload_2020-7-16_12-33-40.png
     

    Attached Files:

  7. naninunenoy

    naninunenoy

    Joined:
    Oct 25, 2017
    Posts:
    1
    @aasiq I had same issue. You need to make public your swift class to use in obj-c.
     
    zz2020 likes this.
  8. cloutiertyler

    cloutiertyler

    Joined:
    May 12, 2018
    Posts:
    5
    > 1. In your c interface .mm file you'll need to change your include from "projectname-swift.h" to
    #include "UnityFramework/UnityFramework-Swift.h"

    Sweet heavens @z000z you saved my life with this.
     
    slampants likes this.
  9. Martin_Gonzalez

    Martin_Gonzalez

    Joined:
    Mar 25, 2012
    Posts:
    361
    I had to make public the swift class and also add @objc to the func i wanted to call
     
  10. falkenbrew

    falkenbrew

    Joined:
    Apr 21, 2020
    Posts:
    146
    Looks like I am stuck on a similar issue. "Making public" does that mean "public class Example{ ... }" or something else? Because making the class public and adding @objc to the function does nothing for me. I am getting the same error as aasiq.

    I am basing my code off:
    https://stackoverflow.com/questions/31636408/write-unity-ios-plugin-in-swift-code

    edit: my swift class inherits from NSObject so it should be objective-c friendly, adding @objc did nothing probably because of that?

    edit: I've also tried reordering the build-phase so that the swift code is compiled first, to no avail.
    @aasig: I would be interested to know if and how you fixed your problem as mine is identical (well the error is)

    edit: Turns out I messed up and created two mm files containing the extern definition. Only one of these files had #import "UnityFramework/UnityFramework-Swift.h" and that was causing my problem.

    edit: This was also important. After not working on the project for a while, it no longer compiled.
    UnityFramework -> Build Phases -> Headers and drag the relevant header files from Project to Public.
    https://stackoverflow.com/questions/27776497/include-of-non-modular-header-inside-framework-module
     
    Last edited: Jan 12, 2021
  11. iyakov

    iyakov

    Joined:
    Aug 27, 2014
    Posts:
    4
    I setup xcode project to make Objective C code see and call swift classes. However, I have an issue when swift code does not see a type defined in Objective C.

    It was previously done via "Objective C bridging header" but it is not allowed for Unity 2019.3+'s framework target.

    i.e. if I try to use a type from header I get "Use of undeclared type 'UserInformationData'", where UserInformationData is a struct defined in my custom ObjectiveC header. Documentation says I should now (for framework target which is generated by Unity 2019.3+) include them into umbrella header, but I have no idea what it means. I was trying to include my custom ObjectiveC header into UnityFramework.h with no success - get the error "Include of non-modular header inside framework module 'UnityFramework'". "Define module" and "allow non modular includes" properties are set for both targets (main and framework).

    Do you have any clues on how to fix it?
     
    le_duke likes this.
  12. Shaundev

    Shaundev

    Joined:
    Jul 3, 2012
    Posts:
    6
    @zOOOz, thanks for the detailed explanation, it's really helped!

    Could you provide an example of the objective-c wrapper class to call SendMessage from, and how this would be used in the swift code? I'm stuck on this point and trying to edit an old plugin I didn't make which is using SendMessage in the swift code.

    Thanks!
     
  13. ferrer_unity

    ferrer_unity

    Joined:
    Jun 3, 2019
    Posts:
    1
    Hello! Can you provide an example of how to solve it? UnitySendMessage is still now working in my swift code.
     
  14. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
    @iyakov I believe the problem your seeing is because you need to add the header of your UserInformationData class to the UnityFramework.h

    @shunshun Sure here's all you need:
    Code (CSharp):
    1.  
    2. #import "SendMessageInterface.h"
    3. #import "UnityInterface.h"
    4.  
    5. @implementation SendMessageInterface
    6. + (void) SendMessage:(NSString *)obj to:(NSString*) method and:(NSString*) msg
    7. {
    8.     UnitySendMessage([obj UTF8String], [method UTF8String], [msg UTF8String]);
    9. }
    10. @end
    11.  
    You'll want to add to the UnityFramework.h file:
    #import "SendMessageInterface.h"

    Also you'll need to make sure that "SendMessageInterface.h" is added to the Public Headers section in the UnityFramework -> Build Phases settings

    And then in a swift class you should be able to call it by doingL
    SendMessageInterface.sendMessage("yourMonoBehaviorName",to:"yourMethodName", and:"yourMessage")
     
    iyakov and le_duke like this.
  15. TijsVdV

    TijsVdV

    Joined:
    Mar 7, 2017
    Posts:
    32
    @z000z Hi, this topic has helped me alot sofar. Your UnitySendMessage solved that issue for me. But currently i still have an other issue and maybey you could help me.

    We have a swift class we wrote our self but we are unable to acces that anymore without the Unity-Bridge.
    This Swift class contains functions, a singleton. And all of these are preceded with @obj but still i cant call any of them.
    Do you have any idea what i am missing?
     
    Last edited: Sep 23, 2020
  16. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
    @TijsVdV Have you created a .mm file that implements externc functions to expose your swift class functions? Something like:

    Code (CSharp):
    1.  
    2. #include "UnityFramework/UnityFramework-Swift.h"
    3. #pragma mark - C interface
    4.  
    5. extern "C" {  
    6.  
    7.     void _doSwiftThing()
    8.     {  
    9.         [[YourSwiftClass shared] doSwiftThing];
    10.     }
    11.  
    12. }
    13.  
     
    le_duke likes this.
  17. TijsVdV

    TijsVdV

    Joined:
    Mar 7, 2017
    Posts:
    32
    I am actually calling my functions directly out of the objective c part of my .mm file, we do have an extern C part at the bottom for the functions we call out of Unity. But i need my swift functions to be called in iOS functions like applicationDidFinishLoading etc..

    But even when i try that calling them in my extern c part it still doesnt work. I will try to create a seperate .mm file with only extern c stuff in to call my swift functions and see if that works.

    *Edit1: still have the same issue in extern c. Weird thing is it recognizes my class but i cant call the single ton or any functions of it.

    *Edit2: i have a feeling the briding-header file is still required. Is this correct? But then it should be in the Unity-iPhone part of the build and not the UnityFramework?
     
    Last edited: Sep 24, 2020
  18. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
    @TijsVdV Ah ok I haven't tried using them in the iOS code, for just using in the c# code you don't need a bridging header.
     
  19. TijsVdV

    TijsVdV

    Joined:
    Mar 7, 2017
    Posts:
    32
    Pff still no success. This is holding our new release back. Its a shame that hardly any documentation can be found.
     
  20. acsoow

    acsoow

    Joined:
    Dec 8, 2020
    Posts:
    2
    @z000z thanks for the in-detail explanation. I am trying to make a third party package work and i followed the above mentioned steps. Am getting the "using bridging headers with framework targets is unsupported" error in Xcode.

    Any Ideas, Thanks!
     
  21. cihad_unity

    cihad_unity

    Joined:
    Dec 27, 2019
    Posts:
    35
    I've been working for two days to run a swift code.
    As far as I see, we need module.modulemap file but still couldn't figure out how to do.
    I have an asset installed in my project and tried copying and modifying its code to do the same but no success.
    Looks like i can't have multiple modulemaps in the libraries folder?
     
  22. iyakov

    iyakov

    Joined:
    Aug 27, 2014
    Posts:
    4
    Thanks!
     
  23. Chadobado

    Chadobado

    Joined:
    Oct 19, 2016
    Posts:
    26
    Thanks for all the posts in this thread. Between them and this module map example I was able to get my plugin mostly working with the changes. The last problem I’m having is using UnityFramework in an Xcode (SwiftUI) project via Uaal. For some reason linker can’t find the x86_64 architecture for the plugin for use in the simulator. The unity project works fine in the simulator. It’s only failing when trying to use UnityFramework via Uaal. I’ve blown a week on it with no success. Anyone able to get this working or have any suggestions? TIA!

    Additional details --> Here
     
    Last edited: Feb 1, 2021
  24. z000z

    z000z

    Joined:
    Dec 31, 2014
    Posts:
    96
    Wanted to add a few helpful hints to this thread that I've run into.

    1. In your swift code, anywhere you use @objc, like for example @objc class Cart: NSObject, or @objc static func createApplePaySession, you will want to add public or open to after the @objc, like this: @objc public class Cart: NSObject. Xcode will complain that it can't see the class or function if you don't have public or open there. @TijsVdV this might be what you were running into.

    2. If you're seeing complaints about using a Bridging header, it means that something is setting one up for you. Some plugins may try to do this using Unity's build pipeline, look for some c# code under an editor folder that's using the PostProcessBuildAttribute. You should look for some use of the PBXProject (or possibly a class that inherits from it), and calling SetBuildProperty and passing in "SWIFT_OBJC_BRIDGING_HEADER" either directly or through some static. You'll want to remove that.

    3. If your Xcode project has some errors in .mm files where it can't find a class, there's generally 3 causes for that, You might just need to add this line to the top of your .mm file
    #import "UnityFramework/UnityFramework-Swift.h"
    Or
    You might need to add the .h file for the class to your UnityFramework.h file
    Or
    You might need to add the .h file for the class to the public header section (in the UnityFramework's Build Phases settings)

    4. You can automate adding your headers to the Public area of the Build Phases setting by using the PBXProject's AddPublicHeaderToBuild.

    5. You can also automate adding your headers to the UnityFramework.h file as well, you can just fine the file using the buildPath/UnityFramework/UnityFramework.h (buildPath is passed into your function you decorate with the attribute PostProcessBuildAttribute). Then you can read the file in, add some lines to it with your headers, and write it back out.
     
  25. ROBYER1

    ROBYER1

    Joined:
    Oct 9, 2015
    Posts:
    1,454
    We are trying to go about adding push notifications with VOIP (call incoming notifications) in Unity, the latter of which can only be done with Apple Callkit/pushkit in Swift code, would the methods suggested in this thread be appropriate for bridging some Swift code with our app to handle the native VOIP call notifications/full screen call takeover stuff while also being within the same Xcode exported Unity project?
     
  26. jtok4j

    jtok4j

    Joined:
    Dec 6, 2013
    Posts:
    322
    I too had issues with the UnitySendMessage, taking me a few hours to search for solutions, but I finally found it here:

    It's at this link: https://github.com/jwtan/SwiftToUnityExample#swift-to-unity-ios-example

    Under the "Umbrella Header" section/instructions.
    Followed the instructions, to configure the various files and make them public, then no more complaints from Xcode/Swift that it couldn't understand UnitySendMessage! :)
     
  27. arnaldoGaroto

    arnaldoGaroto

    Joined:
    Feb 3, 2013
    Posts:
    22
    I got it working based on https://github.com/jwtan/SwiftToUnityExample/
    if you don't need to use UnitySendMessage I think you don't need UnityFramework.modulemap and you don't need the postProcess script

    DemoPlugin.Swift
    Code (CSharp):
    1.  
    2.  
    3. import Foundation
    4.  
    5. @objc public class DemoPlugin: NSObject
    6. {
    7.     @objc public static let instance = DemoPlugin() //singleton initialization
    8.  
    9.     @objc public func DemoPluginSetValue(_ param: String)
    10.     {
    11.         print("\(#function) is called with param: \(param)")  
    12.     }
    13.  
    14.  
    15.     @objc public func DemoPluginGetValue() -> String
    16.     {
    17.         print("\(#function) is called")  
    18.  
    19.         return "something";
    20.     }
    21. }
    22.  


    DemoPluginSwiftToUnityBridge.mm
    Code (CSharp):
    1.  
    2.  
    3. #import <UnityFramework/UnityFramework-Swift.h>
    4.  
    5. //Bridge between Unity interface and the Swift implementation
    6. //based on https://github.com/jwtan/SwiftToUnityExample
    7.  
    8. extern "C"
    9. {
    10.     char* DemoPluginGetValue()
    11.     {
    12.         NSString *value = [[DemoPlugin instance] DemoPluginGetValue];
    13.         if (value == nil || [value isEqualToString:@""])
    14.         {
    15.             value = @"";
    16.         }
    17.      
    18.         //copy string and convert in the format I need to return it
    19.         const char* utf8String = [value UTF8String];
    20.         char* copy = (char*)malloc(strlen(utf8String) + 1);
    21.         strcpy(copy, utf8String);
    22.         return copy;
    23.     }
    24.  
    25.     void DemoPluginSetValue(const char* param)
    26.     {
    27.         NSString* paramNSString = [NSString stringWithUTF8String: param];
    28.  
    29.         [[DemoPlugin instance] DemoPluginSetValue:paramNSString];
    30.     }
    31. }
    32.  
    33.  
    34.  


    ClassUsingPlugin.cs
    Code (CSharp):
    1.  
    2.  
    3. using System;
    4. using UnityEngine;
    5. #if !UNITY_EDITOR && UNITY_IOS
    6. using System.Runtime.InteropServices;
    7. #endif
    8.  
    9.  
    10.         #if !UNITY_EDITOR && UNITY_IOS
    11.         [DllImport("__Internal")]
    12.         private static extern string DemoPluginGetValue();
    13.  
    14.         [DllImport("__Internal")]
    15.         private static extern void DemoPluginSetValue(string param);
    16.         #endif
    17.  
    18.         public static string GetValue()
    19.         {
    20.             #if !UNITY_EDITOR && UNITY_IOS
    21.  
    22.             return DemoPluginGetValue();
    23.  
    24.             #endif
    25.             return "";
    26.         }
    27.  
    28.  
    29.         public static void SetValue(string param)
    30.         {
    31.             #if !UNITY_EDITOR && UNITY_IOS
    32.          
    33.             DemoPluginSetValue(param);
    34.             return;
    35.  
    36.             #endif
    37.         }
    38.  
    39.  
     
    igorskugar, saktan and FlexStudio05 like this.