iOS/Android/Windows/Web を構築するためプラットフォーム Uno Platform を試してみた

# Uno Platform とは

UWPベースのコード（C＃およびXAML）を書くだけでタイトルにある4プラットフォームで実行できるプラットフォームです。(iOS,Android,WebAssembly)

https://platform.uno/docs/articles/intro.html

# 環境準備

- Visual Studio 2019

以下のワークロードのインストール

- ユニバーサルWindowsプラットフォーム
- .NET デスクトップ開発 
- .NET によるモバイル開発 
- ASP.NET と Web 開発 

# 実装

手順は公式と同じように進むので英語ですがこちらでも参照できます。
https://platform.uno/docs/articles/getting-started-tutorial-1.html

## 拡張機能のインストール

拡張機能→拡張機能の管理から 「Uno Platform Solution Templates」を追加し、 vs を再起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183354/cb080699-9d2b-fbb5-5715-04d6f4367903.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183354/c02d0065-0e7e-ddcb-9d2e-c360c7051bfd.png)

## プロジェクトの作成

拡張機能追加後にプロジェクトを作成すると、 Un Platform のテンプレートが追加されていますのでそちらを使用します。今回はアプリケーションを作成するので App を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183354/3eca0ea6-0a42-cf3d-d782-0f93bfa4bda4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183354/5a77a5e1-cebc-6cc9-df91-2a291b539f8b.png)

## Nuget のアップデートと追加
ソリューションを選択し以下の3つの nuget をそれぞれ最新にバージョンアップさせます。

- Uno.Core
- Uno.UI
- Uno.Wasm.Bootstrap

ただし、Microsoft.Extensions.Logging.Console は**更新しない** ようにしてください。

同じくソリューションを選択し以下の Nuget を追加します。

```
Refractored.MvvmHelpers
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183354/7d063d66-1a7f-e27e-b759-2ae90fe8e250.png)


## フォルダとクラスの作成

プロジェクト名.Shared の中に Models フォルダを作成し、IssueItem.cs を作成します。
同じくプロジェクト名.Shared の中に Converts フォルダを作成し、 StringFormatConverter.cs を作成します。
作成後が以下の画像です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183354/d08d0bfe-24f5-22dc-cba3-98d3019d0e79.png)

```csharp:IssueItem.cs
using System;
using MvvmHelpers;

namespace UnoSampleApp.Shared.Models
{
    public class IssueItem : ObservableObject
    {
        private int id;
        public int Id
        {
            get => id;
            set => SetProperty(ref id, value);
        }

        private IssueType type;
        public IssueType Type
        {
            get => type;
            set => SetProperty(ref type, value);
        }

        private string title;
        public string Title
        {
            get => title;
            set => SetProperty(ref title, value);
        }

        private string description;
        public string Description
        {
            get => description;
            set => SetProperty(ref description, value);
        }

        private IssueStatus status;
        public IssueStatus Status
        {
            get => status;
            set => SetProperty(ref status, value);
        }

        private int effort;
        public int Effort
        {
            get => effort;
            set => SetProperty(ref effort, value);
        }

        private DateTimeOffset createdAt = DateTimeOffset.Now.ToLocalTime();
        public DateTimeOffset CreatedAt
        {
            get => createdAt;
            set => SetProperty(ref createdAt, value);
        }

        private DateTimeOffset? startedAt;
        public DateTimeOffset? StartedAt
        {
            get => startedAt;
            set => SetProperty(ref startedAt, value);
        }

        private DateTimeOffset? completedAt;
        public DateTimeOffset? CompletedAt
        {
            get => completedAt;
            set => SetProperty(ref completedAt, value);
        }
    }

    public enum IssueType
    {
        Bug,
        Issue,
        Task,
        Feature
    }

    public enum IssueStatus
    {
        Icebox,
        Planned,
        WIP,
        Done,
        Removed
    }
}

```

```csharp:StringFormatConverter.cs
using System;
using Windows.UI.Xaml.Data;

namespace UnoSampleApp.Shared.Converters
{
    public class StringFormatConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            return string.Format(parameter.ToString(), value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
}

```


## MainPage.xaml の変更

以下の通りです。

```xml:MainPage.xaml
<Page
    x:Class="UnoSampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UnoSampleApp"
    xmlns:converters="using:UnoSampleApp.Shared.Converters"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ios="http://nventive.com/ios"
    mc:Ignorable="d ios">
    <Page.Resources>
        <converters:StringFormatConverter x:Key="StringFormatConverter" />
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" RowSpacing="8">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" Orientation="Horizontal" Background="LightGray" Padding="5">
            <Canvas Background="Blue" Width="10" x:Name="IssueTypeIndicator" />
            <TextBlock Text="{x:Bind Item.Id}" Margin="6,0" VerticalAlignment="Center" />
            <ComboBox x:Name="IssueTypeBox"
            ItemsSource="{x:Bind IssueTypeList}"
            SelectedItem="{x:Bind Item.Type,Mode=TwoWay}"
            SelectionChanged="IssueType_SelectionChanged"
            PlaceholderText="Enter the Issue Type"
            HorizontalAlignment="Stretch"
            Margin="10,0,0,0"/>
        </StackPanel>
        <TextBox Text="{x:Bind Item.Description,Mode=TwoWay}"
         Grid.Row="2"
         AcceptsReturn="True"
         Header="Description"
         Height="200"
         Margin="10,0"
         PlaceholderText="Enter Text Here" />
        <TextBlock Text="Planning" FontWeight="Bold" FontSize="16" Grid.Row="3" Margin="10,0" />

        <StackPanel Orientation="Horizontal" Grid.Row="4" Margin="10,0" Spacing="20">
            <StackPanel Background="LightGray" Padding="20">
                <TextBlock Text="Effort" FontWeight="Bold" FontSize="16" Margin="10,0" />
                <TextBox Text="{x:Bind Item.Effort,Mode=TwoWay}"
               HorizontalTextAlignment="Center"
               HorizontalAlignment="Center"
               HorizontalContentAlignment="Center"
               BorderBrush="Transparent"
               Background="Transparent"/>
                <Slider Value="{x:Bind Item.Effort,Mode=TwoWay}" Width="100" Minimum="0" Maximum="15" />
            </StackPanel>
            <StackPanel Background="LightGray"
              Padding="20">
                <TextBlock Text="Status" FontWeight="Bold" FontSize="16" Margin="10,0" />
                <ComboBox ItemsSource="{x:Bind StatusList}"
              SelectedItem="{x:Bind Item.Status}"
              HorizontalAlignment="Stretch"
              SelectionChanged="StatusPicker_SelectionChanged" />
                <TextBlock Text="{x:Bind Item.StartedAt,Converter={StaticResource StringFormatConverter},ConverterParameter='Started: {0:MMM dd, yyyy hh:mm tt}',Mode=OneWay}" />
                <TextBlock Text="{x:Bind Item.CompletedAt,Converter={StaticResource StringFormatConverter},ConverterParameter='Completed: {0:MMM dd, yyyy hh:mm tt}',Mode=OneWay}" />
            </StackPanel>
        </StackPanel>

        <TextBlock Text="{x:Bind Item.CreatedAt, Converter={StaticResource StringFormatConverter}, ConverterParameter='Created: {0:MMM dd, yyyy hh:mm tt}'}" Grid.Row="5"
           Margin="10,0"/>
    </Grid>
    
</Page>

```

```xml
    xmlns:local="using:UnoSampleApp"
    xmlns:converters="using:UnoSampleApp.Shared.Converters"
```
ここのコードはそれぞれのプロジェクトに合わせて変更してください。

xaml.cs も併せて変更します。

```csharp:MainPage.xaml.cs
using System;
using UnoSampleApp.Shared.Models;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace UnoSampleApp
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public static readonly DependencyProperty IssueItemProperty =
     DependencyProperty.Register(nameof(Item), typeof(IssueItem), typeof(MainPage), new PropertyMetadata(default(IssueItem)));

        public MainPage()
        {
            this.InitializeComponent();
        }

        public IssueItem Item
        {
            get => (IssueItem)GetValue(IssueItemProperty);
            set => SetValue(IssueItemProperty, value);
        }

        public IssueStatus[] StatusList => new[]
        {
        IssueStatus.Icebox,
        IssueStatus.Planned,
        IssueStatus.WIP,
        IssueStatus.Done,
        IssueStatus.Removed
    };

        public IssueType[] IssueTypeList => new[]
        {
        IssueType.Bug,
        IssueType.Feature,
        IssueType.Issue,
        IssueType.Task
    };

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            Item = new IssueItem
            {
                Id = 1232,
                Title = "Getting Started",
                Description = @"Create a page to enter Issues that we need to work on.

## Acceptance Criteria

- Display the issue Id
- Provide an ability to select the issue Type (i.e. Bug, Feature, etc)
- Include an Issue Title
- Include a full issue description with support for Markdown
- Include an issue effort
- Include an ability for a developer to update the Status (i.e Icebox, WIP, etc)

## Additional Comments

We would like to have a visual indicator for the type of issue as well as something to visualize the effort involved",
                Effort = 3,
                Status = IssueStatus.WIP,
                Type = IssueType.Feature,
                CreatedAt = new DateTimeOffset(2019, 04, 03, 08, 0, 0, TimeSpan.FromHours(-8)),
                StartedAt = new DateTimeOffset(2019, 04, 30, 08, 0, 0, TimeSpan.FromHours(-8))
            };
        }


        // Sets the time when we Complete or Start an issue.
        private void StatusPicker_SelectionChanged(object sender, SelectionChangedEventArgs args)
        {
            switch (Item.Status)
            {
                case IssueStatus.Removed:
                case IssueStatus.Done:
                    if (Item.CompletedAt is null)
                        Item.CompletedAt = DateTimeOffset.Now.ToLocalTime();
                    break;
                case IssueStatus.WIP:
                    if (Item.StartedAt is null)
                        Item.StartedAt = DateTimeOffset.Now.ToLocalTime();
                    break;
                default:
                    Item.StartedAt = null;
                    Item.CompletedAt = null;
                    break;
            }
        }

        // Provides a unique color based on the type of Issue
        private void IssueType_SelectionChanged(object sender, SelectionChangedEventArgs args)
        {
            var color = Colors.Red;
            switch (IssueTypeBox.SelectedItem)
            {
                case IssueType.Feature:
                    color = Colors.Green;
                    break;
                case IssueType.Issue:
                    color = Colors.Blue;
                    break;
                case IssueType.Task:
                    color = Colors.Yellow;
                    break;
            }
            IssueTypeIndicator.Background = new SolidColorBrush(color);
        }
    }
}

```

以上で終了です。
ctrl + F5 で実行しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183354/7307c557-da53-00aa-2f5f-aea4ec340046.png)

# 参考

- [Uno Platform](https://platform.uno/)
- [Create a Single Page App with Uno](https://platform.uno/docs/articles/getting-started-tutorial-1.html)
- https://github.com/unoplatform/uno