Что такое SwiftUI и зачем он нужен?
SwiftUI — это современный, декларативный фреймворк для построения пользовательских интерфейсов на всех платформах Apple: iOS, macOS, watchOS и tvOS. В отличие от традиционного подхода UIKit (где вы вручную управляете состоянием и обновлением вьюх), SwiftUI позволяет описывать, каким должен быть интерфейс, а не как его построить. Фреймворк автоматически отслеживает изменения данных и перерисовывает только те части интерфейса, которые изменились.
Зачем он нужен?
- Меньше кода: Один и тот же код работает на всех платформах Apple.
- Декларативность: Вы описываете интерфейс как набор простых структур, а не как последовательность команд.
- Автоматическое управление состоянием: @State, @Binding, @ObservableObject — SwiftUI сам решает, когда перерисовывать интерфейс.
- Живой превью: В Xcode вы видите изменения в реальном времени.
- Анимации: Добавление анимаций — это одна строка кода.
SwiftUI — это не библиотека, которую нужно устанавливать отдельно. Это встроенный фреймворк, доступный с iOS 13, macOS 10.15, watchOS 6 и tvOS 13. Он поставляется вместе с Xcode и SDK Apple.
Установка (Настройка проекта)
Поскольку SwiftUI — это часть стандартного SDK Apple, его не нужно устанавливать через pip, npm или CocoaPods. Всё, что вам нужно — это Xcode (версия 11 или новее) и проект, настроенный на iOS 13+ / macOS 10.15+.
Как создать новый проект с SwiftUI:
- Откройте Xcode.
- Выберите File → New → Project.
- Выберите шаблон "App" (или "SwiftUI App" в более старых версиях).
- Убедитесь, что в поле "Interface" выбран "SwiftUI".
- Нажмите "Next" и создайте проект.
Если вы работаете с существующим проектом UIKit, вы можете добавить SwiftUI файл (File → New → File → SwiftUI View). SwiftUI и UIKit могут сосуществовать в одном проекте через UIHostingController.
Быстрый старт с примером кода
Давайте создадим простое приложение "Счётчик кликов". Это классический пример, который показывает базовые концепции SwiftUI: состояние, кнопки и текст.
import SwiftUI
struct ContentView: View {
// @State — это свойство, которое хранит состояние и автоматически перерисовывает View при его изменении
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Счётчик: \(count)")
.font(.largeTitle)
.padding()
HStack(spacing: 20) {
Button("Увеличить") {
count += 1
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
Button("Сбросить") {
count = 0
}
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(10)
}
}
}
}
// Точка входа в приложение
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Объяснение:
@State— говорит SwiftUI, что это свойство нужно отслеживать. При измененииcount, SwiftUI автоматически перерисовываетContentView.VStackиHStack— это контейнеры для вертикального и горизонтального расположения элементов.Button— создаёт кнопку, которая выполняет замыкание при нажатии.- Модификаторы (
.font(),.padding(),.background()) — это способ настройки внешнего вида.
Основные методы и функции SwiftUI (минимум 10)
1. @State
Что делает: Позволяет объявить изменяемое свойство, которое является источником истины для данного View. Когда значение меняется, View перерисовывается.
Параметры: Просто объявляется как @State private var имя: Тип = значение. Рекомендуется использовать private для инкапсуляции.
struct ExampleView: View {
@State private var isOn = false
var body: some View {
Toggle("Включено", isOn: $isOn)
}
}
2. @Binding
Что делает: Создаёт двустороннюю связь с состоянием, которое принадлежит другому View. Позволяет дочернему View изменять состояние родительского.
Параметры: Используется с символом $ для передачи ссылки на состояние.
struct ParentView: View {
@State private var text = ""
var body: some View {
ChildView(text: $text)
}
}
struct ChildView: View {
@Binding var text: String
var body: some View {
TextField("Введите текст", text: $text)
}
}
3. VStack, HStack, ZStack
Что делает: Контейнеры для компоновки дочерних вьюх. VStack — вертикально, HStack — горизонтально, ZStack — слоями (один поверх другого).
Параметры: spacing (расстояние между элементами), alignment (выравнивание).
struct StackExample: View {
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Верх")
HStack {
Text("Левый")
Text("Правый")
}
ZStack {
Color.red
Text("Поверх красного")
}
.frame(height: 100)
}
}
}
4. Text
Что делает: Отображает статический или динамический текст с возможностью форматирования.
Параметры: Строка или LocalizedStringKey для локализации.
struct TextExample: View {
var body: some View {
Text("Hello, World!")
.font(.title)
.fontWeight(.bold)
.foregroundColor(.blue)
.multilineTextAlignment(.center)
.lineLimit(2)
}
}
5. Image
Что делает: Отображает изображение из Assets, SF Symbols или загруженное из сети.
Параметры: Имя изображения (String) или системное имя SF Symbol.
struct ImageExample: View {
var body: some View {
Image(systemName: "star.fill")
.resizable()
.frame(width: 50, height: 50)
.foregroundColor(.yellow)
}
}
6. Button
Что делает: Создаёт интерактивную кнопку, которая выполняет действие при нажатии.
Параметры: action (замыкание), label (вьюха для отображения).
struct ButtonExample: View {
@State private var message = ""
var body: some View {
VStack {
Text(message)
Button("Нажми меня") {
message = "Кнопка нажата!"
}
.buttonStyle(.borderedProminent)
}
}
}
7. List
Что делает: Отображает прокручиваемый список данных. Аналог UITableView в UIKit.
Параметры: Массив данных, замыкание для создания строки.
struct ListExample: View {
let fruits = ["Яблоко", "Банан", "Апельсин", "Киви"]
var body: some View {
List(fruits, id: \.self) { fruit in
Text(fruit)
}
}
}
8. NavigationStack (или NavigationView для старых версий)
Что делает: Создаёт навигационный стек для перехода между экранами.
Параметры: Корневая вьюха и NavigationLink для переходов.
struct NavigationExample: View {
var body: some View {
NavigationStack {
List(1..<10) { i in
NavigationLink("Элемент \(i)", value: i)
}
.navigationDestination(for: Int.self) { value in
Text("Детали для \(value)")
.font(.largeTitle)
}
.navigationTitle("Список")
}
}
}
9. @ObservableObject и @Published
Что делает: Позволяет создать внешнюю модель данных (ViewModel), которая может быть разделена между несколькими View.
Параметры: Класс, наследующий от ObservableObject, и свойства с @Published.
class UserSettings: ObservableObject {
@Published var username = "Гость"
}
struct ProfileView: View {
@ObservedObject var settings = UserSettings()
var body: some View {
Text("Привет, \(settings.username)!")
TextField("Имя", text: $settings.username)
}
}
10. GeometryReader
Что делает: Позволяет получить информацию о размерах и положении родительского контейнера для создания адаптивных макетов.
Параметры: Замыкание, которое получает объект GeometryProxy.
struct GeometryReaderExample: View {
var body: some View {
GeometryReader { geometry in
VStack {
Text("Ширина: \(geometry.size.width)")
Text("Высота: \(geometry.size.height)")
}
.frame(width: geometry.size.width / 2, height: geometry.size.height / 2)
.background(Color.green)
}
.frame(height: 200)
}
}
11. ForEach
Что делает: Итерация по коллекции для динамического создания вьюх.
Параметры: Массив данных, идентификатор (id) и замыкание для построения вьюхи.
struct ForEachExample: View {
let colors: [Color] = [.red, .green, .blue]
var body: some View {
HStack {
ForEach(colors, id: \.self) { color in
RoundedRectangle(cornerRadius: 10)
.fill(color)
.frame(width: 50, height: 50)
}
}
}
}
Реальный практический пример: Приложение "Список задач" (To-Do List)
Давайте создадим полноценное приложение для управления задачами. Оно будет использовать все основные концепции SwiftUI: состояние, списки, навигацию, удаление и добавление элементов.
import SwiftUI
// Модель данных
struct Task: Identifiable {
let id = UUID()
var title: String
var isCompleted: Bool = false
}
// ViewModel
class TaskViewModel: ObservableObject {
@Published var tasks: [Task] = []
func addTask(title: String) {
tasks.append(Task(title: title))
}
func toggleTask(_ task: Task) {
if let index = tasks.firstIndex(where: { $0.id == task.id }) {
tasks[index].isCompleted.toggle()
}
}
func deleteTask(at offsets: IndexSet) {
tasks.remove(atOffsets: offsets)
}
}
// Главный экран
struct TaskListView: View {
@StateObject private var viewModel = TaskViewModel()
@State private var newTaskTitle = ""
var body: some View {
NavigationStack {
VStack {
// Поле для ввода новой задачи
HStack {
TextField("Новая задача", text: $newTaskTitle)
.textFieldStyle(.roundedBorder)
Button("Добавить") {
guard !newTaskTitle.isEmpty else { return }
viewModel.addTask(title: newTaskTitle)
newTaskTitle = ""
}
.buttonStyle(.borderedProminent)
}
.padding()
// Список задач
List {
ForEach(viewModel.tasks) { task in
HStack {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(task.isCompleted ? .green : .gray)
.onTapGesture {
viewModel.toggleTask(task) Также в библиотеке
Очереди задач для Rust: обзор и примеры использования
CLI фреймворки для Go (Golang): полный обзор инструментов для создания консольных приложений