iOS Mobile Инженер

iOS Mobile Инженер

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

Button и обработка нажатий

SwiftUIViews BasicsBuilt-in Views

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

Button — интерактивная view в SwiftUI для обработки нажатий. Она состоит из action (замыкание с кодом действия) и label (визуальное содержимое кнопки).

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

  • Button(action:label:) — основной инициализатор с действием и содержимым
  • Button("Title") — сокращённый синтаксис для текстовой кнопки
  • buttonStyle() — модификатор для изменения стиля кнопки
  • disabled() — отключение кнопки
  • Любая view как label — кнопка может содержать текст, иконку или любую комбинацию views

Плюсы

  • Простой декларативный синтаксис
  • Встроенные стили (.borderedProminent, .bordered, .plain)
  • Автоматическая обработка accessibility
  • Гибкое содержимое кнопки

Минусы

  • Ограниченная кастомизация встроенных стилей
  • Для сложных кнопок нужны кастомные ButtonStyle
  • Нет встроенной поддержки long press (нужен gesture)

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

  • Забывают про @State для изменения состояния в action
  • Путают Button с onTapGesture (Button имеет встроенную accessibility)
  • Не знают про .buttonStyle() модификатор
  • Делают бизнес-логику прямо в action вместо выноса в метод

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

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

В отличие от UIButton в UIKit, SwiftUI Button — это декларативная view, которая автоматически обрабатывает accessibility, highlight state и platform-specific стили.


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

Анатомия Button

swift
Button(action: {
    // Код, выполняемый при нажатии
}, label: {
    // Визуальное содержимое кнопки
})

Способы создания Button

Code Example 1: Какие способы создания Button существуют?

swift
// 1. Полный синтаксис
Button(action: {
    print("Нажато")
}, label: {
    Text("Нажми меня")
})
 
// 2. Сокращённый с trailing closure
Button {
    print("Нажато")
} label: {
    Text("Нажми меня")
}
 
// 3. Для простой текстовой кнопки
Button("Нажми меня") {
    print("Нажато")
}
 
// 4. С ролью (iOS 15+)
Button("Удалить", role: .destructive) {
    print("Удаление")
}

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

Базовая кнопка с действием

Code Example 2: Как создать кнопку, которая изменяет состояние?

swift
struct CounterView: View {
    @State private var count = 0
 
    var body: some View {
        VStack(spacing: 20) {
            Text("Счётчик: \(count)")
                .font(.largeTitle)
 
            Button("Увеличить") {
                count += 1
            }
 
            Button("Сбросить") {
                count = 0
            }
        }
    }
}

Стили кнопок

Code Example 3: Какие встроенные стили кнопок есть в SwiftUI?

swift
struct ButtonStyles: View {
    var body: some View {
        VStack(spacing: 20) {
            Button("Automatic") { }
                .buttonStyle(.automatic)
 
            Button("Bordered") { }
                .buttonStyle(.bordered)
 
            Button("Bordered Prominent") { }
                .buttonStyle(.borderedProminent)
 
            Button("Borderless") { }
                .buttonStyle(.borderless)
 
            Button("Plain") { }
                .buttonStyle(.plain)
        }
    }
}

Кнопка с иконкой и текстом

Code Example 4: Как создать кнопку с иконкой?

swift
struct IconButtons: View {
    var body: some View {
        VStack(spacing: 20) {
            // Иконка + текст
            Button {
                // действие
            } label: {
                Label("Добавить", systemImage: "plus.circle.fill")
            }
            .buttonStyle(.borderedProminent)
 
            // Только иконка
            Button {
                // действие
            } label: {
                Image(systemName: "heart.fill")
                    .font(.title)
            }
 
            // Кастомное содержимое
            Button {
                // действие
            } label: {
                HStack {
                    Image(systemName: "cart.fill")
                    Text("Купить")
                    Text("$9.99")
                        .fontWeight(.bold)
                }
                .padding()
                .background(Color.blue)
                .foregroundStyle(.white)
                .cornerRadius(10)
            }
        }
    }
}

Отключение кнопки

Code Example 5: Как отключить кнопку по условию?

swift
struct DisabledButton: View {
    @State private var username = ""
 
    var body: some View {
        VStack(spacing: 20) {
            TextField("Имя пользователя", text: $username)
                .textFieldStyle(.roundedBorder)
 
            Button("Продолжить") {
                print("Отправка: \(username)")
            }
            .buttonStyle(.borderedProminent)
            .disabled(username.isEmpty)
        }
        .padding()
    }
}

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

Кнопка с confirmation dialog

swift
struct DeleteButton: View {
    @State private var showingConfirmation = false
 
    var body: some View {
        Button("Удалить", role: .destructive) {
            showingConfirmation = true
        }
        .confirmationDialog(
            "Вы уверены?",
            isPresented: $showingConfirmation,
            titleVisibility: .visible
        ) {
            Button("Удалить", role: .destructive) {
                // удаление
            }
            Button("Отмена", role: .cancel) { }
        }
    }
}

Async action в кнопке

swift
struct AsyncButton: View {
    @State private var isLoading = false
 
    var body: some View {
        Button {
            Task {
                isLoading = true
                await performAsyncAction()
                isLoading = false
            }
        } label: {
            if isLoading {
                ProgressView()
            } else {
                Text("Загрузить")
            }
        }
        .disabled(isLoading)
    }
 
    func performAsyncAction() async {
        try? await Task.sleep(nanoseconds: 2_000_000_000)
    }
}

Кастомный ButtonStyle

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

swift
struct ScaleButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
            .opacity(configuration.isPressed ? 0.8 : 1.0)
            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
    }
}
 
// Использование
Button("Масштабируемая кнопка") { }
    .buttonStyle(ScaleButtonStyle())

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

АспектОписание
Accessibility✅ Автоматическая поддержка VoiceOver
Стилизация✅ Встроенные стили + кастомные ButtonStyle
Гибкость✅ Любой контент как label
Long press⚠️ Нужен отдельный gesture
Сложные стили⚠️ Требуют кастомный ButtonStyle

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

Q: Чем Button отличается от onTapGesture?

Button — это полноценный интерактивный элемент с accessibility поддержкой, highlight эффектом и role. onTapGesture — просто обработчик жеста, не определяет элемент как интерактивный для accessibility.

Q: Как сделать кнопку на всю ширину экрана?

Используйте .frame(maxWidth: .infinity) на label или оберните в контейнер с .frame.

Q: Что такое role у Button?

role определяет семантику кнопки: .destructive — деструктивное действие (красный цвет), .cancel — отмена. Это влияет на стиль и accessibility.

Q: Почему кнопка не реагирует на нажатие?

Возможные причины: .disabled(true), .allowsHitTesting(false), перекрытие другой view, или view с нулевым размером.


Источники

Code Example 1: Creating buttons

❓ Какие способы создания Button существуют?

swift
Button(action: {
    print("Нажато")
}, label: {
    Text("Нажми меня")
})
 
Button {
    print("Нажато")
} label: {
    Text("Нажми меня")
}
 
Button("Нажми меня") {
    print("Нажато")
}
 
Button("Удалить", role: .destructive) {
    print("Удаление")
}

Code Example 2: Button with state

❓ Как создать кнопку, которая изменяет состояние?

swift
struct CounterView: View {
    @State private var count = 0
 
    var body: some View {
        VStack(spacing: 20) {
            Text("Счётчик: \(count)")
                .font(.largeTitle)
 
            Button("Увеличить") {
                count += 1
            }
 
            Button("Сбросить") {
                count = 0
            }
        }
    }
}

Code Example 3: Button styles

❓ Какие встроенные стили кнопок есть в SwiftUI?

swift
struct ButtonStyles: View {
    var body: some View {
        VStack(spacing: 20) {
            Button("Automatic") { }
                .buttonStyle(.automatic)
 
            Button("Bordered") { }
                .buttonStyle(.bordered)
 
            Button("Bordered Prominent") { }
                .buttonStyle(.borderedProminent)
 
            Button("Borderless") { }
                .buttonStyle(.borderless)
 
            Button("Plain") { }
                .buttonStyle(.plain)
        }
    }
}

Code Example 4: Button with icon

❓ Как создать кнопку с иконкой?

swift
struct IconButtons: View {
    var body: some View {
        VStack(spacing: 20) {
            Button {
            } label: {
                Label("Добавить", systemImage: "plus.circle.fill")
            }
            .buttonStyle(.borderedProminent)
 
            Button {
            } label: {
                Image(systemName: "heart.fill")
                    .font(.title)
            }
 
            Button {
            } label: {
                HStack {
                    Image(systemName: "cart.fill")
                    Text("Купить")
                    Text("$9.99")
                        .fontWeight(.bold)
                }
                .padding()
                .background(Color.blue)
                .foregroundStyle(.white)
                .cornerRadius(10)
            }
        }
    }
}

Code Example 5: Disabled button

❓ Как отключить кнопку по условию?

swift
struct DisabledButton: View {
    @State private var username = ""
 
    var body: some View {
        VStack(spacing: 20) {
            TextField("Имя пользователя", text: $username)
                .textFieldStyle(.roundedBorder)
 
            Button("Продолжить") {
                print("Отправка: \(username)")
            }
            .buttonStyle(.borderedProminent)
            .disabled(username.isEmpty)
        }
        .padding()
    }
}

Code Example 6: Custom ButtonStyle

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

swift
struct ScaleButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)
            .opacity(configuration.isPressed ? 0.8 : 1.0)
            .animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
    }
}
 
Button("Масштабируемая кнопка") { }
    .buttonStyle(ScaleButtonStyle())