티스토리 뷰

일반 웹 URL로 창을 출력하지 않고 로컬 index.html을 불러오는 CustomControl WebView

사용된 Nuget 버전

UWP

Uno Platform

UWP

Uno Platform WinUI UWP에서 WebView는 WebView2만 지원되므로 WebView2를 상속받아 아래와 같은 코드 작성

public class WebViewer : WebView2
{
    public WebViewer()
    {
        Initialized();
    }

    private async void Initialized()
    {
        await EnsureCoreWebView2Async();
        var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Resources/WebView/index.html"));
        var path = "file:///" + file.Path;
        this.CoreWebView2.Navigate(path);
    }
}

Resources경로는 아래와 같고 자바 스크립트 첨부 시 index.html와 동일한 방식으로 추가

.xaml

xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
<c:WebViewer x:Name="webviewTest" />
<Button Click="Button_Click" Content="테스트" />
.cs

private async void Button_Click(object sender, RoutedEventArgs e)
{
    var script = @"console.log('스크립트 실행해보기')";
    await this.webviewTest.ExecuteScriptAsync(script);
}

WASM

Uno Platform WinUI WASM에서 WebView는 공식 홈페이지 문서에 지원하지 않는 내용으로 기재되어 있다.

WinUI가 적용되지 않은 상태에서는 다른 방식으로 CustomControl을 생설할 수 있으나, WinUI에서는 적용되지 않았으며, WinUI없이 직접 체크는 진행하지 않았다

WASM는 웹으로 빌드되어 결과물이 나오기때문에 html tag중 iFrame 또는 Embed를 직접 입력하여 로컬 index.html을 띄우면 되지 않을까 생각했다.

사양에 관련된 부분을 최대한 배제하고도 사용해도 될까 의문이 들었으며, 이 방식으로 해결한다면 스크립트 실행에 불편함이 있다는걸 알게되었다. UWP나 Android에서는 스크립트 코드를 직접 실행할 수 있는데 이 방식을 사용한다면 스크립트를 따로 제작한 후 파일로 생성하여 그 파일을 HTML이 다시 로드하여 업데이트 하는 방식은 일관성있지 않은 방법이기에 효율적으로 보기 어렵다.

WinUI에서 WebView를 사용하기 위해 다음과 같이 진행하였다.

아래와 같은 코드를 작성한다

.cs
public partial class WebViewer : FrameworkElement
{
    public WebViewer()
    {
        this.Background = new SolidColorBrush(Colors.Transparent);
        this.SetHtmlContent("<p id="name">홍길동</p>");
        this.Loaded += WebViewer_Loaded;
    }

    private void WebViewer_Loaded(object sender, RoutedEventArgs e)
    {
         var script1 = @"console.log('스크립트 실행해보기')";
	     var script2 = @$"document.getElementById('name).innerHTML=""동길홍""";

		//둘중 하나 사용
        //this.ExecuteJavascriptAsync(script1);
        //Uno.Foundation.WebAssemblyRuntime.InvokeJS(script2);
    }
}

this.Background를 투명으로 설정하지 않는다면 WebView 포인터 동작이 하지 않는다.

이는, 공식 문서에 기재되어 있는 내용

더보기

참고URL https://platform.uno/docs/articles/interop/wasm-javascript-3.html

//XAML behavior: a non-null background is required on an element to be "visible to pointers".

// Uno reproduces this behavior, so we must set it here even if we're not using the background.

// Not doing this will lead to a `pointer-events: none` CSS style on the control.

WASM에서 스크립트 사용 시 requireJS를 사용하여 모듈을 불러오는 방식을 사용한다.

requireJS는 익숙하지 않고, 원하는 방식이 아니라 다른 방식으로 진행하였다.

WASM 프로젝트를 수정하여 index.html을 다음과 같이 작성하였다.

<WasmShellIndexHtmlPath>$(MSBuildThisFileDirectory)\wwwroot\index.html</WasmShellIndexHtmlPath>

index.html파일 생성 후 다음과 같이 작성

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

    <script type='text/javascript' src='./scripts/my_chart.js'></script>

    <script type="text/javascript" src="./require.js"></script>
    <script type="text/javascript" src="./mono-config.js"></script>
    <script type="text/javascript" src="./uno-config.js"></script>
    <script type="text/javascript" src="./uno-bootstrap.js"></script>
    <script async type="text/javascript" src="./dotnet.js"></script>
    $(ADDITIONAL_CSS)
    $(ADDITIONAL_HEAD)
</head>
<body>
    <div id="uno-body" class="container-fluid uno-body">
        <div class="uno-loader"
             loading-position="bottom"
             loading-alert="none">

            <!-- Logo: change src to customize the logo -->
            <img class="logo"
                 src=""
                 title="Uno is loading your application" />

            <progress></progress>
            <span class="alert"></span>
        </div>
    </div>
    <noscript>
        <p>This application requires Javascript and WebAssembly to be enabled.</p>
    </noscript>
</body>
</html>

css의 경우 WasmCSS폴더에 위치하면 따로 link를 해주지 않아도 된다. WasmScripts에 스크립트를 넣을 시 Import되는 방식도 똑같으나 이전에 기재한 내용과 같이 requireJS를 사용해야 하기에 index.html에 직접 생성하였다.

.xaml

xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
<c:WebViewer x:Name="webviewTest" />
<Button Click="Button_Click" Content="테스트" />
.cs
var script = @"console.log('스크립트 실행해보기')";
Uno.Foundation.WebAssemblyRuntime.InvokeJS(script);

또는 스크립트에 따로 적용한 함수를 불러올 수 도 있다
WasmScripts > main.js 파일 생성
아래와 같이 작성

function MyUpdate() {
    console.log('함수로 스크립트 실행하기')
}

Uno.Foundation.WebAssemblyRuntime.InvokeJS("MyUpdate();");

Android

Android에서는 WebView를 사용해야하는데, 이 방식으로는 원하는 동작을 하지 어려워 Android 네이티브 WebView를 사용하였다.

코드는 아래와 같다.

using Android.Content;
using Android.Webkit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Windows.Storage;

namespace MyApp.SharedUI.Controls
{
    public partial class WebViewer : WebView
    {
        public WebViewer(Context context) : base(context)
        {
            global::Android.Webkit.WebView.SetWebContentsDebuggingEnabled(true);
            this.Settings.UseWideViewPort = true;   //화면 사이즈 허용
            this.Settings.JavaScriptEnabled = true; 
            this.Settings.DomStorageEnabled = true; 
            Initialized();
        }

        private async void Initialized()
        {
            //var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Resources/WebView/index.html"));
            //var path = "file:///" + file.Path;
            var path = "file:///android_asset/index.html";
            this.LoadUrl(path);
            
        }
    }
}

global::Android.Webkit.WebView.SetWebContentsDebuggingEnabled(true);WebView

디버깅을 위해 작성하는 코드이며, 중요한 것은

this.Settings.JavaScriptEnabled = true;

this.Settings.DomStorageEnabled = true;

이 두개의 코드로 활성화하지 않는다면 스크립트 실행이 불가하고 내부에서 사용하는 리소스를 불러올 수 없다.

(css나 js같은 파일들)

DomStorageEnabled 활성화 하지 않고 스크립트를 불러올 시 다음과 같은 에러를 크롬 디버깅 모드에서 확인할 수 있다.

더보기

"Uncaught TypeError: Cannot read property 'getItem' of null"

path는 2가지 모두 사용할 수 있는데, 공유 프로젝트 사용 시 GetFileFromApplicationUriAsync를 사용하여 파일 경로를 불러오고 Android에서 읽을 수 있도록 file:///로 변환해 주었다.

이 방식도 가능하나 Android assets에 위치해도 동일하게 사용할 수 있다.

다만, 공유프로젝트를 사용했을 때 단점으로는 import src또한 동일한 방식으로 변경해야한다.

<link rel="stylesheet" href="file:////data/~~~~my_chart.css">
<script type='text/javascript' src='file:////data/~~~~my_chart.js'/>

개인적으로 코드는 작성하였으나 안쓰는거지만 아래 코드를 참고하면 될듯하다.

var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Resources/WebView/index.html"));
using (var inputStream = await file.OpenReadAsync())
using (var classicStream = inputStream.AsStreamForRead())
using (var streamReader = new StreamReader(classicStream))
{
    while (streamReader.Peek() >= 0)
    {
        var path = file.Path.Replace("index.html", "");
        string cssFilePath = System.IO.Path.Combine(path, @"my_chart.css");
        Uri uri = new Uri(cssFilePath);
        string cssUriPath = uri.AbsoluteUri;

        string jsFilePath = System.IO.Path.Combine(path, @"my_chart.js");
        uri = new Uri(jsFilePath);
        string jsUriPath = uri.AbsoluteUri;

        var readAllText = streamReader.ReadToEnd();
        var replace1 = readAllText.Replace(@$"<link rel=""stylesheet"" href=""toastui-chart.css"">", @$"<link rel=""stylesheet"" href=""{cssUriPath}"">");
        var replace2 = replace1.Replace(@$"<script type='text/javascript' src='toastui-chart.js'></script>", @$"<script type='text/javascript' src='{jsUriPath}'></script>");

        //await this.EnsureCoreWebView2Async();
        //this.NavigateToString(replace2);
    }
}

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크