1. 서론 — CollectionView 스크롤이 갑자기 튀어 올라가는 이유
MAUI CollectionView를 사용하면 다음과 같은 현상이 자주 발생한다.
- 항목을 추가(Add)하면 스크롤이 맨 처음으로 이동
- 항목을 수정하면 리스트 전체가 다시 그려짐
- ObservableCollection을 갱신해도 스크롤이 유지되지 않음
- Filtering(Search) 후 다시 원래 목록으로 돌아가면 스크롤이 초기화됨
많은 초보자는 이를 단순 버그로 오해하지만, 실제로는 MAUI CollectionView의 Virtualization 구조와 ItemsSource 변경 방식이 핵심 원인이다.
이 글에서는 스크롤 초기화가 발생하는 이유를 내부 구조 관점에서 분석하고, 실전 해결 전략을 제시한다.

2. 스크롤 초기화가 발생하는 핵심 원인 분석
원인 1: ItemsSource 전체를 새로운 컬렉션으로 교체하는 경우
다음 코드는 전형적인 문제 코드를 보여준다.
ItemsSource가 새로운 객체로 바뀌면 MAUI는 다음 동작을 수행한다.
- VirtualizingLayout 초기화
- 모든 셀 재생성
- 스크롤 위치 Reset
즉, “리스트 전체를 새로 고치기 때문에” 스크롤이 당연히 위로 튀게 된다.
원인 2: 셀 재활용(Recycling) 구조 때문에 수정된 항목 위치에서 스크롤이 흔들림
MAUI CollectionView는 Virtualization을 사용한다.
스크롤이 내려갈 때:
- 화면 밖 항목은 버리고
- 재활용된 셀을 재사용하며
- BindingContext만 새로 바꾼다
이 과정에서
항목 갱신 시 셀 레이아웃이 재계산되면 스크롤이 흔들릴 수 있다.
특히 다음 같은 경우:
- 항목의 Height가 동적으로 바뀜
- DataTemplate이 Conditional 방식
- ItemsSource가 정렬(Sort) 변경됨
원인 3: Replace(=대체) 업데이트가 아닌 Reset 이벤트가 발생
ObservableCollection은 3가지 이벤트를 발생시킨다.
- Add
- Remove
- Replace
- Reset
이 중 Reset은 스크롤 초기화를 유발한다.
예:
다음 코드는 모든 변경을 Reset으로 처리한다.
foreach (var x in filtered)
Items.Add(x);
이 경우 UI는 다음처럼 판단한다.
“이 리스트는 완전히 새로운 목록이다 → 스크롤 위치를 처음으로 재설정하자”
원인 4: 필터링(Search) 후 ItemsSource를 원래 리스트로 되돌릴 때 항상 초기화됨
예:
필터링이 끝나고 원래 리스트로 돌아가는 순간
항목 수가 크게 변한 것처럼 보여
CollectionView는 스크롤 위치를 초기화한다.
원인 5: Android iOS 플랫폼 차이
Android는 Virtualization이 강하게 적용되어 있어 위치가 튀기 쉽고,
iOS는 UI 스레드 성능이 좋아 상대적으로 부드럽다.
따라서 동일 코드라도 Android에서 문제가 더 많이 발생한다.
3. 실전에서 흔히 보는 잘못된 패턴 3가지
❌ 잘못된 패턴 1 — 필터링 후 Items 새로 생성
스크롤 초기화 100% 발생.
❌ 잘못된 패턴 2 — Clear + Add 반복
foreach (var x in list)
Items.Add(x);
ObservableCollection은 Reset 이벤트를 발생시키므로 스크롤 유지 불가능.
❌ 잘못된 패턴 3 — DataTemplate이 자주 바뀌는 조건부 UI
DataTemplate이 바뀌면 셀이 완전히 재생성되므로 스크롤이 초기화된다.
4. 스크롤 초기화를 방지하는 정석 전략
이제 실전적으로 검증된 해결책을 제시한다.
✔ 전략 1: ItemsSource 교체 금지, 기존 ObservableCollection을 유지한 채 Add/Remove만 수행
예:
Items = new ObservableCollection<MyItem>(); // ❌ 금지
올바른 방식:
foreach (var x in filtered)
Items.Add(x);
물론 이것도 Reset을 유발할 수 있으므로,
다음 전략과 함께 사용하는 것이 중요하다.
✔ 전략 2: CollectionView의 스크롤 위치를 저장 후 복원
MyCollectionView.ScrollTo(offset, position: ScrollToPosition.Start, animate: false);
MAUI 8부터 안정적으로 동작하는 구조.
✔ 전략 3: 항목을 변경할 때 Replace가 아닌 Modify 방식 사용
예:
item.Name = "Updated"; // 내부 값만 수정
ObservableCollection Replace 이벤트가 아닌
PropertyChanged만 발생하므로 스크롤 영향이 거의 없다.
✔ 전략 4: 필터링 구조 변경 — ItemsSource를 교체하지 않고 "IsVisible" 활용
필터링 시 이렇게 하지 말고:
이렇게 설계:
item.IsVisible = MatchesFilter(item);
그리고 XAML:
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid IsVisible="{Binding IsVisible}">
스크롤은 절대 초기화되지 않는다.
✔ 전략 5: Android에서 스크롤 튐 완화를 위한 handler 설정
CollectionViewHandler.Mapper.AppendToMapping("NoAnimation", (handler, view) =>
{
handler.PlatformView.ItemAnimator = null;
});
#endif
애니메이션 제거로 스크롤 튐이 크게 감소한다.
5. 실전 예제 — 필터링(Search) 시 스크롤 유지하는 최적 구조
✔ ViewModel
private void ApplyFilter(string keyword)
{
foreach (var item in Items)
item.IsVisible = item.Title.Contains(keyword, StringComparison.OrdinalIgnoreCase);
}
✔ XAML
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame IsVisible="{Binding IsVisible}">
<Label Text="{Binding Title}" />
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
이 방식의 장점:
- ItemsSource 변경 없음
- 스크롤 유지
- Virtualization 유지
- UI 부드러움
- 대용량 데이터에서도 빠름
6. 마무리 — 스크롤 초기화 문제를 해결하면 UX 품질이 크게 향상된다
CollectionView 스크롤 초기화 문제는 단순한 버그가 아니라
- Virtualization 구조
- ItemsSource 변경 방식
- ObservableCollection 이벤트
- 셀 재활용
- 플랫폼별 렌더링 차이
가 모두 연결된 복합적인 문제이다.
이 글에서 제시한 전략을 적용하면:
- 스크롤이 튀지 않고
- 필터링이 부드럽게 동작하며
- 항목 변경 시 UI 흔들림 없이
- 안정적인 UX를 제공할 수 있다.
MAUI UI 성능 품질이 확실히 올라간다.