iOS Mobile Инженер

iOS Mobile Инженер

Роадмап навыков для прокачки

Основы ScrollView

SwiftUILists & CollectionsScrollView

Основная идея

ScrollView — контейнер, который позволяет прокручивать содержимое, если оно не помещается на экран. Это базовый компонент для создания скроллируемых интерфейсов.

Ключевые аспекты

  • ScrollView(.vertical) — вертикальный скролл (по умолчанию)
  • ScrollView(.horizontal) — горизонтальный скролл
  • ScrollView([.vertical, .horizontal]) — скролл в обоих направлениях
  • showsIndicators — показ/скрытие индикаторов скролла
  • LazyVStack/LazyHStack — lazy loading содержимого

Плюсы

  • Полный контроль над содержимым
  • Поддержка горизонтального скролла
  • Работает с любыми views
  • Комбинируется с LazyVStack для производительности

Минусы

  • Не lazy по умолчанию (все views создаются сразу)
  • Нет встроенных стилей как у List
  • Требует явного задания направления

Частые ошибки на собеседованиях

  • Забывают про LazyVStack для длинных списков
  • Не знают разницу между ScrollView и List
  • Путают оси скролла
  • Используют ScrollView где достаточно List

Введение и проблематика

Когда содержимое не помещается на экран, нужна возможность прокрутки. ScrollView — базовый контейнер SwiftUI для создания скроллируемых областей.

В отличие от List, ScrollView не предоставляет стилизацию ячеек, но даёт полный контроль над содержимым и поддерживает горизонтальный скролл.


Базовая теория

Базовое использование

Code Example 1: Как создать вертикальный ScrollView?

swift
ScrollView {
    VStack(spacing: 20) {
        ForEach(0..<50) { index in
            Text("Элемент \(index)")
                .frame(maxWidth: .infinity)
                .padding()
                .background(Color.blue.opacity(0.2))
                .cornerRadius(10)
        }
    }
    .padding()
}

Параметры ScrollView

ПараметрОписание
.verticalВертикальный скролл (по умолчанию)
.horizontalГоризонтальный скролл
showsIndicatorsПоказывать ли полосы прокрутки

Практические примеры

Горизонтальный скролл

Code Example 2: Как создать горизонтальную карусель?

swift
struct HorizontalCarousel: View {
    let items = ["Фото 1", "Фото 2", "Фото 3", "Фото 4", "Фото 5"]
 
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 16) {
                ForEach(items, id: \.self) { item in
                    RoundedRectangle(cornerRadius: 12)
                        .fill(Color.blue)
                        .frame(width: 200, height: 150)
                        .overlay(
                            Text(item)
                                .foregroundStyle(.white)
                        )
                }
            }
            .padding(.horizontal)
        }
    }
}

LazyVStack для производительности

Code Example 3: Как оптимизировать длинный список?

swift
struct LazyScrollView: View {
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 12) {
                ForEach(0..<1000) { index in
                    Text("Элемент \(index)")
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(Color.gray.opacity(0.1))
                        .cornerRadius(8)
                }
            }
            .padding()
        }
    }
}
⚠️

Без LazyVStack все 1000 элементов создадутся сразу, что приведёт к тормозам. LazyVStack создаёт views только при появлении на экране.

Двунаправленный скролл

Code Example 4: Как создать скролл в обоих направлениях?

swift
struct BiDirectionalScroll: View {
    var body: some View {
        ScrollView([.horizontal, .vertical]) {
            VStack(spacing: 20) {
                ForEach(0..<20) { row in
                    HStack(spacing: 20) {
                        ForEach(0..<10) { col in
                            RoundedRectangle(cornerRadius: 8)
                                .fill(Color.blue)
                                .frame(width: 100, height: 100)
                                .overlay(
                                    Text("\(row),\(col)")
                                        .foregroundStyle(.white)
                                )
                        }
                    }
                }
            }
            .padding()
        }
    }
}

Пограничные кейсы

Scroll to position (iOS 14+)

Code Example 5: Как программно прокрутить к элементу?

swift
struct ScrollToExample: View {
    var body: some View {
        ScrollViewReader { proxy in
            VStack {
                Button("К элементу 50") {
                    withAnimation {
                        proxy.scrollTo(50, anchor: .center)
                    }
                }
 
                ScrollView {
                    LazyVStack {
                        ForEach(0..<100) { index in
                            Text("Элемент \(index)")
                                .id(index)
                                .frame(maxWidth: .infinity)
                                .padding()
                                .background(index == 50 ? Color.yellow : Color.gray.opacity(0.1))
                        }
                    }
                }
            }
        }
    }
}

ScrollView vs List

swift
// ScrollView — полный контроль
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            CustomCard(item: item)
        }
    }
}
 
// List — встроенные стили
List(items) { item in
    Text(item.name)
}

Плюсы и минусы

АспектОписание
Гибкость✅ Любой контент, любой стиль
Направление✅ Вертикальный и горизонтальный
Производительность⚠️ Нужен LazyVStack для оптимизации
Стилизация⚠️ Нет встроенных стилей

Вопросы интервьюера

Q: Чем ScrollView отличается от List?

ScrollView — низкоуровневый контейнер без стилей. List — высокоуровневый с платформенным стилем, секциями, свайпами.

Q: Почему нужен LazyVStack?

Без него все views создаются сразу при загрузке. LazyVStack создаёт views только когда они появляются на экране.

Q: Как скрыть индикаторы скролла?

ScrollView(showsIndicators: false) { }.

Q: Можно ли определить позицию скролла?

Да, с помощью ScrollViewReader и .scrollPosition() (iOS 17+).


Источники

Code Example 1: Vertical ScrollView

❓ Как создать вертикальный ScrollView?

swift
ScrollView {
    VStack(spacing: 20) {
        ForEach(0..<50) { index in
            Text("Элемент \(index)")
                .frame(maxWidth: .infinity)
                .padding()
                .background(Color.blue.opacity(0.2))
                .cornerRadius(10)
        }
    }
    .padding()
}

Code Example 2: Horizontal carousel

❓ Как создать горизонтальную карусель?

swift
struct HorizontalCarousel: View {
    let items = ["Фото 1", "Фото 2", "Фото 3"]
 
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 16) {
                ForEach(items, id: \.self) { item in
                    RoundedRectangle(cornerRadius: 12)
                        .fill(Color.blue)
                        .frame(width: 200, height: 150)
                        .overlay(Text(item).foregroundStyle(.white))
                }
            }
            .padding(.horizontal)
        }
    }
}

Code Example 3: LazyVStack for performance

❓ Как оптимизировать длинный список?

swift
struct LazyScrollView: View {
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 12) {
                ForEach(0..<1000) { index in
                    Text("Элемент \(index)")
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(Color.gray.opacity(0.1))
                }
            }
            .padding()
        }
    }
}

Code Example 4: Bi-directional scroll

❓ Как создать скролл в обоих направлениях?

swift
struct BiDirectionalScroll: View {
    var body: some View {
        ScrollView([.horizontal, .vertical]) {
            VStack(spacing: 20) {
                ForEach(0..<20) { row in
                    HStack(spacing: 20) {
                        ForEach(0..<10) { col in
                            RoundedRectangle(cornerRadius: 8)
                                .fill(Color.blue)
                                .frame(width: 100, height: 100)
                        }
                    }
                }
            }
        }
    }
}

Code Example 5: Scroll to position

❓ Как программно прокрутить к элементу?

swift
struct ScrollToExample: View {
    var body: some View {
        ScrollViewReader { proxy in
            VStack {
                Button("К элементу 50") {
                    withAnimation {
                        proxy.scrollTo(50, anchor: .center)
                    }
                }
 
                ScrollView {
                    LazyVStack {
                        ForEach(0..<100) { index in
                            Text("Элемент \(index)")
                                .id(index)
                        }
                    }
                }
            }
        }
    }
}