Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Native WatchOS app support #10070

Open
wojciech-kulik opened this issue Nov 7, 2020 · 75 comments
Open

[Feature] Native WatchOS app support #10070

wojciech-kulik opened this issue Nov 7, 2020 · 75 comments
Labels
enhancement The issue or pull request is an enhancement iOS Issues affecting Xamarin.iOS watchOS
Milestone

Comments

@wojciech-kulik
Copy link
Contributor

It would be great to be able to include native SwiftUI watchOS app in Xamarin.iOS project. Similarly to extensions (AdditionalAppExtensions).

For now, I think it is possible by pasting manually the Watch folder from the built native project into xcarchive created by Visual Studio.

It would be great to have it integrated.

@tj-devel709 tj-devel709 added enhancement The issue or pull request is an enhancement iOS Issues affecting Xamarin.iOS labels Nov 9, 2020
@tj-devel709 tj-devel709 added this to the Future milestone Nov 9, 2020
@wojciech-kulik
Copy link
Contributor Author

wojciech-kulik commented Nov 13, 2020

I successfully managed to add native watchOS to my Xamarin xcarchive and publish it on App Store.

Instruction:

  1. Archive Xamarin project
  2. Archive native watchOS project (make sure that versions, bundle id, profiles and sdk match Xamarin project).
  3. Copy dsyms, WatchKitSupport2 folder and Products/Applications/xxx.app/Watch folder from native xcarchive into Xamarin's xcarchive.

I had sometimes problems with connectivity on the simulator, not sure why, but it helped to add to Xamarin watchOS project (it's later anyway replaced in xcarchive by native one).

Steps to run it on simulator and fix connectivity issues:

  • Run iPhone and Apple Watch simulator
  • Open Watch app on iPhone, let it sync
  • Run Xamarin project
  • Kill app (detach debugger)
  • Install watchOS app using xcrun simctl install booted WatchApp.app (from native DerivedData folder Build/Products/Debug-watchsimulator)

For some reason, connection with Apple Watch doesn't work when you are running app using VS debugger.

@t9mike
Copy link

t9mike commented Mar 1, 2021

Hi @wojciech-kulik. Is your app running OK on Series 3 via App Store? I can run via Xcode debug version on Series 3. But TestFlight version will not launch. I think my only difference from you is I never added fake Apple Watch project to my Xamarin solution. I'll have to try that tonight and see if it helps.

Series 5 & 6 (44mm) OK.

@wojciech-kulik
Copy link
Contributor Author

wojciech-kulik commented Mar 1, 2021

@t9mike I don't have unfortunately Apple Watch, so I can't say. If you wish you can try to install my app and check this. However, this app is only available in the region of Poland, so you would have to change it.

This feature is extra paid by activating the premium package in-app (few dollars), however, the app should open without buying premium. Should be enough for your verification.

Please note that I updated this app 3 months ago, so I was also using older version of Xcode and VS then is available now. My Apple Watch Extension is written in SwiftUI.

App Store URL: https://itunes.apple.com/us/app/smog-polska/id1367444922

@t9mike
Copy link

t9mike commented Mar 2, 2021

@wojciech-kulik, your app had different issue: it would not install to Series 3 but would install and run to Series 5.

My test: I set my App Store to Poland and downloaded your app. I was able to install to a 44mm Series 5 and run. But it would not even install to the Series 3. The INSTALL button would transition briefly to animation then go back to INSTALL after a few seconds.

This was with two watches + Phone I completely erase and setup with a new Apple ID.

@t9mike
Copy link

t9mike commented Mar 2, 2021

@wojciech-kulik, I uploaded Xcode only version of my app to TestFlight and see the same issue. So in my case it is not related to Xamarin. I am not getting crash logs so hard to say what's going on, but I can at least open a Technical Support Incident if I can't figure it out.

@wojciech-kulik
Copy link
Contributor Author

@t9mike that's strange, thanks for letting me know. And what watchOS it was? My app supports watchOS 7+. Let me know if you find out the root cause, thanks!

@t9mike
Copy link

t9mike commented Mar 3, 2021

@wojciech-kulik it was watchOS7. I have not figured out my issue yet.

@piotrandrzejewski1
Copy link

Any updates to the topic? I'm still wondering if Xamarin is planning to at leas support embedding native WatchOS apps officially (ex. with "AdditionalAppExtensions").

@tougher
Copy link

tougher commented Oct 20, 2021

Anybody found a way to "hack" AdditionalAppExtensions to get a native watch build in?

@tougher
Copy link

tougher commented Oct 25, 2021

No need to hack anything, it's super easy to add a watch build by adding e.g. <_ResolvedWatchAppReferences Include="$(MSBuildProjectDirectory)/...../Watch WatchKit App.app" /> to your iOS project file.
But I guess there is no support for this in .NET 6, I will have to investigate.

@tougher
Copy link

tougher commented Jan 11, 2022

So the _ResolvedWatchAppReferences trick seems to be working, but there is a signing mismatch on the watch part build in Xcode. I see this in my phone console trying to install on the watch:

0x16dddf000 ACXUserPresentableErrorForCodeWithContext: Failed to create localized error for Error Domain=ACXErrorDomain Code=17 "Got error 17 in install done from remote side (MI error ApplicationVerificationFailed ; Extended 0xe8008017 ; Desc Failed to verify code signature of ... WatchKit App.app : 0xe8008017 (A signed resource has been added, modified, or deleted.))" FunctionName=-[ACXServerInstallOperation receivedDictionaryOrData:]}, Falling back to returning unlocalized error

Anybody got a clue on how to resign the watch app, when building the main app?

@SprengerS
Copy link

@tougher Did you resolve your problem? I have got the same problem. Is it necessary to resign all frameworks, apps and appex?

@tougher
Copy link

tougher commented Feb 9, 2022

@SprengerS oh yes, I thought I already posted the solution. The trick is to do deep signing and not resign. I inspected the behavior in Xcode and saw that --deep was happening when signing the iOS part when a watch companion app was in the mix.

Here is what I've added to the csproj:

<PropertyGroup>
    <WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/AwesomeWatchApp-dcttbatqxtdqwfaemnjjizxlqmsl/Build/Products</WatchAppBuildPath>
    <WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$(MSBuildProjectDirectory)/../AwesomeWatchApp/DerivedData/Build/Products</WatchAppBuildPath>
    <WatchAppBundle>AwesomeAppWatchApp WatchKit App.app</WatchAppBundle>
    <WatchAppConfiguration Condition=" '$(Platform)' == 'iPhoneSimulator' ">watchsimulator</WatchAppConfiguration>
    <WatchAppConfiguration Condition=" '$(Platform)' == 'iPhone' ">watchos</WatchAppConfiguration>
    <WatchAppBundleFullPath>$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
</PropertyGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Debug' AND Exists('$(WatchAppBundleFullPath)') ">
    <_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
    <_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
</ItemGroup>
<PropertyGroup Condition=" '$(_ResolvedWatchAppReferences)' != '' ">
    <CodesignExtraArgs>--deep</CodesignExtraArgs>
</PropertyGroup>
<Target Name="PrintWatchAppBundleStatus" BeforeTargets="Build">
    <Message Text="WatchAppBundleFullPath: '$(WatchAppBundleFullPath)' exists" Condition=" Exists('$(WatchAppBundleFullPath)') " />
    <Message Text="WatchAppBundleFullPath: '$(WatchAppBundleFullPath)' does NOT exist" Condition=" !Exists('$(WatchAppBundleFullPath)') " />
</Target>

I build the watch part in a Cake script, just before the iOS app:

#addin nuget:https://api.nuget.org/v3/index.json?package=Cake.Xcode&version=5.0.0

var configuration = Argument("configuration", "Release");
var outputDirArgument = Argument("outputDir", "./artifacts");
var outputDir = new DirectoryPath(outputDirArgument);

var iOSProj = new FilePath("./AwesomeApp.Ios/AwesomeApp.Ios.csproj");
var watchProj = new FilePath("./AwesomeWatchApp/AwesomeWatchApp.xcworkspace");

Task("BuildWatch")
    .Does(() =>
{   
    var derivedDataPath = watchProj.GetDirectory().Combine("DerivedData").FullPath;
    Information("DerivedDataPath {0}", derivedDataPath);
        
    var settings = new XCodeBuildSettings {
        Workspace = watchProj,
        Scheme = "AwesomeWatchApp WatchKit App",
        Sdk = "watchos",
        Configuration = configuration,
        DerivedDataPath = derivedDataPath,
        BuildSettings = new Dictionary<string, string>
        {
            { "MARKETING_VERSION", marketingVersion },
            { "CURRENT_PROJECT_VERSION", bundleVersion },
        }
    };

    XCodeBuild(settings);
});

Task("BuildiOS")
    ...
    .IsDependentOn("BuildWatch")
    .Does(() =>
{
    var settings = GetDefaultBuildSettings()
        .WithProperty("Version", versionInfo.SemVer)
        .WithProperty("PackageVersion", versionInfo.SemVer)
        .WithProperty("InformationalVersion", versionInfo.InformationalVersion)
        .WithProperty("BuildIpa", "True")
        .WithProperty("IpaPackageDir", outputDir.FullPath.ToString())
        .WithProperty("IpaPackageName", "archive.ipa")
        .WithProperty("Platform", "iPhone")
        .WithTarget("Build");

    MSBuild(iOSProj, settings);
});

@SprengerS
Copy link

@tougher thank you very much. I just tried to overwrite a Build Task but this not works. I will check your solution

@Csaba8472
Copy link

@tougher does the _ResolvedWatchAppReferences approach work for you in a .net6 project?

@tougher
Copy link

tougher commented Jun 10, 2022

I was looking into what would happen when we finally updated to net6 and ended up with this (untested) hack:

<PropertyGroup>
     <CreateAppBundleDependsOn>$(CreateAppBundleDependsOn);_CopyWatchOS2AppsToBundle</CreateAppBundleDependsOn>
</PropertyGroup>

That is just copying the watch app build to the Watch folder inside the iOS app bundle. I pretty sure there is more that needs to be done, see

@Csaba8472
Copy link

Thank you that helped a lot! I can confirm it works with some exception. In Debug config it's fine it works on both the simulator and on device. Personally, I commented out the part I didn't need when I run(watchsimulator or watchos) most probably, there is a better way to do that. This is great for debug because now when I run the ios project it doesn't delete the watch app, instead it installs the latest.

With Release config I had two issues:
1, it didn't copy dsyms in its folder(other than that I've seen all folders and files where is should be)
2, I had this issue:

ITMS-90496: Invalid Executable - The executable 'MSToDoMaui.app/Watch/watchApp.app/PlugIns/watchApp Extension.appex/watchApp Extension' does not contain bitcode.

however, the good old copy-paste method works so it's fine.

This is what I've ended up with:

	<PropertyGroup>
		<WatchAppBuildPath>/Users/csabahuszar/Projects/MSToDoKMMM/iosApp/DerivedData/iosApp/Build/Products</WatchAppBuildPath>
		<WatchAppBundle>watchApp.app</WatchAppBundle>
		<WatchAppConfiguration Condition=" '$(Configuration)' == 'Debug'">watchos</WatchAppConfiguration>
		<!--<WatchAppConfiguration Condition=" '$(Configuration)' == 'Debug'">watchsimulator</WatchAppConfiguration>-->
		<WatchAppBundleFullPath>$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
	</PropertyGroup>
	<ItemGroup Condition="Exists('$(WatchAppBundleFullPath)') ">
		<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
	</ItemGroup>
	<PropertyGroup Condition=" '$(_ResolvedWatchAppReferences)' != '' ">
		<CodesignExtraArgs>--deep</CodesignExtraArgs>
	</PropertyGroup>
	<Target Name="PrintWatchAppBundleStatus" BeforeTargets="Build">
		<Message Text="WatchAppBundleFullPath: '$(WatchAppBundleFullPath)' '$(TargetFramework)' '$(RuntimeIdentifier)' '$(Platform)' exists. " Condition=" Exists('$(WatchAppBundleFullPath)') " />
		<Message Text="WatchAppBundleFullPath: '$(WatchAppBundleFullPath)' '$(TargetFramework)' '$(RuntimeIdentifier)' '$(Platform)' does NOT exists. " Condition=" !Exists('$(WatchAppBundleFullPath)') " />
	</Target>
	<PropertyGroup>
		<CreateAppBundleDependsOn>$(CreateAppBundleDependsOn);_CopyWatchOS2AppsToBundle</CreateAppBundleDependsOn>
	</PropertyGroup>

@adam-russell
Copy link

adam-russell commented Jul 2, 2022

Just in case this helps anyone -- I also ran into the same error as @Csaba8472 about the watch extension not containing bitcode when it was submitted to TestFlight. Apparently, a release build does not actually trigger bitcode generation, but archiving does. You can also pass flags to xcodebuild to generate bitcode (see https://stackoverflow.com/questions/31486232/how-do-i-xcodebuild-a-static-library-with-bitcode-enabled). I'm building from AppCenter, and I have some shell scripts to build the Swift extensions and components with the xcodebuild.

So, the key for me was to build the watch target with the below command line:

xcodebuild BITCODE_GENERATION_MODE=bitcode OTHER_CFLAGS="-fembed-bitcode" -configuration Release -target [Watch Target Name Here]

After that and the csproj changes above, I was able to get the build submitted to TestFlight without any errors and without having to copy/paste anything. I haven't actually submitted the build to be live in the App Store, but the build itself seems fine so far.

@tougher
Copy link

tougher commented Jul 2, 2022

I'm building + archiving with cake and that seem to be enough:

Task("BuildWatch")
    .Does(() =>
{   
    var archivePath = watchProj.GetDirectory().Combine("WatchApp.xcarchive").FullPath;
    Information("ArchivePath {0}", archivePath);
        
    var settings = new XCodeBuildSettings {
       Workspace = watchProj,
       Scheme = "WatchApp WatchKit App",
       Sdk = "watchos",
       Configuration = configuration,
       ArchivePath = archivePath,
       Archive = true,
       BuildSettings = new Dictionary<string, string>
       {
           { "MARKETING_VERSION", marketingVersion },
           { "CURRENT_PROJECT_VERSION", bundleVersion },
       }
   };
   
   XCodeBuild(settings);
});

@fedemkr
Copy link

fedemkr commented Sep 22, 2022

First of all, thanks you all for this solution and just in case anyone faces the same issue, in order to build it in Github Actions I had to change the path to:

<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/MyAwesomeApp.xcarchive/Products/Applications/MyAwesomeApp.app/Watch</WatchAppBuildPath>

Given that we're archiving the XCode project in the watchOS folder in the project directory

@vouksh
Copy link

vouksh commented Nov 2, 2022

We're currently looking into porting our Xamarin-based WatchOS companion app to swift. I'm trying to follow along with some of your examples.

I created a new WatchOS project in Xcode 14, put it in the root of our repository, got the build paths setup so that it's easily reached from the Xamarin project. I modified the snippet shown here to match our app name(s), paths, etc.

However, when I tried to build the phone app (after building the Watch app), I get several build errors. I was able to correct some of them, but these last 3 have me a bit stumped.

OnWatch.app: [MT7014] The Watch App 'OnWatch' does not contain an Info.plist.
        
Info.plist: [MT7020] The Watch App 'OnWatch Watch App' has an invalid Info.plist: the WKWatchKitApp key must be present and have a value of 'true'.
        
OnWatch Watch App.app: [MT7022] The Watch App 'OnWatch Watch App' does not contain any Watch Extensions.

I'm not sure why it's saying that there's no Info.plist, then finds it? then says there's a problem with it.
And there's no extension, because I don't think you need an extension app with the switfUI-based WatchOS apps.

@wojciech-kulik
Copy link
Contributor Author

wojciech-kulik commented Nov 2, 2022

@vouksh I have found a universal solution for all Xamarin problems, I rewrote my app to native. It took 3 weeks and all problems are gone :). I had too many headaches caused by doing all those crazy things and hoping that nothing would break. Not to mention that after 6 months I had no idea what to do to release my app, because there were too many weird steps.

So I recommend this approach if you can do it within a reasonable time. Better to rewrite an app in 3 weeks than spend 3 weeks solving crazy problems

@vouksh
Copy link

vouksh commented Jan 4, 2023

@ivanicin Should be fixed now. Cloudflare broke SSL between them and my server, so I bypassed it for now. Thanks for letting me know. My monitoring tools didn't alert me because they bypassed Cloudflare.

vs-mobiletools-engineering-service2 pushed a commit to vs-mobiletools-engineering-service2/xamarin-macios that referenced this issue Feb 27, 2023
vs-mobiletools-engineering-service2 pushed a commit to vs-mobiletools-engineering-service2/xamarin-macios that referenced this issue Feb 27, 2023
rolfbjarne added a commit that referenced this issue Feb 28, 2023
…ative watchOS app (#17628)

Modified the FindWatchOS2AppBundleTaskBase and Xamarin.iOS.Common.targets so that it only tries to copy the WatchKit stub into the IPA file if the watch app bundle includes the folder.

This should fix the error that was found in #10070 by @ivanicin 

Backport of #17004

Co-authored-by: Jack Butler <[email protected]>
Co-authored-by: Jack Aardal <[email protected]>
Co-authored-by: Rolf Bjarne Kvinge <[email protected]>
rolfbjarne added a commit that referenced this issue Mar 3, 2023
…hOS app (#17626)

Modified the FindWatchOS2AppBundleTaskBase and Xamarin.iOS.Common.targets so that it only tries to copy the WatchKit stub into the IPA file if the watch app bundle includes the folder.

This should fix the error that was found in #10070 by @ivanicin 


Backport of #17004

Co-authored-by: Jack Butler <[email protected]>
Co-authored-by: Jack Aardal <[email protected]>
Co-authored-by: Rolf Bjarne Kvinge <[email protected]>
@jahmai-ca
Copy link

Do the steps discussed above apply to iOS projects that target net7.0-ios?
Is there any reason why I couldn't use a watch.app artifact produced by a Xamarin.WatchOS project instead of a native SwiftUI project?
I am looking for a way to avoid rewriting our watchOS app and include it in our recently migrated net7.0-ios app, at least for the time being.

@rolfbjarne
Copy link
Member

Is there any reason why I couldn't use a watch.app artifact produced by a Xamarin.WatchOS project instead of a native SwiftUI project?

AFAIK nobody has tried it, but I believe it should work, given that a watchOS app from a Xamarin.WatchOS project is self-contained.

@jahmai-ca
Copy link

Is there any reason why I couldn't use a watch.app artifact produced by a Xamarin.WatchOS project instead of a native SwiftUI project?

AFAIK nobody has tried it, but I believe it should work, given that a watchOS app from a Xamarin.WatchOS project is self-contained.

Great, I'll give it a try.

Do you have any suggestions for attempting this with a net7 SDK-style project? I see a few different methods above and none of them seem to necessarily be a perfect success.

Finally will net8 have any improved support for this, or maybe just document the methodology?

@rolfbjarne
Copy link
Member

Do you have any suggestions for attempting this with a net7 SDK-style project? I see a few different methods above and none of them seem to necessarily be a perfect success.

Unfortunately no.

Since I believe you're the first person to try a Xamarin.WatchOS project, it might be an idea to get it working locally with a dummy Xcode watch project first, and once that's working use the Xamarin.WatchOS app instead.

Finally will net8 have any improved support for this, or maybe just document the methodology?

It's unlikely we'll do anything beyond improved documentation.

We're however open to contributions, if you find something that would make it easier for you!

@vouksh
Copy link

vouksh commented Aug 4, 2023

@jahmai-ca

Do the steps discussed above apply to iOS projects that target net7.0-ios? Is there any reason why I couldn't use a watch.app artifact produced by a Xamarin.WatchOS project instead of a native SwiftUI project? I am looking for a way to avoid rewriting our watchOS app and include it in our recently migrated net7.0-ios app, at least for the time being.

I was able to get a native SwiftUI watchOS app bundled into a MAUI .NET 7 iOS app successfully. I've got a ton of projects lined up, but I'll try to take some time to document the steps. It should be essentially the same thing for a Xamarin-based app since you're just basically copying the watchOS .app bundle into the phone .app bundle.

I don't know if it will validate and pass Apple review yet, as we're nowhere near that stage, so YMMV.

@LaDzi
Copy link

LaDzi commented Aug 4, 2023

Would love to see a Documentation on native SwiftUI watchOS app bundled into a MAUI .NET 7 iOS app. Tried the one mentioned here before, but wasn't able to get this up and running.

@jahmai-ca
Copy link

@jahmai-ca

Do the steps discussed above apply to iOS projects that target net7.0-ios? Is there any reason why I couldn't use a watch.app artifact produced by a Xamarin.WatchOS project instead of a native SwiftUI project? I am looking for a way to avoid rewriting our watchOS app and include it in our recently migrated net7.0-ios app, at least for the time being.

I was able to get a native SwiftUI watchOS app bundled into a MAUI .NET 7 iOS app successfully. I've got a ton of projects lined up, but I'll try to take some time to document the steps. It should be essentially the same thing for a Xamarin-based app since you're just basically copying the watchOS .app bundle into the phone .app bundle.

I don't know if it will validate and pass Apple review yet, as we're nowhere near that stage, so YMMV.

That would be great! I am currently making an attempt, and so far just have it building successfully for simulator. Will report back on how far I get.

@vouksh
Copy link

vouksh commented Aug 4, 2023

@jahmai-ca @LaDzi
Until I get some actual documentation written, here's the gist of what you need to do. Create a "Watch" folder in your solution, and add something similar the following to your .csproj file. You'll obviously want to replace the paths and names with the path and name of your own watchOS .app bundle, but hopefully this will point you in the right direction.

	<ItemGroup Condition="'$(TargetFramework)'=='net7.0-ios'"> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\Maui.Watch Watch App"> 
        <Link>Watch\Maui.Watch Watch App.app\Maui.Watch Watch App</Link> 
      </BundleResource> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\embedded.mobileprovision"> 
        <Link>Watch\Maui.Watch Watch App.app\embedded.mobileprovision</Link> 
      </BundleResource> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\Info.plist"> 
        <Link>Watch\Maui.Watch Watch App.app\Info.plist</Link> 
      </BundleResource> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\PkgInfo"> 
        <Link>Watch\Maui.Watch Watch App.app\PkgInfo</Link> 
      </BundleResource> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\_CodeSignature\CodeResources"> 
        <Link>Watch\Maui.Watch Watch App.app\_CodeSignature\CodeResources</Link> 
      </BundleResource> 
    </ItemGroup>

If you're pushing to a simulator, you'll want to replace the "watchos" portion with "watchsimulator". Check the contents of the .app folder and make sure all of the files are listed and copied.

It's a PITA, but the BundleResource command doesn't accept wildcards. If I discover a better way, I'll post about it. But until then this will hopefully suffice.

@jawbrey
Copy link

jawbrey commented Aug 4, 2023

@vouksh thanks, +1000 for getting a detailed writeup or docs on how to do this. Lack of wearable support has been a big thorn for our leadership, being able to crack that would be a huge help for us.

@vouksh
Copy link

vouksh commented Aug 4, 2023

@jawbrey Right there with you. I work on a medical-oriented app for Parkinsons research, and we require a wearable device, typically a watch, to monitor motion data to feed to an algorithm. 90% of our app relies on the watch, with the phone app mostly being used to process and upload the data (it does some other things, but that's the primary function). That's why I've been spearheading a lot of this. I don't have a lot of choice but to make it work, haha.

@jahmai-ca
Copy link

@vouksh Thanks for the reply!

So I think my use-case might be different because I am using an externally built Xamarin.WatchOS app bundle rather than a native SwiftUI. In my case, I needed the net7.0-ios build process to sign my watch.app. I am not sure your BundleResource approach results in that happening (I didn't try though, I went down a separate rabbit hole before you replied ;-) ). Is your watch app already signed by using XCode archive or something?

At any rate, I was able to get it working with the following changes to our net7.0-ios apps .csproj file.

Notes:

  • Watch.app is being built separately by msbuild. This is done in our CI, or locally by a developer via another .sln file.
  • Watch.app project configurations are the same as the iPhone app project configurations (vanilla Debug/Release), but the iPhone app project uses the AnyCPU platform while the Watch.app project uses the iPhone/iPhoneSimulator platforms. Hence the use of RuntimeIdentifier to find the correct externally built bundle.
  • As I needed to modify the built-in build properties that were modified in the Xamarin .targets imports (after the end of the .csproj file), I had to change the way the SDK.props and SDK.targets were included, per https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2022
  • The RequireWatchApp/Exists condition is so that our CI will fail if the watch app isn't there, but a developer can optionally build without it if they want.
  • _CopyWatchOS2AppsToBundle was key to getting the watch app bundle copied into the phone application bundle.
  • _ResolvedWatchAppReferences was key to getting the embedded watch app bundle signed correctly.
<Project>
  <!-- https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk -->
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
  
  <!-- SNIP ... normal project stuff ... -->
  
  <!-- https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk -->
  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
  <!-- WATCH: Include the WatchApp if it has been built -->
  <PropertyGroup>
    <WatchAppPlatform>iPhone</WatchAppPlatform>
    <WatchAppPlatform Condition=" '$(RuntimeIdentifier)' == 'iossimulator-x64' ">iPhoneSimulator</WatchAppPlatform>
    <WatchApp>$(MSBuildProjectDirectory)\..\MyApp.Watch\bin\$(Configuration)\$(WatchAppPlatform)\MyAppWatch.app</WatchApp>
  </PropertyGroup>
  <ItemGroup Condition=" '$(RequireWatchApp)' == 'true' or Exists('$(WatchApp)') ">
    <_ResolvedWatchAppReferences Include="$(WatchApp)" Visible="false" />
  </ItemGroup>
  <PropertyGroup Condition=" '$(RequireWatchApp)' == 'true' or Exists('$(WatchApp)') ">
    <CreateAppBundleDependsOn>
      _CopyWatchOS2AppsToBundle;
      $(CreateAppBundleDependsOn);
    </CreateAppBundleDependsOn>
  </PropertyGroup>
  <!-- /WATCH -->
</Project>

I have successfully uploaded the IPA to AppStoreConnect without any validation issues, installed it with Testflight, and the watch app runs successfully on my Apple Watch series 4, but I have no yet submitted for review.

@LaDzi
Copy link

LaDzi commented Aug 12, 2023

@jahmai-ca @LaDzi Until I get some actual documentation written, here's the gist of what you need to do. Create a "Watch" folder in your solution, and add something similar the following to your .csproj file. You'll obviously want to replace the paths and names with the path and name of your own watchOS .app bundle, but hopefully this will point you in the right direction.

	<ItemGroup Condition="'$(TargetFramework)'=='net7.0-ios'"> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\Maui.Watch Watch App"> 
        <Link>Watch\Maui.Watch Watch App.app\Maui.Watch Watch App</Link> 
      </BundleResource> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\embedded.mobileprovision"> 
        <Link>Watch\Maui.Watch Watch App.app\embedded.mobileprovision</Link> 
      </BundleResource> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\Info.plist"> 
        <Link>Watch\Maui.Watch Watch App.app\Info.plist</Link> 
      </BundleResource> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\PkgInfo"> 
        <Link>Watch\Maui.Watch Watch App.app\PkgInfo</Link> 
      </BundleResource> 
      <BundleResource Include="..\WatchApps\Apple\Maui.Watch\DerivedData\Maui.Watch\Build\Products\$(Configuration)-watchos\Maui.Watch Watch App.app\_CodeSignature\CodeResources"> 
        <Link>Watch\Maui.Watch Watch App.app\_CodeSignature\CodeResources</Link> 
      </BundleResource> 
    </ItemGroup>

If you're pushing to a simulator, you'll want to replace the "watchos" portion with "watchsimulator". Check the contents of the .app folder and make sure all of the files are listed and copied.

It's a PITA, but the BundleResource command doesn't accept wildcards. If I discover a better way, I'll post about it. But until then this will hopefully suffice.

Thx for the explanation. Watch app on iphone simulator shows my app and tries to install it (wasn't showing before). Unfortunately I get an error (not enough space). Running my watch app from xcode works fine. Watch app in iphone simulator shows it's installed etc.

I'm completely new to all this. Do I need a developer account to go any further or to make those things work? In addition to the above, "embedded.mobileprovision" wasn't created either. Guess its because of the missing developer account.

Appreciate your time and replies, maybe I should start with a standalone maui app, instead of going all in 😅.

@vouksh
Copy link

vouksh commented Aug 12, 2023

Thx for the explanation. Watch app on iphone simulator shows my app and tries to install it (wasn't showing before). Unfortunately I get an error (not enough space). Running my watch app from xcode works fine. Watch app in iphone simulator shows it's installed etc.

I'm completely new to all this. Do I need a developer account to go any further or to make those things work? In addition to the above, "embedded.mobileprovision" wasn't created either. Guess its because of the missing developer account.

Appreciate your time and replies, maybe I should start with a standalone maui app, instead of going all in 😅.

I would definitely recommend making a standalone MAUI app first, then adding a watch component after you've got the basics. The underlying communication between watch and phone apps can be extremely fickle if you don't know what you're doing. Unless your app is highly dependent on the Watch app like ours is.

And you'd definitely want to get an Apple Developer account, as well as a physical iPhone (and Apple Watch), as the simulator isn't great for developing apps that "speak" to each other.

@LaDzi
Copy link

LaDzi commented Aug 12, 2023

Thx for the explanation. Watch app on iphone simulator shows my app and tries to install it (wasn't showing before). Unfortunately I get an error (not enough space). Running my watch app from xcode works fine. Watch app in iphone simulator shows it's installed etc.
I'm completely new to all this. Do I need a developer account to go any further or to make those things work? In addition to the above, "embedded.mobileprovision" wasn't created either. Guess its because of the missing developer account.
Appreciate your time and replies, maybe I should start with a standalone maui app, instead of going all in 😅.

I would definitely recommend making a standalone MAUI app first, then adding a watch component after you've got the basics. The underlying communication between watch and phone apps can be extremely fickle if you don't know what you're doing. Unless your app is highly dependent on the Watch app like ours is.

And you'd definitely want to get an Apple Developer account, as well as a physical iPhone (and Apple Watch), as the simulator isn't great for developing apps that "speak" to each other.

Yes I agree, better start off small and go step by step. Thx mate 😊. Feels good to know, that there is a chance to add the watch app later on. Was a motivation killer to even start for some time. Glad to get in touch with you. Happy Coding 😊!

@vouksh
Copy link

vouksh commented Aug 21, 2023

So for anyone wanting to bundle a native watchOS app into a MAUI app, ignore what I had posted above. Here's a slightly tweaked version of my Xamarin.iOS segment that triggers the app to be bundled into the package properly (and allows for proper signing)
Obviously adjust the paths to match your structure, but this is all that's needed. It's based off of @jahmai-ca's modifications, but without the CI-specific stuff, as this is just meant for building on the same machine.
I'm working on seeing if I can get a native Kotlin-based WearOS app to communicate with a MAUI phone app, but have been having no luck, but if I succeed, I'll post about it with instructions.

	<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios'">
		<WatchAppProjectName>Maui.Watch</WatchAppProjectName>
		<WatchAppPlatformType>watchos</WatchAppPlatformType>
		<WatchAppBuildPath>../WatchApps/Apple/$(WatchAppProjectName)/DerivedData/$(WatchAppProjectName)/Build/Products/$(Configuration)-$(WatchAppPlatformType)</WatchAppBuildPath>
		<WatchAppBundle>Maui.Watch Watch App.app</WatchAppBundle>
		<WatchAppBundleFullPath>$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
	</PropertyGroup>
	<ItemGroup Condition="'$(TargetFramework)'=='net8.0-ios'">
		<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
	</ItemGroup>
	<PropertyGroup Condition="'$(TargetFramework)'=='net8.0-ios'">
		<CreateAppBundleDependsOn>
			_CopyWatchOS2AppsToBundle;
			$(CreateAppBundleDependsOn);
		</CreateAppBundleDependsOn>
	</PropertyGroup>

@jawbrey
Copy link

jawbrey commented Aug 21, 2023

I would kill for a working sample solution of a MAUI app including a Watch app. Just sayin', if anyone has time to put one together.

@Csaba8472
Copy link

I would kill for a working sample solution of a MAUI app including a Watch app. Just sayin', if anyone has time to put one together.

this worked last year, and I'm not aware of anything that might have broken it.

@vouksh
Copy link

vouksh commented Sep 11, 2023

I would kill for a working sample solution of a MAUI app including a Watch app. Just sayin', if anyone has time to put one together.

@jawbrey I put together a semi-working example here: MauiWithWatchApps
The Apple Watch/iOS side works, but I haven't been able to get the Android/WearOS communication working yet. But it should provide a simple enough jumping-off point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement The issue or pull request is an enhancement iOS Issues affecting Xamarin.iOS watchOS
Projects
None yet
Development

No branches or pull requests