본문 바로가기

Develop/MAUI 가이드

[Xamarin] Effects 로 Platform-Specific 하게 구현하기

반응형

Effects
Effects

Effects 로 Platform-Specific 하게 구현하기

Xamarin 에서 UI 를 플랫폼 별로 커스터마징할 때는 Effect 와 Custom Renderer 을 사용합니다. 이 글에서는 Effect 를 사용하는 방법을 설명하겠습니다.

Effect

Effect 는 Custom Renderer 을 단순하게 구현하는 방법이라고 이해하시면 됩니다. Effect 를 구현하는 순서는 아래와 같습니다.

  1. 지원하고자 하는 플랫폼 프로젝트마다 PlatformEffect 를 상속받는 클래스를 만듭니다.
  2. PlatformEffect 를 상속받은 클래스의 OnAttached, OnDetached 메서드를 오버라이딩합니다.
  3. ResolutionGroupName 속성과 ExportEffect 속성을 등록합니다.
  4. 공통 프로젝트에서 Element.Effects 에 새로 정의한 Effect 를 추가합니다.

1. PlatformEffect 상속받기

먼저 플랫폼 프로젝트 (Android 또는 iOS 또는 UWP 프로젝트) 에 PlatformEffect 를 상속받는 클래스를 하나 추가합니다.

// iOS 프로젝트에서 PlatformEffect 를 상속받는다.
public class FocusEffect : PlatformEffect
{

}

2. OnAttached 와 OnDetached 메서드를 오버라이딩

public class FocusEffect : PlatformEffect
{

    // Effect 가 UI 에 붙어서 조작이 가능해질 때 호출된다.
    protected override void OnAttached ()
    {
        // this.Control 로 UI 에 접근하여 원하는 조작을 하면 된다.
    }

    // Effect 가 떨어질 때 호출되는 것 같은데 연구가 더 필요하다.
    protected override void OnDetached ()
    {

    }

    // 추가적으로 UI의 속성이 변경되었을 때 반응하는 것도 가능하다.
    protected override void OnElementPropertyChanged (PropertyChangedEventArgs args)
    {

    }
}

오버라이딩해서 UI 를 수정할 때 제일 명심해야 하는 건 타입 캐스팅 이 필요할 수 있다는 사실입니다.

this.Control 속성으로 플랫폼 종속적인 UI 에 접근할 수 있는데 Control 의 타입이 충분히 구체적이지 않아, 대체적으로 타입 캐스팅이 필요합니다.

타입 캐스팅을 하려면 이제 구체적으로 어떤 타입의 UI 인지를 알아야 하는데 플랫폼 별로 다르니 주의가 필요합니다. 플랫폼 별로 어떤 타입일지는 디버깅으로 타입을 우선 확인하고 진행해도 되고 Custom Renderer 에서 알려준 공식 문서 를 확인하는 것도 좋은 방법입니다.

3. ResolutionGroupName 속성과 ExportEffect 속성 등록

메소드를 오버라이딩해서 플랫폼 종속적인 코딩을 한 후, Effect 가 Xamarin 에 노출되어 알아서 등록되어 사용되도록 해야 합니다. 이때 사용하는 속성이 ResolutionGroupName 속성과 ExportEffect 속성입니다. 사용하는 방법은 아주 간단하며 아래와 같이 사용하면 됩니다.


// 아래와 같이 namespace 위에 적어도 되고 클래스 위에 적어도 된다.
[assembly:ResolutionGroupName ("MyCompany")]
// typeof 와 nameof 를 사용하면 좋다.
[assembly:ExportEffect (typeof(EffectsDemo.iOS.FocusEffect), nameof(EffectsDemo.iOS.FocusEffect))]
namespace EffectsDemo.iOS
{
    public class FocusEffect : PlatformEffect
    {

    }
}

4. 공통 프로젝트에서 새로 정의한 Effect 사용

플랫폼 프로젝트에 정의한 PlatformEffect 는 공통 프로젝트에서 2 가지 방법으로 사용하실 수 있습니다.

먼저 RoutingEffect 을 정의해서 사용하는 방법이 있습니다. 이 방법은 제가 제일 선호하는 방법입니다. XAML 에서 사용할 수도 있고 클래스로 정의하기 때문에 커스터마이징을 하기도 용이합니다.

// RoutingEffect 를 상속받는다.
public class FocusEffect : RoutingEffect
{
    // ResolutionGroupName 속성과 ExportEffect 속성에 정의한 내용을 정확하게 적어야 한다.
    public FocusEffect () : base ($"MyCompany.{nameof(FocusEffect)}")
    {
    }
}

두번째 방법은 코드로 Effect 를 사용하는 방법입니다.

// ResolutionGroupName 속성과 ExportEffect 속성에 정의한 내용을 정확하게 적어야 한다.
var focusEffect = Effect.Resolve ($"MyCompany.{nameof(FocusEffect)}");
// UI 의 Effects 목록에 추가한다.
element.Effects.Add (focusEffect);

Effect 로 플랫폼 종속적인 조작이 가능한 원리

Xamarin 소스 코드를 살펴보면 PlatformEffect 는 플랫폼 별로 정의된 방식이 다릅니다.

공통으로 정의된 형식은 아래와 같습니다.

public abstract class PlatformEffect<TContainer, TControl> : Effect where TContainer : class where TControl : class

Android 에서 정의된 형식은 아래와 같습니다.

public abstract class PlatformEffect : PlatformEffect<ViewGroup, AView>

iOS 에서 정의된 형식은 아래와 같습니다.

public abstract class PlatformEffect : PlatformEffect<UIView, UIView>

공통으로 정의된 형식에 <TContainer, TControl> 이라는 제네릭이 정의되어 있고, 플랫폼 프로젝트 별로 플랫폼 종속적인 UI를 제네릭에 반영했습니다. 여기서 PlatformEffect 소스코드를 보면 ContainerControl 로 플랫폼 종속적인 UI 에 접근하실 수 있습니다.

public abstract class PlatformEffect<TContainer, TControl> : Effect where TContainer : class where TControl : class
{
    public TContainer Container { get; internal set; }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void SetContainer(TContainer container) => Container = container;

    public TControl Control { get; internal set; }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public void SetControl(TControl control) => Control = control;

    // ...
}

따라서 Effect 를 사용하면 Xamarin 이 Effect 에 플랫폼 종속적인 UI 를 PlatformEffect.ContainerPlatformEffect.Control 속성으로 전달해주고 OnAttached 메서드 또는 OnDetached 메서드와 같이 오버라이딩할 수 있는 메서드를 통해 플랫폼 종속적인 조작하는 방법으로 플랫폼별 커스터마이징이 가능해집니다.

자세한 설명

ResolutionGroupName 속성은 Assembly 를 타켓으로 등록되는 속성입니다. ResolutionGroupName 목적은 namespace 같이 다른 패키지에 등록된 다른 Effect 와 구별하기 위해 사용하는 속성입니다. 따라서 프로젝트 별로 한번만 설정해도 됩니다.

반응형