Module 4 - Importing UI from Figma

In the previous modules, you've learned how to create a UI using C# Markup and set up bindings to the underlying models that request data from data services.

In this module, you will learn how to import a C# Markup UI from Figma.
Figma is a collaborative app UI design tool that allows users to create, share, and comment on designs in real-time. It then allows exporting the UI in selected markup language for developers to carry on with the pre-designed app.

Uno Platform offers a Figma plugin that enables exporting the UI designed in Figma as both XAML and C# Markup.
In this module, you'll learn how to export C# Markup from a pre-existing Figma design for the Tube Player app, how to import it into the app you've started to create in the previous modules, and how to wire it up with the existing presentation model and the services it's interacting with.

Open the Figma file and set up an account

  1. Open the Tube Player Figma file via this link.

  2. Click Open in Figma

  3. If you are not signed in with Figma, you will be asked to do so. Create an account if you don't have one. Follow the instructions to sign up with your Google account or use a username and password.

    Figma sign-in page

  4. Follow the instructions. When you've finished you may see a screen similar to the following, click Continue to file.

    Figma sign-in complete

  5. You may be asked additional questions but feel free to click Skip those that offer this option.

    Figma questionnaire

  6. The Uno Tube Player design will open in Figma, displaying the Cover page:

    Figma Uno Tube Player cover

Install the Uno Platform plugin for Figma

  1. Open the Resources menu (Shift+I) and navigate to the Plugins tab:

    Figma plugins menu

  2. Search and install the Uno Platform (Figma to C# or XAML) plugin.

  1. Launch the plugin by clicking the Uno Platform (Figma to C# or XAML) plugin.

Enable navigation in the plugin

The plugin comes with various features and settings. As you will be using the Uno Navigation extension in the Tube Player app (in module 7), enable the Navigation feature in Figma, so that the appropriate navigation settings are included with the generated C# Markup.

Enabling navigation setting in the Uno Figma plugin

MainPage - Video feed

Export C# Markup from Figma using the Uno Platform plugin

  1. All pages designed in the Uno Tube Player design in Figma are listed in the left-side navigation bar. Select Tube Player screens (you may need to scroll down a bit), then select the 1.0 Video feed screen on the page or the submenu which will open at the bottom of the sidebar.
    This screen will be used to generate the C# Markup of the MainPage in TubePlayer app.

    Figma plugin default page

  2. If you closed the plugin you can open it by right-clicking the screen, then clicking PluginsDevelopmentUno Platform.

    Open Figma plugin from screen context menu

  3. Open the Export tab, select C# from the dropdown, then click Refresh (the circled arrow button on the bottom).

    Figma plugin export page

    Feel free to compare and examine the generated XAML and its C# Markup counterpart.

  4. Select all contents (Ctrl+A on Windows).

    Selected code in Visual Studio

    MainPage code contents (collapsed for brevity)
    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Media;
    using Microsoft.UI.Xaml.Media.Imaging;
    using Microsoft.UI.Xaml.Navigation;
    using System;
    using Uno.Extensions.Markup;
    using Uno.Extensions.Navigation.UI;
    using Uno.Material;
    using Uno.Toolkit.UI;
    
    namespace TubePlayer.Presentation;
    
    public partial class MainPage : Page
    {
        public MainPage()
        {
            this
                .Background(Theme.Brushes.Background.Default)
                .NavigationCacheMode(NavigationCacheMode.Required)
                .StatusBar
                (
                    s => s
                        .Foreground(StatusBarForegroundTheme.Auto)
                        .Background(Theme.Brushes.Surface.Default)
                )
                .Resources
                (
                    r => r
                        .Add("Icon_Chevron_Right", "F1 M 1.4099998474121094 0 L 0 1.4099998474121094 L 4.579999923706055 6 L 0 10.59000015258789 L 1.4099998474121094 12 L 7.409999847412109 6 L 1.4099998474121094 0 Z")
                        .Add("Icon_Search", "F1 M 12.5 11 L 11.710000038146973 11 L 11.430000305175781 10.729999542236328 C 12.410000324249268 9.589999556541443 13 8.110000014305115 13 6.5 C 13 2.9100000858306885 10.089999914169312 0 6.5 0 C 2.9100000858306885 0 0 2.9100000858306885 0 6.5 C 0 10.089999914169312 2.9100000858306885 13 6.5 13 C 8.110000014305115 13 9.589999556541443 12.410000324249268 10.729999542236328 11.430000305175781 L 11 11.710000038146973 L 11 12.5 L 16 17.489999771118164 L 17.489999771118164 16 L 12.5 11 L 12.5 11 Z M 6.5 11 C 4.009999990463257 11 2 8.990000009536743 2 6.5 C 2 4.009999990463257 4.009999990463257 2 6.5 2 C 8.990000009536743 2 11 4.009999990463257 11 6.5 C 11 8.990000009536743 8.990000009536743 11 6.5 11 Z")
                )
                .Content
                (
                    new AutoLayout()
                        .PrimaryAxisAlignment(AutoLayoutAlignment.Center)
                        .VerticalAlignment(VerticalAlignment.Stretch)
                        .HorizontalAlignment(HorizontalAlignment.Center)
                        .Width(400)
                        .Children
                        (
                            new NavigationBar()
                                .Width(400)
                                .AutoLayout(counterAlignment: AutoLayoutAlignment.Center)
                                .Content
                                (
                                    new AutoLayout()
                                        .PrimaryAxisAlignment(AutoLayoutAlignment.Center)
                                        .Orientation(Orientation.Horizontal)
                                        .Children
                                        (
                                            new Image()
                                                .Source(new BitmapImage(new Uri("https://picsum.photos/384/40")))
                                                .Stretch(Stretch.UniformToFill)
                                                .AutoLayout(primaryAlignment: AutoLayoutPrimaryAlignment.Stretch)
                                        )
                                ),
                            new AutoLayout()
                                .Background(Theme.Brushes.Surface.Default)
                                .Padding(12)
                                .Children
                                (
                                    new TextBox()
                                        .Background(Theme.Brushes.Surface.Variant.Default)
                                        .Text(b => b.Binding("SearchTerm").TwoWay())
                                        .PlaceholderText("Search")
                                        .CornerRadius(20)
                                        .BorderThickness(0)
                                        .Style(Theme.TextBox.Styles.Outlined)
                                        .ControlExtensions
                                        (
                                            icon:
                                                new PathIcon()
                                                    .Data(StaticResource.Get<Geometry>("Icon_Search"))
                                                    .Foreground(Theme.Brushes.OnSurface.Variant.Default)
                                        )
                                ),
                            new ListView()
                                .Background(Theme.Brushes.Background.Default)
                                .ItemsSource(b => b.Binding("VideoSearchResults"))
                                .Padding(12, 8)
                                .Navigation(request: "VideoDetails")
                                .AutoLayout(primaryAlignment: AutoLayoutPrimaryAlignment.Stretch)
                                .ItemTemplate
                                (
                                    () =>
                                        new CardContentControl()
                                            .Margin(0, 0, 0, 8)
                                            .Style(StaticResource.Get<Style>("ElevatedCardContentControlStyle"))
                                            .Content
                                            (
                                                new AutoLayout()
                                                    .Background(Theme.Brushes.Surface.Default)
                                                    .CornerRadius(12)
                                                    .PrimaryAxisAlignment(AutoLayoutAlignment.Center)
                                                    .Children
                                                    (
                                                        new AutoLayout()
                                                            .Background(Theme.Brushes.Surface.Default)
                                                            .CornerRadius(12)
                                                            .Padding(8, 8, 8, 0)
                                                            .MaxHeight(288)
                                                            .MaxWidth(456)
                                                            .AutoLayout(counterAlignment: AutoLayoutAlignment.Center)
                                                            .Children
                                                            (
                                                                new Border()
                                                                    .Height(204.75)
                                                                    .CornerRadius(6)
                                                                    .Child
                                                                    (
                                                                        new Image()
                                                                            .Source(b => b.Binding("Details.Snippet.Thumbnails.Medium.Url"))
                                                                            .Stretch(Stretch.UniformToFill)
                                                                    ),
                                                                new AutoLayout()
                                                                    .Spacing(8)
                                                                    .Orientation(Orientation.Horizontal)
                                                                    .Padding(0, 8)
                                                                    .Children
                                                                    (
                                                                        new Border()
                                                                            .Width(60)
                                                                            .Height(60)
                                                                            .CornerRadius(6)
                                                                            .AutoLayout(counterAlignment: AutoLayoutAlignment.Center)
                                                                            .Child
                                                                            (
                                                                                new Image()
                                                                                    .Source(b => b.Binding("Channel.Snippet.Thumbnails.Medium.Url"))
                                                                                    .Stretch(Stretch.UniformToFill)
                                                                            ),
                                                                        new AutoLayout()
                                                                            .PrimaryAxisAlignment(AutoLayoutAlignment.Center)
                                                                            .AutoLayout(primaryAlignment: AutoLayoutPrimaryAlignment.Stretch)
                                                                            .Children
                                                                            (
                                                                                new TextBlock()
                                                                                    .Text(b => b.Binding("Channel.Snippet.Title"))
                                                                                    .Height(22)
                                                                                    .Foreground(Theme.Brushes.OnSurface.Default)
                                                                                    .Style(Theme.TextBlock.Styles.TitleMedium),
                                                                                new TextBlock()
                                                                                    .Text(b => b.Binding("Details.Snippet.Title"))
                                                                                    .Height(16)
                                                                                    .Foreground(Theme.Brushes.OnSurface.Medium)
                                                                            ),
                                                                        new Button()
                                                                            .Foreground(Theme.Brushes.OnSurface.Variant.Default)
                                                                            .Style(Theme.Button.Styles.Icon)
                                                                            .AutoLayout(counterAlignment: AutoLayoutAlignment.Center)
                                                                            .Content
                                                                            (
                                                                                new PathIcon()
                                                                                    .Data(StaticResource.Get<Geometry>("Icon_Chevron_Right"))
                                                                                    .Foreground(Theme.Brushes.OnSurface.Variant.Default)
                                                                            )
                                                                    )
                                                            )
                                                    )
                                            )
                                )
                        )
                )
                ;
        }
    }
    
  5. Copy the selected code to the clipboard (Ctrl+C on Windows).

Import C# Markup to the Tube Player project

  1. Open MainPage.cs and replace all contents with the copied code.

  2. Wrap the code with the DataContext extension method yet again (remember to add a closing parenthesis to the end):

     public sealed partial class MainPage : Page
     {
         public MainPage()
         {
    +        this.DataContext<MainViewModel>((page, vm) => page
                 .Background(ThemeResource.Get<Brush>("BackgroundBrush"))
    
                    ...
    +        ));
         }
     }
    
  3. In the following steps you'll replace the loosely-typed bindings generated by Figma with strongly-typed ones. For example, instead of .ItemsSource(b => b.Binding("VideoSearchResults")), we'll use .ItemsSource(() => vm.VideoSearchResults), where vm refers to the vm argument provided by the DataContext extension in the code you've just added in the previous step. By the way, we plan to introduce strongly-typed bindings in the Figma plugin, so stay tuned!

  4. Replace the binding of the search-term TextBlock, in the following manner:

    -.Text(b => b.Binding("SearchTerm").TwoWay())
    +.Text(b => b.Binding(() => vm.SearchTerm).TwoWay().UpdateSourceTrigger(UpdateSourceTrigger.PropertyChanged))
    
  5. Replace the binding of the ListView in the following manner:

    -.ItemsSource(b => b.Binding("VideoSearchResults"))
    +.ItemsSource(() => vm.VideoSearchResults)
    
  6. Set the ItemTemplate extension method to a strongly typed one by providing the YoutubeVideo type argument:

    Update this:

    -.ItemTemplate
    -(
    -    () =>
             new CardContentControl()
             ...
    

    With the following:

    +.ItemTemplate<YoutubeVideo>
    +(
    +    youtubeVideo =>
             new CardContentControl()
    
  7. Replace the other bindings either manually or using a regular-expression based find and replace. You'll also use this method to replace the loosely-typed bindings in the VideoDetailsPage as well. To replace them using a regex search using Visual Studio or Visual Studio Code, press Ctrl+H, then toggle the Use regular expressions option on (Alt+E). Search for the following pattern:

    b\s+\=\>\s+b\.Bind\(\"([\w\.]+)\"\)
    

    And replace it with the following:

    () => youtubeVideo.$1
    
  8. Replace all occurrences in the current MainPage.cs file (Alt+A):

    Visual Studio find and replace pane

  9. To avoid the nullability errors, either add null-conditional operators or null-forgiving operators after each nullable property access, or add the following to the beginning of the file (see nullable references), for example:

    -.Source(() => youtubeVideo.Details.Snippet.Thumbnails.Medium.Url)
    +.Source(() => youtubeVideo.Details.Snippet?.Thumbnails?.Medium?.Url!)
    

    or

    #nullable disable
    

Video detail

Export from Figma

  1. Go back to Figma and select the Video detail screen.

  2. If the Uno Platform plugin has closed, press Ctrl+Alt+P to reopen the plugin, or right-click the Video detail screen and select the plugin from the Plugins submenu.

  3. Open the Export tab, select C# from the dropdown menu, then click Refresh.

  4. Select and copy all code to the clipboard.

    Video detail code contents (collapsed for brevity)
    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Media;
    using Microsoft.UI.Xaml.Navigation;
    using System;
    using Uno.Extensions.Markup;
    using Uno.Toolkit.UI;
    
    namespace TubePlayer.Presentation;
    
    public partial class VideoDetailsPage : Page
    {
        public VideoDetailsPage()
        {
            this
                .Background(Theme.Brushes.Background.Default)
                .NavigationCacheMode(NavigationCacheMode.Required)
                .StatusBar
                (
                    s => s
                        .Foreground(StatusBarForegroundTheme.Auto)
                        .Background(Theme.Brushes.Surface.Default)
                )
                .Resources
                (
                    r => r
                        .Add("Icon_Arrow_Back", "F1 M 16 7 L 3.8299999237060547 7 L 9.420000076293945 1.4099998474121094 L 8 0 L 0 8 L 8 16 L 9.40999984741211 14.59000015258789 L 3.8299999237060547 9 L 16 9 L 16 7 Z")
                )
                .Content
                (
                    new AutoLayout()
                        .Background(Theme.Brushes.Background.Default)
                        .Children
                        (
                            new AutoLayout()
                                .Width(400)
                                .AutoLayout
                                (
                                    counterAlignment: AutoLayoutAlignment.Center,
                                    primaryAlignment: AutoLayoutPrimaryAlignment.Stretch
                                )
                                .Children
                                (
                                    new NavigationBar()
                                        .HorizontalContentAlignment(HorizontalAlignment.Left)
                                        .Content("Video")
                                        .MainCommand
                                        (
                                            new AppBarButton()
                                                .Icon
                                                (
                                                    new PathIcon()
                                                        .Data(StaticResource.Get<Geometry>("Icon_Arrow_Back"))
                                                        .Foreground(Theme.Brushes.OnSurface.Default)
                                                )
                                        ),
                                    new MediaPlayerElement()
                                        .AreTransportControlsEnabled(true)
                                        .Width(400)
                                        .Height(300)
                                        .AutoLayout(counterAlignment: AutoLayoutAlignment.Start)
                                        .TransportControls
                                        (
                                            new MediaTransportControls()
                                                .IsCompact(true)
                                        ),
                                    new ScrollViewer()
                                        .AutoLayout(primaryAlignment: AutoLayoutPrimaryAlignment.Stretch)
                                        .Content
                                        (
                                            new AutoLayout()
                                                .Children
                                                (
                                                    new AutoLayout()
                                                        .Spacing(6)
                                                        .Padding(16)
                                                        .Width(400)
                                                        .AutoLayout(counterAlignment: AutoLayoutAlignment.Start)
                                                        .Children
                                                        (
                                                            new TextBlock()
                                                                .TextWrapping(TextWrapping.Wrap)
                                                                .Text(b => b.Binding("Video.Channel.Snippet.Title"))
                                                                .Foreground(Theme.Brushes.OnSurface.Default)
                                                                .Style(Theme.TextBlock.Styles.TitleLarge),
                                                            new TextBlock()
                                                                .Text(b => b.Binding("Video.FormattedStatistics"))
                                                                .Foreground(Theme.Brushes.OnSurface.Medium)
                                                                .AutoLayout(counterAlignment: AutoLayoutAlignment.Start)
                                                        ),
                                                    new AutoLayout()
                                                        .Spacing(8)
                                                        .Orientation(Orientation.Horizontal)
                                                        .Padding(16, 8)
                                                        .Width(400)
                                                        .AutoLayout(counterAlignment: AutoLayoutAlignment.Start)
                                                        .Children
                                                        (
                                                            new Border()
                                                                .Width(40)
                                                                .Height(40)
                                                                .CornerRadius(20)
                                                                .AutoLayout(counterAlignment: AutoLayoutAlignment.Center)
                                                                .Child
                                                                (
                                                                    new Image()
                                                                        .Source(b => b.Binding("Video.Channel.Snippet.Thumbnails.High.Url"))
                                                                        .Stretch(Stretch.UniformToFill)
                                                                ),
                                                            new AutoLayout()
                                                                .Spacing(2)
                                                                .PrimaryAxisAlignment(AutoLayoutAlignment.Center)
                                                                .Height(37)
                                                                .AutoLayout
                                                                (
                                                                    counterAlignment: AutoLayoutAlignment.Center,
                                                                    primaryAlignment: AutoLayoutPrimaryAlignment.Stretch
                                                                )
                                                                .Children
                                                                (
                                                                    new AutoLayout()
                                                                        .Orientation(Orientation.Horizontal)
                                                                        .AutoLayout
                                                                        (
                                                                            counterAlignment: AutoLayoutAlignment.Start,
                                                                            primaryAlignment: AutoLayoutPrimaryAlignment.Stretch
                                                                        )
                                                                        .Children
                                                                        (
                                                                            new TextBlock()
                                                                                .Text(b => b.Binding("Video.FormattedSubscriberCount"))
                                                                                .Foreground(Theme.Brushes.OnSurface.Medium)
                                                                                .AutoLayout(counterAlignment: AutoLayoutAlignment.Start)
                                                                        ),
                                                                    new TextBlock()
                                                                        .Text(b => b.Binding("Video.Channel.Snippet.Title"))
                                                                        .Foreground(Theme.Brushes.OnSurface.Default)
                                                                        .Style(Theme.TextBlock.Styles.TitleMedium)
                                                                        .AutoLayout(counterAlignment: AutoLayoutAlignment.Start)
                                                                )
                                                        ),
                                                    new TextBlock()
                                                        .TextWrapping(TextWrapping.Wrap)
                                                        .Text(b => b.Binding("Video.Channel.Snippet.Description"))
                                                        .Margin(16)
                                                        .Foreground(Theme.Brushes.OnSurface.Variant.Default)
                                                        .Style(Theme.TextBlock.Styles.BodySmall)
                                                )
                                        )
                                )
                        )
                )
                ;
        }
    }
    
  5. Keep the Figma Uno Platform plugin window open, as you will access it in the upcoming module to download color theme overrides from it.

Import to Tube Player project

  1. Open the VideoDetailsPage.cs file, and replace it with the code in the clipboard.

  2. Append a DataContext after this.:

    .DataContext<VideoDetailsViewModel>((page, vm) => page
    

    then add the closing parenthesis before the semicolon closing this method.

  3. Update the bindings with strongly typed bindings as before. To utilize Find and replace, use the following regular-expressions. Search for this pattern:

    b\s+\=\>\s+b\.Bind\(\"([\w\.]+)\"\)
    

    and replace it with the following:

    () => vm.$1
    
  4. Update the bindings here as well to avoid the nullability errors, either add nullability operators or disable nullable reference types, for example:

    -.Source(() => vm.Video.Channel.Snippet.Thumbnails.High.Url)
    +.Source(() => vm.Video.Channel.Snippet?.Thumbnails?.High?.Url!)
    

    or

    #nullable disable
    

Run the app

Run the app (F5 on Visual Studio) and observe the UI changes, it should look similar to the following:

UI output of the first page

If you try tapping one of the videos in the list, an exception will occur. This is because navigation has not yet been implemented. You will address that in Module 7 - Navigation.
The image above the search page is a random image. It will be replaced in Module 11 - App finalization

Next Step

Module 5 offers an alternative way to import the UI code without Figma. Since you've already imported the UI, you may skip to Module 6, where you will adjust the UI you've imported by overriding the app's color theme.

Previous | Next