본문 바로가기

Develop/MAUI 가이드

[Xamarin] Renderer 를 만들었더니 BackgroundColor 가 적용되지 않을 때 해결하는 방법

반응형

Renderer 를 만들었더니 BackgroundColor 가 적용되지 않을 때 해결하는 방법
Renderer 를 만들었더니 BackgroundColor 가 적용되지 않을 때 해결하는 방법

Renderer 를 만들었더니 BackgroundColor 가 적용되지 않을 때 해결하는 방법

Xamarin 으로 개발하다가 특정 LayoutCustomRenderer 를 만들었더니 View.BackgroundColor 에 적용한 배경색이 사라지는 버그를 만났고, 해결 과정을 공유합니다.

해결방법은 의외로 간단했습니다. CustomRendererViewRenderer가 아닌 VisualElementRenderer 로 상속받아 아래와 같이 구현하면됩니다.

[assembly: ExportRenderer(typeof(MyView), typeof(MyViewRenderer))]
namespace Solution.iOS
{
    public class MyViewRenderer : VisualElementRenderer
    {
        // ...
    }
}

VisualElementRenderer 대신 ViewRenderer 을 상속받았던 이유는 공식 문서에 제일 많이 등장하는 ViewRenderer 을 상속받으면 괜찮겠지' 하는 이유로 상속을 받았습니다.

그리고 Xamarin 의 View 관계 다이어그램에서 View 라는 유사한 이름도 있길래 괜찮을 줄 알았습니다.

Xamarin View 관계 다이어그램
Xamarin View 관계 다이어그램에서 View

그리고 Xamarin 소스코드를 봐도 ViewRendererVisualElementRenderer 를 상속받고 있어서 문제없을줄 알았는데! 결과는 BackgroundColor 가 적용되지 않는 버그였습니다.

public abstract class ViewRenderer : ViewRenderer<View, NativeView>
{

}

public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView>, IVisualNativeElementRenderer, ITabStop where TView : View where TNativeView : NativeView
{
    // ...
}

버그를 해결할 방법을 검색하다가 Xamarin Github 이슈 중 동일한 질문이 있어서 다행히 해결은 할 수 있었습니다. 이슈를 처음 제기한 개발자도 저와 동일한 이유로 왜 ViewRenderer가 아닌 VisualEmentRenderer를 상속받아야 하는지 혼란에 빠졌고 설명을 부탁하는 글을 남겼습니다.

Thanks! I figured it might be the wrong renderer, which leads me to another question: what is the easiest way to find the correct base renderer? I tried finding myself around the codebase, but as I said in the SO question, StackLayout, like some other classes, are not declared in the AssemblyInfo.
Also, the documentation tells me that I should be using ViewRenderer instead of VisualElementRenderer, as stated here. In fact, looking at the class hierarchy, ViewRenderer extends VisualElementRenderer, which also leads me to believe that I should be inheriting from ViewRenderer, which should be more specific and more complete.
From this experience, I think the docs are not clear enough regarding this topic, because, as you can see, I couldn't figure out how I could correctly extend a StackLayout (and it would probably happen with other layouts too) by reading the docs AND looking at the code .
What do you think? Am I missing something or is this something that should be improved in the docs?

질문에 대한 Xamarin 개발팀의 답변은 아래와 같습니다.

I'm guessing that guard is there so it only runs when the renderer is reused which is only really the case with CollectionView and ListView so that when a new element comes in the background is updated
AFAIR you can't really create a custom renderer for stacklayout, grid, etc... because these are all just xplat layouts. They don't have any specific platform level representations. All they do is define the bounds of the elements they contain and then those bounds get projected to the platform.
i'm not sure your use case but you'd probably want to create a custom renderer for a customview instead of a stacklayout or a contentview where the contents is a stacklayout

요약하면 Layout 은 Native View 로 연결되는 것이 아니라 화면에 영역을 잡기 위한 용도로만 사용되는 xplat (cross-platform) layout 이기 때문에 CustomRenderer 을 만들 수 없다는 답변이었습니다.

Xamarin 개발팀의 답변과 소스코드를 살펴보니 의문이 해결되었습니다. 아래 코드를 읽어 보시면 알 수 있습니다.

ViewRenderer

public abstract class ViewRenderer<TView, TNativeView> : VisualElementRenderer<TView>, IVisualNativeElementRenderer, ITabStop where TView : View where TNativeView : NativeView
{
    protected override void SetBackgroundColor(Color color)
    {
        if (IsElementOrControlEmpty)
            return;

        // 연결된 NativeView 의 BackgroundColor 를 변경합니다.
        // 그런데 Layout 은 Xamarin 팀이 설명했듯이 연결된 NativeView 가 없어서 Control 이 null 이 됩니다.
        // 따라서 배경색을 변경할 수가 없게 됩니다.
        if (color == Color.Default)
            Control.BackgroundColor = _defaultColor;
        else
            Control.BackgroundColor = color.ToUIColor();

        Control.Layer.BackgroundColor = color == Color.Default ? _defaultColor : color.ToCGColor();
}

VisualElementRenderer

public class VisualElementRenderer<TElement> : NativeView, IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement
{
    public sealed override NativeColor BackgroundColor
    {
        // 상속 받은 NativeView 의 BackgroundColor 를 변경합니다. 
        // Layout 처럼 연결될 NativeView 가 없더라도 NativeView 를 상속했기 때문에 배경색이 변합니다.
        get { return base.BackgroundColor; }
        set { base.BackgroundColor = value; }
    }

    protected virtual void SetBackgroundColor(Color color)
    {
        if (color == Color.Default)
            BackgroundColor = _defaultColor;
        else
            BackgroundColor = color.ToUIColor();
    }
}

결국 BackgroundColor 가 변하지 않았던 이유는LayoutCustomRenderer 를 만들거라 예상하지 못했고 LayoutCustomRendererViewRenderer 를 상속받아 버리면 Controlnull 이라 BackgroundColor 를 변경할 수 없었습니다.

반응형