SwiftUI. Property wrappers

Обертки свойство SwiftUI

Справочник

Выбор property wrappers

При выборе обертки свойства вы можете воспользоваться следующей схемой

Данная схемая является переработанной схемой Криса Эйдхофа

@State

Каждое изменение значения, помеченного с помощью @State приводит к повторному вычислению свойства body представления, а соответственно и к перерисовке интерфейса.
@State private var isEnable: Bool = false
Данное свойство подходит только для простых типов, вроде Int, Bool, String и т.д. При отслеживании более сложных значений (например, кастомных типов) может привести к непредсказуемому поведению.
Позволяет управлять @State-свойством родительского представления. Представление, получившее @Binding-свойство, может как считывать его значение, а значит реагировать на изменения, так и изменять его самостоятельно, передавая это изменение в представление-источник. Таким образом между @State-свойством и @Binding-свойством создается двухсторонняя связь, и изменение одного влечет за собой изменение другого. Рассмотрим пример. Существует View, которое должно иметь свойство, связанное со @State-свойством родителя. Для этого такое свойство помечается с помощью обертки @Binding.
struct SecondView: View {
  @Binding var intValue: Int

  var body: some View {
    Button("Increment") {
      intValue += 1
    }
  }
}
Теперь при создании экземпляра SecondView нам необходимо передать @State-свойство.
struct StateView: View {
  @State private var intValue = 0

  var body: some View {
    VStack {
      Text("intValue equals \(intValue)")

      SecondView(intValue: $intValue)
    }
  }
}
Обратите внимание, что в данном случае параметр intValue передается по ссылке, с помощью $.
Теперь изменение свойства intValue в любом из представлений повлечет за его обновление и в другом предсталвении, а значит и перестройку body.

@StateObject

Если некоторый кастомный тип подписан на протокол ObservableObject и имеет одно или несколько свойств-издателей, то при изменении любого из них происходит повторное вычисление свойства body представления, а соответственно и перерисовка интерфейса.
// Тип со свойством-издателем
class TestObject: ObservableObject {
    @Published var num: Int = 0
}
// Представление
struct StateObjectTestView: View {
    @StateObject var stateObject = TestObject()
    
    var body: some View {
        VStack {
            Text("State object: \(stateObject.num)")
            Button("Increase state object", action: {
                stateObject.num += 1
                print("State object: \(stateObject.num)")
            })
        }
    }
}
При нажатии на кнопку Button будет увеличиваться значение свойства num объекта stateObject. И так как данное свойство являестя издателем, тип подписан на ObservableObject, а его экземпляр обернут @StateObject, то вид будет перерисовываться. Срок жизни объекта @StateObject такой же, как и у связанного с ним представления. Это значит, что если мы перейдем через NavigationLink к представлению, изменим значения объекта @StateObject, вернемся к NavigationView, потом вновь перейдем к представлению с данным @StateObject-объектом, то все изменения будут сброшены. Это корректное поведение.

@ObservedObject

Оборачивает свойство, в которое может быть помещен объект из родительского представления. Например, в родительском представлении создан @StateObject-объект, и есть необходимость передавать его в дочернее представление при его открытии. Открытие дочернего представления и передача объекта:
NavigationLink("To child", destination: ChildView(observedObject: stateObject))
Использование переданного объекта в дочернем представлении:
struct ChildView: View {
    @ObservedObject var observedObject: TestObject
    
    var body: some View {
        VStack {
            Text("ObservedObject: \(observedObject.num)")
            Button("Increase observedObject", action: {
                observedObject.num += 1
                print("ObservedObject \(observedObject.num)")
            })
        }
    }
}

@EnvironmentObject

Как и @ObservedObject, позволяет передать наблюдаемый объект из родительского представления в дочерние. Но в данном случае нет необходимости непосредственно передачи. Для использования @ObservedObject необходимо:
  • В родительском представлении вызвать метод environmentObject и передать ему наблюдаемый объект.
  • @main
    struct TestApp: App {
        @StateObject var environmentObject = TestObject()
        var body: some Scene {
            WindowGroup {
                ContentView().environmentObject(environmentObject)
            }
        }
    }
  • В дочернем представлении обернуть свойство с помощью @ObservedObject. В нем будет находиться экземпляр наблюдаемого объекта.
  • @EnvironmentObject var environmentObject: TestObject
Теперь при каждом изменении environmentObject вид будет перерисовываться. В одной иерархии представлений может быть только один environmentObject-объект конкретного типа данных. То есть поиск наблюдаемого объекта происходит именно по типу данных.

@Environment

Позволяет получить доступ к глобальным свойствам приложения или отдельных представлений, таким, как работает ли приложения в светлом или темном режиме, контекст управляемого объекта core data, режим отображения представления и т.д.

colorScheme

Позволяет определить текущую цветовую схему приложения
@Environment(\.colorScheme) var colorScheme: ColorScheme
if colorScheme == .dark { // Checks the wrapped value.
    DarkContent()
} else {
    LightContent()
}

presentationMode

Позволяет получить и изменить режим представления текущего View
@Environment(\.presentationMode) var presentationMode
presentationMode.wrappedValue.dismiss()

Позволяет отслеживать принимает ли представление пользовательский ввод (другими словами, на каком представлении сейчас находится фокус ввода). Есть два варианта использования обертки: со значением типа Bool, или с перечислением.

Значение типа Bool

Используется для отслеживания фокуса конкретного представления
@FocusState private var isUsernameFocused: Bool
@State private var username = "Anonymous"
TextField("Enter your username", text: $username)
	.focused($isUsernameFocused)

Перечисление

Используется для отслеживания фокуса нескольких представлений
enum FocusedField {
	case username, password
}
@FocusState private var focusedField: FocusedField?
TextField("Enter your username", text: $username)
	.focused($focusedField, equals: .username)

SecureField("Enter your password", text: $password)
	.focused($focusedField, equals: .password)

Добавить комментарий