반응형

SwiftUI swipeActions를 ScrollView와 LazyVStack에서 사용하는 방법 (iOS 20 / Xcode 27)

기존 SwiftUI에서는 swipeActions 및 드래그 앤 드롭 재정렬(onMove) 기능이 List 뷰 내부에서만 제한적으로 동작했습니다. iOS 20과 Xcode 27부터는 swipeActionsContainer 및 reorderable 모디파이어가 추가되어 ScrollView, LazyVStack, LazyHStack 등 일반 뷰 컨테이너에서도 동일한 상호작용을 구현할 수 있습니다.


 

기존 List 제한 사항과 신규 API 비교

List는 기본 UIKit의 UITableView 기반으로 작동하여 커스텀 레이아웃이나 스크롤 바인딩 처리에 제약이 있었습니다. 신규 API 도입으로 일반 스크롤 뷰에서도 레이아웃 자유도를 유지하면서 스와이프 및 정렬 기능을 적용할 수 있습니다.

구분 iOS 19 이하 (List 전용 iOS 20 이상 (swipeActionsContainer)
적용 가능 뷰 List ScrollView, LazyVStack, LazyHStack, Custom Stack
스와이프 선언 .swipeActions(...) .swipeActionsContainer(...)
드래그 정렬 .onMove(perform:) .reorderable(by:) / .reorderContainer(...)
스크롤 최적화 기본 테이블 셀 재사용 lazy 계열 뷰의 개별 셀 상태 유지

 

1. ScrollView 및 LazyVStack 내 스와이프 액션 구현

swipeActionsContainer 모디파이어를 사용하면 ScrollView 내부의 각 아이템 뷰에 개별 스와이프 버튼을 할당할 수 있습니다.

import SwiftUI

struct CustomSwipeScrollView: View {
    @State private var items = ["첫 번째 항목", "두 번째 항목", "세 번째 항목"]
    
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 12) {
                ForEach(items, id: \.self) { item in
                    Text(item)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding()
                        .background(Color.gray.opacity(0.1))
                        .cornerRadius(8)
                        .swipeActionsContainer(edge: .trailing) {
                            Button(role: .destructive) {
                                deleteItem(item)
                            } label: {
                                Label("삭제", systemImage: "trash")
                            }
                            
                            Button {
                                archiveItem(item)
                            } label: {
                                Label("보관", systemImage: "archivebox")
                            }
                            .tint(.blue)
                        }
                }
            }
            .padding()
        }
    }
    
    private func deleteItem(_ item: String) {
        items.removeAll { $0 == item }
    }
    
    private func archiveItem(_ item: String) {
        // 보관 처리 로직
    }
}
  • edge 파라미터를 통해 .leading 또는 .trailing 방향을 지정합니다.
  • Button(role: .destructive) 사용 시 기본 시스템 삭제 스타일이 적용됩니다.

 

2. LazyVStack 커스텀 레이아웃 드래그 정렬 (reorderable)

reorderable 모디파이어를 활성화하면 List를 사용하지 않고도 요소의 순서를 드래그하여 변경할 수 있습니다.

import SwiftUI

struct ReorderableLazyVStack: View {
    @State private var tasks = ["수업 듣기", "운동하기", "장보기", "코드 리뷰"]
    
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 10) {
                ForEach($tasks, id: \.self) { $task in
                    HStack {
                        Image(systemName: "line.3.horizontal")
                            .foregroundColor(.gray)
                        Text(task)
                        Spacer()
                    }
                    .padding()
                    .background(Color.white)
                    .cornerRadius(10)
                    .shadow(radius: 1)
                    .reorderable(by: $tasks, item: task)
                }
            }
            .reorderContainer()
            .padding()
        }
    }
}
  • 개별 아이템 뷰에 .reorderable(by:item:)을 선언하고, 바인딩된 데이터 배열을 전달합니다.
  • 부모 컨테이너(LazyVStack 등)에 .reorderContainer()를 추가하여 드래그 상호작용 제스처 범위를 정의합니다.

 

3. 실무 적용 시 주의사항 및 버전 호환성

이전 iOS 버전을 지원해야 하는 프로젝트에서는 @available 속성을 활용해 조건부 처리를 작성해야 합니다.

if #available(iOS 20.0, *) {
    contentView
        .swipeActionsContainer(edge: .trailing) {
            // iOS 20 이상 신규 API
        }
} else {
    // iOS 19 이하 대안 코드 또는 List 사용
}
  • 식별자 고유성: ForEach 구문 사용 시 데이터 요소의 id가 명확히 구분되어야 드래그 정렬 시 애니메이션 튐 현상이 발생하지 않습니다.
  • 제스처 충돌: ScrollView 자체의 horizontal 스크롤 제스처와 swipeActionsContainer가 충돌하지 않도록 vertical 방향 스크롤 뷰에서 주로 사용할 것을 권장합니다.

 

핵심 요약

  1. iOS 20 및 Xcode 27부터 List 없이 ScrollView 및 LazyVStack에서 swipeActionsContainer를 통해 스와이프 액션을 구현할 수 있습니다.
  2. reorderable 및 reorderContainer 모디파이어를 결합하여 커스텀 레이아웃 기반 드래그 앤 드롭 정렬이 가능합니다.
  3. 하위 버전 호환성을 위해 @available(iOS 20.0, *) 기법을 통한 조건부 분기 처리가 필요합니다.
반응형
Posted by 까칠코더
,