본문 바로가기

Develop/MAUI 가이드

[Xamarin] View 안에 View 를 주입할 수 있는 View 를 작성해보자

반응형

Insert View in View
Insert View in View

View 안에 View 를 주입할 수 있는 View 를 작성해보자

View 안에 여러 View 를 주입하고 속성에 따라 교체하는 ContainerView 를 구현하거나, NavigationBar 의 크기와 모양만 잡아두고 Button 을 추가하는 등 활용하는 방법이 많으니 알아두시면 유용합니다.

예제 코드 ContainerView

// ContentView.Content 를 Views 속성으로 연결하는 Attribute
[ContentProperty(nameof(Views))]
public class ContainerView : ContentView
{
    // 주입된 View 를 배치하는게 사용할 Layout
    private StackLayout _layout;

    // 외부에서 주입할 View 를 저장할 자료구조
    public ObservableCollection<View> Views { get; set; }

    public ContainerView()
    {
        _layout = new StackLayout();

        Views = new ObservableCollection<View>();
        Views.CollectionChanged += OnViewsCollectionChanged;

        // ContentView.Contnet 에 할당한 View 만 화면에 표시된다.
        Content = _layout;
    }

    // ContainerView 에 View 가 추가되거나 제거될 때 대응하기 위한 메서드
    private void OnViewsCollectionChanged((object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.OldItems != null)
        {
            foreach(View view in e.OldItems)
                _layuout.Children.Remove(view);
        }

        if (e.NewItems != null)
        {
            foreach(View view in e.NewItems)
                _layuout.Children.Add(view);
        }
    }
}

ContainerView 활용하는 방법

ContainerView 를 XAML 에서 다음과 같이 활용할 수 있습니다.

<local:ContainerView>
  <Label Text="A" />
  <Label Text="B" />
  <Label Text="C" />
  <!-- Views -->
</local:ContainerView>

ContainerView 태그 안에 정의한 Label 들은 [ContentProperty(nameof(Views))] 로 인해서 ContainerView.Views 에 추가됩니다. 그리고 ViewsLabel 이 추가될 때마다 CollectionChanged 이벤트가 발생하게 되고, 이벤트를 구독한 OnViewsCollectionChanged 메서드가 실행되면서 ContainerView._layoutLabel 을 추가하게 됩니다. 만약 [ContentProperty(nameof(Views))] 를 지정하지 않으면 ContentView.ContentLabel 이 추가되니 주의바랍니다. 만약 [ContentProperty(nameof(Views))] 를 사용하지 않고 View 를 추가하려면 아래와 같이 바꿔주면 됩니다.

<local:ContainerView>
  <local:ContainerView.Views>
      <Label Text="A" />
      <Label Text="B" />
      <Label Text="C" />
      <!-- Views -->
  </local:ContainerView.Views>
</local:ContainerView>

ContainerView 확장

만약 ContainerView 를 다채롭게 사용하고 싶다면 다음과 같이 개선해서 사용할 수도 있습니다.

public class ContainerView : ContentView
{
    private StackLayout _layout;
    private StackLayout _aTypesLayout;
    private StackLayout _bTypesLayout;

       private Label _title;
    public Label Title 
    { 
        get => _title;
        set
        {
            _title = value;
            _layout.Children.RemoveAt(0);
            _layout.Children.Insert(0, _title);
        }
    }

    public ObservableCollection<ViewA> ATypes { get; set; }
    public ObservableCollection<ViewB> BTypes { get; set; }

    public ContainerView()
    {
        _title = new Label { Text = "Title" };'

        _aTypesLayout = new StackLayout();
        ATypes = new ObservableCollection<View>();
        ATypes.CollectionChanged += OnViewsCollectionChanged;

        _bTypesLayout = new StackLayout();
        BTypes = new ObservableCollection<View>();
        BTypes.CollectionChanged += OnViewsCollectionChanged;

        _layout = new StackLayout
        {
            Children = { _title, _aTypesLayout, _bTypesLayout },
        }

        Content = _layout;
    }

    private void OnViewsCollectionChanged((object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.OldItems != null)
        {
            foreach(View view in e.OldItems)
                _layuout.Children.Remove(view);
        }

        if (e.NewItems != null)
        {
            foreach(View view in e.NewItems)
                _layuout.Children.Add(view);
        }
    }
}

확장한 ContainerView 는 아래와 같이 사용할 수 있습니다.

<local:ContainerView>
  <local:ContainerView.Title>
    <Label Text="Custom" />
  </local:ContainerView.Title> 
  <local:ContainerView.ATypes>
      <ViewA Value="A1" />
      <ViewA Value="A2" />
      <ViewA Value="A3" />
  </local:ContainerView.ATypes>
  <local:ContainerView.BTypes>
      <ViewB Value="B1" />
      <ViewB Value="B2" />
      <ViewB Value="B3" />
  </local:ContainerView.BTypes>
</local:ContainerView>

주의해야 부분은 ContentView 를 상속받아서 확장했기 때문에 local:ContainerView.Content 에 새로운 View 를 할당하게 되면 다른 View 들이 화면에 보이지 않게 됩니다. 따라서 Content 로 사용할 만큼 기본이 되는 View 를 ContentProperty 로 지정해놓길 권장드립니다.

반응형