Develop & Publish .NET MAUI Application for Mac using Visual Studio Code
Tags: dotnet, maui, mac, publish
Published: 2023-12-01 12:39:08

Hello Developers,
  
=> So today we are developing a basic app using .NET maui framework & we'll publish it for the Mac platform.

=> .NET MAUI, which stands for .NET Multi-platform App UI, is an open-source, cross-platform framework for building native mobile and desktop applications. It is an evolution of the Xamarin.Forms framework and is part of the broader .NET ecosystem.

Before we start our work , let's install required tools.

**Step - 1 : Installation task**

 - [dotnet SDK (Recommend : SDK 7.0)](https://dotnet.microsoft.com/en-us/download) 
 - [dotnet Runtime (Recommend : 7.0)](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) 
 - [Visual Studio Code](https://code.visualstudio.com/download)
 - [XCode] (https://xcodereleases.com/)

-> Add required Extensions in vs code
 - C# (by Microsoft)
 - .NET Runtime Install Tool (by Microsoft)

-> after that open settings & search Omnisharp , make sure that 
   "Use Omnisharp" checkbox is enabled.

![omnisharp](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjx6zffof158cu5lr9eu.png)

-> check above tools are installed successfully or not by following commands from terminal.
 > dotnet --list-sdks
 > dotnet  --list-runtimes

![workload](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68mlqaqa05z9rf05i3ce.png)

-> Check whether maui workload is exist not.
 > dotnet workload list

![workload](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lj6pxbew5ojymjqheyi.png)

-> List all available workloads. 
 > dotnet workload search

![workload](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rip0f45zgh7f4segwl1a.png)

-> Install maui workload
 > dotnet workload install maui

-> Check installed workload
 > dotnet workload list 

![workload](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61hnnwvcg9q1i1h9i7pg.png)

**Step - 2 : Create Maui Project** 

-> Create Maui Template.
> dotnet new maui  

![maui template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4n6l0uwvkf9tonglrkue.png)

-> open the [project_name].csproj file & perform following acions
 
> remove net7.0-android 
> comment windows platform tag

![csproj file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/av3v53slxfhu77h60c0v.png)

-> perform restore & build. 

> dotnet restore (add nuget packages,mentioned in .csproj file)

> dotnet build

![dotnet build](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vnu7qvhiwa42iln6a16v.png)

-> You may encounter the following error. You can either upgrade the Xcode version or add the following tags in the .csproj file.

![Error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4gi1k1q09gai17wlgmj6.png)

```
<PropertyGroup>
     <ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
        <MtouchLink>SdkOnly</MtouchLink>
        <GenerateRuntimeConfiguratonFiles>true</GenerateRuntimeConfiguratonFiles>
    </PropertyGroup>
```

> dotnet build

![build](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a1a2jicndd6h8didqfr9.png)

**Step - 3 : Let's do some code**

=> Add fonts :

-> Add OpenSans-Regular.ttf & OpenSans-Semibold.ttf  fonts file in the Resources/Fonts folder. (check out my GitHub repo for required resources : [Repo](https://github.com/JayMalli/groot_app_maui_mac_publish))  

![fonts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/suv85nt7nh42s745itws.png)

=> AppShell.xaml : 

```
// remove title from header
<ShellContent
    ContentTemplate="{DataTemplate local:MainPage}"
    Route="MainPage"
    Shell.NavBarIsVisible="False" 
/>
```

=> MainPage.xaml

```
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Groot_App_Maui.MainPage">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

            <Image
                Source="groot_img.jpg" // Add this image in resources folder
                SemanticProperties.Description="Cute groot waving hi to you!"
                HeightRequest="400"
                HorizontalOptions="Center" />

            <Label
                Text="I am Groot!"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" />
           
              <Button
                FontAttributes="Bold"  
                Text="Click Me"  
                FontSize="{OnPlatform WinUI=16, MacCatalyst=18}"
                HorizontalOptions="Center"
                Clicked="OnClickBtn"  
             /> 

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

```
=> Add below image in Resources/Images folder.

![resources](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/peq95ei1waiqx2pm7xj5.png)
 
=> MainPage.xaml.cs :

```
// remove unnecessary code

namespace Groot_App_Maui;

public partial class MainPage : ContentPage
{

	public MainPage()
	{
		InitializeComponent();
	}

        public async void OnClickBtn(object sender, EventArgs e)
	{
		await 
 Application.Current.MainPage.DisplayAlert(null, "Again I am Groot :)", "OK");
	} 
}

```

-> Add icon image in Resources/AppIcon folder

![icon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xalfkte5afi4sx1bezg9.png)

=> [project_name].csproj :

```
// other code
<ItemGroup>
		<!-- App Icon -->
		<MauiIcon Include="Resources\AppIcon\app_icon.png"/>
</ItemGroup>
```

![icon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/37phmm5vxtkz5ra1a24o.png)

-> modify icon name in /Platforms/MacCataslyst/Info.plist file
 
```
	<key>XSAppIconAssets</key>
	<string>Assets.xcassets/groot_app_icon.appiconset</string>
```
-> modify icon name in /Platforms/iOS/Info.plist file

```
<key>XSAppIconAssets</key>
<string>Assets.xcassets/groot_app_icon.appiconset</string>
```

-> Modify Properties/launchSettings.json file

```
{
  "profiles": {
    "Windows (Debug)": {
      "commandName": "Project",
      "commandLineArgs": "--device windows --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Windows (Release)": {
      "commandName": "Project",
      "commandLineArgs": "--device windows --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    },
    "Mac (Debug)": {
      "commandName": "Project",
      "commandLineArgs": "--device macos --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Mac (Release)": {
      "commandName": "Project",
      "commandLineArgs": "--device macos --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    }
  }
}
```


**Step - 4 : lets run our app**

> dotnet build
> dotnet run -f net7.0-maccatalyst (from .csproj file)

-> yaahh!!!

![o/p image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dcaqc71ca6m1r6m628xr.png)

**Step - 5 : Its time to publish our app**

-> We'll create a pkg for our app.

-> Modify bundleId in .csproj file
`<ApplicationId>com.companyname.groot_app_maui</ApplicationId>`

-> Modify Info.plist file for mac & ios

```
<key>CFBundleIdentifier</key>
    <string>com.companyname.groot_app_maui</string>
<key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>NSHumanReadableCopyright</key>
    <string>Groot © 2023</string>
    <key>ITSAppUsesNonExemptEncryption</key>
    <false/>
    <key>NSDownloadsUsageDescription</key>
    <string>We need access to your Downloads folder to do XYZ.</string>
	
```

-> Create Entitlements.plist file Platforms/mac & add below code

```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.network.client</key>
    <true/>
    <key>com.apple.security.cs.allow-jit</key>
    <true/><!-- Specify permissions for accessing user's location -->
    <key>com.apple.security.personal-information.location</key>
    <true/><!-- Specify permissions for accessing user's contacts -->
    <key>com.apple.security.personal-information.contacts</key>
    <true/><!-- Specify permissions for accessing user's photos -->
    <key>com.apple.security.assets.photos.read-write</key>
    <true/>
  </dict>
</plist>

```

=> Publish app without signed certificate

> dotnet publish -f net7.0-maccatalyst -c Release -r maccatalyst-x64

-> If you encounter this error in the production app,

> Attempting to JIT compile method '(wrapper delegate-invoke) Google.Apis.Drive.v3.Data.User <Module>:invoke_callvirt_User_About (Google.Apis.Drive.v3.Data.About)' while running in aot-only mode. See https://docs.microsoft.com/xamarin/ios/internals/limitations for more information.

then add UseInterpreter tag in the csproj file.

```
<PropertyGroup>
     <ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
        <MtouchLink>SdkOnly</MtouchLink>
        <UseInterpreter>true</UseInterpreter><GenerateRuntimeConfiguratonFiles>true</GenerateRuntimeConfiguratonFiles>
    </PropertyGroup>
```

![pkg path](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1rkgfendm2t770bzqmh.png)

-> Open the terminal and execute the following command to monitor the logs of the app while the installation task is currently executing.

> tail -f /private/var/log/install.log

-> Additionally, open the Console and click on the start button to monitor logs of our app and view crash reports if the app crashes.

-> If you encounter this error in the console after installing the app from the app package and your published app is not working, then ignore it. There is no reason to believe that the app is not working because of this error. I also got this error in my console, & still app is working. :) 

> Non-fatal error enumerating at <private>, continuing: Error Domain=NSCocoaErrorDomain Code=260 "The file “PlugIns” couldn’t be opened because there is no such file." UserInfo={NSURL=PlugIns/ -- app_path/Contents/, NSFilePath=/Users/mufaddalbharmal/Desktop/app_name.app/Contents/PlugIns, NSUnderlyingError=0x7f7d61708ad0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}

![app installer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9szxv6ikjdgomblduxq.png)

-> You can see in the yellow-highlighted part that there is no sign of a lock, which means our app is not verified.

=> Create Signed Certificate :

-> First, we need to create a signed certificate. Follow the steps outlined in the provided documentation on creating signed certificates for your app: [Docs for certificate](https://learn.microsoft.com/en-us/dotnet/maui/mac-catalyst/deployment/publish-outside-app-store?view=net-maui-8.0)

-> Now, publish our app with the signature created by the certificate. 

> dotnet publish -f net7.0-maccatalyst -c Release -p:MtouchLink=SdkOnly -p:CreatePackage=true -p:EnableCodeSigning=true -p:EnablePackageSigning=true -p:CodesignKey="your_certificate_codesignkey" -p:CodesignProvision="DSR_DevID" -p:CodesignEntitlements="Platforms\MacCatalyst\Entitlements.plist" -p:PackageSigningKey="your_certificate_codesignkey" -p:UseHardenedRuntime=true

where ,

CodesignKey : The name of the code signing key. Set to the name of your distribution certificate, as displayed in Keychain Access. 
e.g.CodesignKey="Developer ID Application: *** (***)"

PackageSigningKey : same as CodesignKey

CodesignProvision :The provisioning profile to use when signing the app bundle.

-> Now you can see there is a lock at the top right corner, indicating that our app is verified and trusted. 

![app installer with lock](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gf8ldi7umi9151ankzto.png)

-> Yaah!!! our production app ready!

![app icon in menu bar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/600y6woki2lab2ibzkns.png)


![app demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qy11u43j47po57wyxzri.png)


=> The example above demonstrates how to develop & publish .NET maui app for Mac platform , you can find the necessary code in the repository mentioned below.

=> [GithHub Repository](https://github.com/JayMalli/groot_app_maui_mac_publish)

Thank you for joining me on this journey of discovery and learning. If you found this blog post valuable and would like to connect further, I'd love to connect with you on [LinkedIn](https://www.linkedin.com/in/jaymalli). You can find me at LinkedIn

If you have thoughts, questions, or experiences related to this topic, please drop a comment below.