Декларативный синтаксис — это современная парадигма программирования, помогающая разработчиками писать код процедурно.
Используя такой подход, разработчик описывает кодом то, что он хочет написать, не обращая внимание на то, как это будет показано/реализовано.
SwiftUI использует декларативный синтаксис, поэтому вы можете просто указать, что должен делать ваш интерфейс, например:
вы пишите кодом, что хотите видеть Image
вью внизу вашего экрана.
View
(вью) — это основной строительный блок UI. Вью наследуется от протокола View
, который является типом представляющим часть UI приложения и имеет модификаторы (modifiers), используемые для настройки.
Называются ленивыми (lazy
), потому что вью не создает все элементы сразу. Элементы выводятся на экран по мере необходимости.
List
, LazyVStack
, LazyHStack
, LazyHGrid
, LazyVGrid
, Table
.
С помощью Modifiers
(модификатора) можно добавить определенное изменение вью. SwiftUI предоставляет около 100-та собственных/встроенных модификаторов, таких как: .padding()
, .background()
и .offset()
.
Так же вы можете создать собственный модификатор, который делает что-либо с вашей вью.
Применив модификатор к вью, добавляется определенное поведение, которое вы ожидаете и возвращается новая вью.
Lazy
вью, как List
подгружают элементы UI, когда они находятся или будут находиться внутри области прокрутки.
Таким образом, не все данные из вью хранятся в текущей вью. Попробуем назвать элементы UI, которые генерируются во вью, displayables
. Вью SwiftUI
может иметь 0
или > 0
dispayables
.
Когда дело доходит до сравнения, можем выделить 4-ре основных категории:
Unary
: Вью с единственным displayable, таким как shapes, colors, controls и labels.Structural
: Вью которая принимает0
или> 0
и комбинирует их в некоторое подмножество:ForEach
,EmptyView
и вью которые строятся сViewBuilder
, такие какTupleView
и_ConditionalView
.Container
: Вью, которые принимают другие вью и управляют их расположением:HStack
,VStack
,List
,LazyVStack
.Modifiers
: Вью, которое добавляет и изменяет внешний вид и/или поведение.
Каждая иерархия View содержит примитивные View, и каждое примитивное View будет представлять собой комбинацию типов View перечисленных выше:
struct MyView: View {
@State var showSecret = false
var body: some View {
VStack {
Group {
Button("Show secret") { showSecret.toggle() }
if showSecret {
Text("Secret")
} else {
Color.red
}
}
.padding()
}
}
}
SwiftUI необходимо отслеживать иерархию вью в runtime
. Ниже схематично изображена MyView()
, которая хранит ссылки и состояние вью.
ViewModifier
- это протокол с помощью которого можно кастомизировать другой модификатор(modifier) или вью.
Вы можете объединить несколько модификатор, которые в конце вернут вью. Чтобы изменить UIView
в UIKit
, вы используете проперти вью. В UIKit вы, вероятно, используете YourView.backgroudColor = .red
, чтобы настроить UIView. Такой подход называет императивным. Для кастомизации в декларативном стиле, в SwiftUI мы просто описываем как вью должна выглядеть, используя ViewModifier
.
Проперти враппер (property wrapper) добавляет слой разделения между кодом, отвечающим за хранение свойств и кодом, отвечающим за объявление этого property wrapper.
Представим, что нужно написать метод, проверяющий потокобезопасность или хранение данных в БД. Вместо того, чтобы писать 2 разных метода, мы напишем один property wrapper и повторно применим его к нескольким свойствам:
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// Выведет "0"
rectangle.height = 24
print(rectangle.height)
// Выведет "12"
На текущий момент в SwiftUI имеется более 17-ти оберток, каждая из которых предоставляет разный функционал:
@Published
@Published
применяется к проперти внутри ObservableObject
и при изменении значения сообщает SwiftUI о перерисовки любой вью, которая использует эту проперти.
@State
@State
property wrapper используется внутри View
объекта и позволяет вашей вью реагировать на любые изменения. Данная обертка не принимает данные с других объектов. В качестве лучшей практики вы должны пометить свои проперти @State как private
. Никакие внешние источники не должны изменять ваш @State проперти. В большинстве случаев используется для простых типов данных как Int
, String
, Bool
и т.д.
@Binding
@Binding
property wrapper используется для передачи значений в дочернюю(child) вью. Вью принимающая биндинг может читать проперти, реагировать на изменения от родительской вью и имеет доступ на запись проперти.
@StateObject
@StateObject
схож со @State
, но использует более сложные типы данных и применяется к ObservableObject
. ObservableObject
принимает reference type (class) и информирует SwiftUI когда в одном из @Published
проперти произошли изменения.
⚠️ Вы должны использовать@StateObject
только один раз для каждого объекта.
@ObservedObject
@ObservedObject
схож со @StateObject
, за исключением того, что в нем не упоминается создание или хранение инстанса. ObservedObject
используется для отслеживания изменений уже созданного объекта c использованием @StateObject
.
⚠️ До созданияStateObject
использовалиObservedObject
для сохранения и хранения объектов, но это было не безопасно. ИногдаObservedObject
мог случайно освободить объект, который он хранил. Поэтому была создана проперти врапперStateObject
.
@EnvironmentObject
Временами нужно получить доступ к объекту из разных вьюх в приложении или во всех дочерних вьюхах.
Достичь такого можно с помощью @EnvironmentObject
. Проперти к которому применили EnvironmentObject
должны наследоваться от ObservableObject
протокола.
Применяем модификатор .environmentObject()
и объект доступен во всех вью, к которой применили модификатор.
@EnvironmentObject
похож на @ObservedObject
. Основное различие в том, что @EnvironmentObject
доступен в большем диапазоне, во множестве вложенных вью.
@Environment
Если вы знакомы с переменными окружения env
в Linux, то вы сразу поймете о чем идет речь.
@Environment
считывает значения окружения ОС и перерисовывает вью если значение изменяется. Чтобы применить @Environment
проперти к вью используйте .environment
модификатор.
Список всех значений доступен в документации.
@AppStorage
@AppStorage
является оберткой над UserDefaults
. Используйте обертку для хранения маленьких, простых значений.
⚠️ Не следует хранитьCVV
код от кредитной карты.
Начиная с iOS 17, разработчикам предлагается использовать новый подход для отслеживания изменений.
@Bindable
@Bindable
Документация
SwiftUI предоставляет property wrappers, которые объявляют reference type в качестве источника истины (source of truth): @ObservedObject
, @StateObject
и @EnvironmentObject
.
Для использования этих врапперов, необходимо чтобы класс стал наблюдаемым (observable).
flowchart TD
A(Reference семантика) --> B(Источник истины/Source of Truth);
B --> State("@StateObject");
B --> Environment("@EnvironmentObject");
B --> Observed("@ObservedObject")
Существует 2 враппера, которые является value
типом: @State
и @Binding
.
Из них только @State
является источником истины (source of truth):
flowchart TD
A(Value семантика) --> B(Источник истины/Source of Truth?);
B --> Y(ДА);
B --> N(НЕТ);
Y --> S("@State")
N --> Bind("@Binding")
Для этого необходимо подписать класс на протокол ObservableObject
.
Если мы хотим, чтобы проперти класса реагировало на изменения и обновляло вью, то нужно добавить атрибут @Published
:
// старый подход
class Counter: ObservableObject {
@Published
var increase: Int = 1
}
ℹ️
@Published
— это проперти враппер, но в контексте применения к свойствам класса — атрибут.
Начиная с iOS 17, разработчикам предлагается использовать новый макрос @Observable
:
// новый подход
@Observable class Counter {
var increase: Int = 1
}
Документация по макросу Observable
Во время выполнения приложения, сцены могут переходить между тремя состояниями:
активная
— сцена активна и пользователь может взаимодействовать с ней.неактивная
— сцена видна, но взаимодействие с ней отключено системой. Например, в режиме мультизадачности, вы можете увидеть список приложений, но эта панель не является активной.фон
— Приложение работает, но сцена не видна в UI. Сцена переходит в это состояние перед завершением работы приложения.
Узнать текущую сцену можно из значения среды scenePhase
.
SwiftUI включает три модификатора, которые реагируют на события жизненного цикла вью:
onAppear(perform:)
выполняет действие при каждом появлении вью, даже если вью появилась не первый раз.onDisappear(perform:)
выполняет действие при уходе с вью.task(priority:_:)
выполняет действие в асинхронной среде при появлении вью. Перестает выполнять при уходе с вью.
⚠️ Ключевая разница междуonAppear
иtask
в том, что покидая вьюonAppear
не останавливает выполнение задачи, аtask
наоборот автоматически запускает и останавливает.