1. MAUI SelectionMode와 CheckBox가 충돌하는 이유
.NET MAUI에서 CollectionView를 사용할 때 SelectionMode를 활성화한 상태에서 각 항목에 CheckBox를 배치하면, CheckBox가 정상적으로 동작하지 않거나 선택 상태가 마음대로 바뀌는 이상한 현상을 자주 경험하게 된다. 체크를 했는데 해제가 되거나, 스크롤을 한 뒤에 다시 위로 올라가면 선택이 초기화되는 식의 문제가 대표적이다. 이 문제는 단순한 바인딩 실수라기보다, CollectionView 내부의 선택 관리 로직과 CheckBox가 가진 별도의 선택 로직이 동시에 존재하면서 서로 충돌하는 구조에서 비롯된다.
MAUI CollectionView는 SelectionMode를 기반으로 각 항목의 선택 상태를 내부적으로 관리한다. SelectionMode가 Single 또는 Multiple인 경우 사용자의 터치 입력을 통해 SelectedItem 또는 SelectedItems 리스트를 갱신하고, 셀을 다시 렌더링할 때 이 선택 정보를 기준으로 스타일을 재적용한다. 반대로 CheckBox는 항목 모델의 IsSelected 같은 Boolean 속성을 직접 바인딩하여 별도의 선택 상태를 유지하려고 한다. 결과적으로 CollectionView의 선택 상태와 모델의 선택 상태가 서로 다른 기준으로 움직이면서, 동일한 UI 요소인 CheckBox를 놓고 두 개의 선택 시스템이 서로 덮어쓰기를 시도하는 구조가 만들어진다. 이것이 SelectionMode와 CheckBox가 충돌하는 근본적인 이유다.

2. SelectionMode 내부 동작 방식, 구조를 이해해야 하는 이유
SelectionMode는 단순한 옵션 값이 아니라 CollectionView의 내부 동작 방식을 결정하는 중요한 설정이다. 먼저 SelectionMode가 None인 경우에는 CollectionView가 내부적으로 어떤 항목도 선택 상태로 관리하지 않는다. SelectedItem이나 SelectedItems 같은 속성도 의미가 거의 없으며, 사용자의 터치는 리스트 스크롤이나 단순 셀 터치로만 처리된다. 이 모드에서는 CollectionView가 선택 상태를 강제로 UI에 반영하지 않기 때문에 CheckBox와 충돌이 거의 발생하지 않는다.
SelectionMode가 Single인 경우에는 항상 하나의 항목만 선택 상태로 유지되도록 설계되어 있다. 사용자가 셀을 탭하면 해당 항목이 SelectedItem으로 설정되고, 셀 재활용 시점에도 이 선택 상태를 기반으로 셀 콘텐츠를 다시 그린다. Multiple인 경우 SelectedItems 컬렉션을 유지하면서 여러 항목의 선택 상태를 내부에서 관리한다. 이때 중요한 점은, CollectionView가 셀을 다시 렌더링할 때 DataTemplate 내부의 바인딩과는 별도로 선택 상태를 반영하는 UI 갱신 로직을 수행한다는 점이다. 이 말은 곧 CheckBox의 IsChecked 값이 true로 바인딩되어 있더라도, CollectionView가 해당 항목을 선택되지 않은 항목으로 판단하면 UI를 다시 그리는 과정에서 false로 그려 버릴 수 있다는 뜻이다.
이 구조 때문에 SelectionMode와 CheckBox를 동시에 사용하면, CheckBox를 눌러 모델의 IsSelected가 true가 되었어도 다음 렌더링 사이클에서 CollectionView가 자신의 선택 정보를 기준으로 CheckBox 상태를 덮어써 버리는 현상이 발생한다. 스크롤 중에 셀이 재활용될 때마다 이 동작이 반복되면, 개발자 입장에서는 선택이 랜덤하게 풀리는 것처럼 보인다. 실제로는 랜덤이 아니라 두 개의 선택 시스템이 서로 싸우고 있는 것이다.
3. SelectionMode와 CheckBox 충돌이 발생하는 대표 코드 패턴
실제 코드에서는 다음과 같은 패턴이 매우 자주 발견된다. 먼저 XAML에서 SelectionMode를 Multiple로 두고, 각 항목에 CheckBox를 배치한 예를 살펴본다.
SelectionMode="Multiple">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="8">
<CheckBox IsChecked="{Binding IsSelected}" />
<Label Text="{Binding Title}"
Margin="10,0,0,0" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
모델과 ViewModel은 대략 다음과 같이 구성되어 있을 것이다.
{
private bool _isSelected;
public string Title { get; set; }
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected == value)
return;
_isSelected = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MyViewModel
{
public ObservableCollection<MyItem> Items { get; } =
new ObservableCollection<MyItem>
{
new MyItem { Title = "첫 번째 항목" },
new MyItem { Title = "두 번째 항목" },
new MyItem { Title = "세 번째 항목" }
};
}

이 상태에서 사용자가 CheckBox를 클릭하면 IsSelected 값은 제대로 바뀌고, PropertyChanged 이벤트도 잘 발생한다. 그러나 CollectionView 입장에서는 해당 항목에 대한 터치 이벤트가 발생했기 때문에, SelectionMode 설정에 따라 SelectedItems에 이 항목을 추가하거나 제거하려고 한다. 이때 CollectionView는 내부 선택 상태를 기준으로 셀을 다시 그리며, CheckBox의 UI 상태 역시 이 선택 상태를 반영한 값으로 덮어쓴다. 모델에 저장된 IsSelected 값과 CollectionView가 관리하는 선택 상태가 서로 다를 경우, 결국 둘 중 하나가 다른 하나를 덮어써 버리는 모순 상태가 만들어진다.
스크롤을 할 때 문제가 더 두드러지는 이유도 이와 같다. 셀이 재활용되면서 CollectionView는 각 셀에 대해 자신의 SelectedItems 정보를 다시 반영하는데, 이 과정에서 CheckBox 바인딩 결과보다 CollectionView의 내부 선택 정보가 우선 적용되기 때문에, 사용자가 의도했던 선택 상태와 전혀 다른 화면이 나타나게 된다. 이런 현상이 반복되면 개발자는 코드가 정상임에도 불구하고 전혀 이해되지 않는 버그를 마주하게 된다.
4. 충돌을 피하기 위한 설계 전략, SelectionMode를 어떻게 써야 하는가
SelectionMode와 CheckBox를 함께 사용할 때의 충돌은 단순히 옵션 하나를 바꿔서 해결할 수 있는 문제가 아니라, 선택 상태를 누가 책임지고 관리할 것인가에 대한 설계 선택의 문제에 가깝다. 가장 근본적인 해결책은 SelectionMode를 None으로 두고 CollectionView의 선택 기능을 완전히 비활성화한 다음, 선택 상태는 전적으로 모델과 CheckBox만으로 관리하는 것이다.
다음은 SelectionMode를 None으로 변경한 XAML 예시다.
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="8">
<CheckBox
IsChecked="{Binding IsSelected, Mode=TwoWay}"
VerticalOptions="Center" />
<Label
Text="{Binding Title}"
Margin="10,0,0,0"
VerticalTextAlignment="Center" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
이렇게 구성하면 CollectionView는 선택 상태를 전혀 관리하지 않으므로, CheckBox와 ObservableCollection, INotifyPropertyChanged가 이루는 데이터 흐름만 남게 된다. 즉, 선택 상태를 관리하는 주체가 오직 모델과 바인딩 시스템 하나로 정리되며, SelectionMode와의 충돌 가능성이 원천적으로 사라진다.
만약 UI 요구사항 때문에 특정 항목을 선택했을 때 배경 색상을 변경하거나 행 전체를 하이라이트해야 한다면, SelectedItem에 의존하기보다 IsSelected 속성을 트리거로 사용하는 것이 좋다. 예를 들어 DataTrigger나 스타일을 활용하여 IsSelected가 true일 때 Grid의 Background 색을 변경하는 방식으로 구현하면 된다. 이렇게 하면 시각적인 강조 효과는 유지하면서도 SelectionMode와의 충돌을 피할 수 있다.
SelectionMode를 반드시 사용해야 하는 특수한 요구사항이 있는 경우에는 CollectionView의 SelectionChanged 이벤트를 활용하여 내부 선택 상태와 모델의 IsSelected를 강제로 동기화하는 전략을 사용해야 한다. 그러나 이 방식은 코드 복잡도와 유지 보수 비용이 크게 증가하고, CheckBox와 함께 사용할 때는 예외 상황이 많이 발생하기 때문에 전문가 관점에서는 가능한 피하는 것이 좋다. 실전 프로젝트에서는 선택 상태를 중앙집중적으로 관리하는 하나의 축만 남기는 것이 훨씬 안전한 설계다.
5. 마무리, SelectionMode를 이해하면 MAUI UI 문제의 절반이 정리된다
MAUI CollectionView에서 SelectionMode와 CheckBox가 충돌하는 문제는 단순한 컨트롤 설정이나 한두 줄의 바인딩 수정으로 해결되는 성격의 문제가 아니다. CollectionView 내부에 존재하는 선택 관리 로직과, 모델이 관리하는 선택 상태가 하나의 UI 요소를 놓고 서로 다른 기준으로 값을 적용하려 하기 때문에 발생하는 구조적인 문제다. SelectionMode가 Single 또는 Multiple일 때 CollectionView는 SelectedItem과 SelectedItems를 기준으로 항상 셀을 다시 렌더링하려고 한다. 반면 CheckBox는 IsSelected 같은 Boolean 속성을 중심으로 별도의 선택 흐름을 만들려고 한다.
이 모순을 해결하는 가장 확실한 방법은 선택의 책임을 하나로 통일하는 것이다. SelectionMode를 None으로 두고 선택은 전적으로 모델과 CheckBox만 사용하거나, 반대로 CheckBox를 제거하고 CollectionView의 SelectionMode만 사용해서 선택을 표현하는 식으로 구조를 단순화해야 한다. 이 글에서 다룬 SelectionMode의 내부 구조와 충돌 원리를 이해하면, 앞서 다루었던 CheckBox 무반응 문제, ObservableCollection 미사용 문제, 데이터 바인딩 꼬임 문제도 함께 연결해서 볼 수 있다.
다음 단계에서는 MAUI에서 자주 발생하는 BindingContext 꼬임 문제를 렌더링 타이밍, 셀 재활용, ViewModel 재사용 구조, 페이지 생명 주기와 함께 분석해 볼 수 있다. 이러한 구조적 이해를 쌓아 두면 MAUI로 복잡한 UI를 개발할 때 발생하는 여러 가지 미묘한 버그를 훨씬 더 빠르고 정확하게 해결할 수 있을 것이다.