Хабрахабр

[Перевод] Понимаем Property Wrappers в SwiftUI

Перевод статьи подготовлен специально для студентов курса «iOS Разработчик. Продвинутый курс v 2.0.»

На прошлой неделе мы начали новую серию постов о фреймворке SwiftUI. Сегодня я хочу продолжить эту тему, рассказав о Property Wrappers в SwiftUI. SwiftUI предоставляет нам обертки свойств @State, @Binding, @ObservedObject, @EnvironmentObject и @Environment. Итак, давайте попытаемся понять разницу между ними и когда, почему и какую из них мы должны использовать.

Property Wrappers

Property Wrappers (далее “обертки свойств”) описаны в предложении SE-0258. Основная идея — обернуть свойства логикой, которая может быть извлечена в отдельную структуру для повторного использования в кодовой базе.

State

@State — это обертка, которую мы можем использовать для обозначения состояния View. SwiftUI будет хранить ее в специальной внутренней памяти вне структуры View. Только связанный View может получить к ней доступ. Как только значение свойства @State изменяется, SwiftUI перестраивает View для учета изменений состояния. Вот простой пример.

struct ProductsView: View , label: { Text("Change filter") } ) ForEach(products) { product in if !self.showFavorited || product.isFavorited { Text(product.title) } } } }
}

В приведенном выше примере у нас есть простой экран с кнопкой и списком продуктов. Как только мы нажимаем на кнопку, она меняет значение свойства state, и SwiftUI перестраивает View.

@Binding

@Binding предоставляет доступ по ссылке для типа-значения. Иногда нам нужно сделать состояние нашего View доступным для его детей. Но мы не можем просто взять и передать это значение, поскольку это тип-значение, и Swift передаст копию этого значения. Вот где приходит на помощь обертка свойства @Binding.

struct FilterView: View { @Binding var showFavorited: Bool var body: some View { Toggle(isOn: $showFavorited) { Text("Change filter") } }
} struct ProductsView: View { let products: [Product] @State private var showFavorited: Bool = false var body: some View { List { FilterView(showFavorited: $showFavorited) ForEach(products) { product in if !self.showFavorited || product.isFavorited { Text(product.title) } } } }
}

Мы используем @Binding чтобы отметить свойство showFavorited внутри FilterView. Мы также используем специальный символ $ для передачи привязываемой ссылки, потому что без $ Swift передаст копию значения вместо передачи самой привязываемой ссылки. FilterView может считывать и записывать значение свойства showFavorited в ProductsView, но не может следить за изменениями, используя эту привязку. Как только FilterView изменяет значение свойства showFavorited, SwiftUI воссоздает ProductsView и FilterView как его дочерний элемент.

@ObservedObject

@ObservedObject работает схоже со @State, но основное отличие состоит в том, что мы можем разделить его между несколькими независимыми View, которые могут подписываться и наблюдать за изменениями этого объекта, и как только изменения появляются, SwiftUI перестраивает все представления, связанные с этим объектом. Давайте рассмотрим пример.

import Combine final class PodcastPlayer: ObservableObject { @Published private(set) var isPlaying: Bool = false func play() { isPlaying = true } func pause() { isPlaying = false }
}

Здесь у нас есть класс PodcastPlayer, который делят между собой экраны нашего приложения. На каждом экране должна отображаться плавающая кнопка паузы в случае, когда приложение воспроизводит эпизод подкаста. SwiftUI отслеживает изменения в ObservableObject с помощью обертки @Published, и как только свойство, помеченное как @Published изменится, SwiftUI перестраивает все View, связанные с этим объектом PodcastPlayer. Здесь мы используем обертку @ObservedObject для привязки нашего EpisodesView к классу PodcastPlayer

struct EpisodesView: View { @ObservedObject var player: PodcastPlayer let episodes: [Episode] var body: some View { List { Button( action: { if self.player.isPlaying { self.player.pause() } else { self.player.play() } }, label: { Text(player.isPlaying ? "Pause": "Play") } ) ForEach(episodes) { episode in Text(episode.title) } } }
}

@EnvironmentObject

Вместо передачи ObservableObject через init-метод нашего View, мы можем неявно внедрить его в Environment нашей View-иерархии. Делая это, мы создаем возможность для всех дочерних представлений текущей Environment обращаться к этому ObservableObject.

class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let window = UIWindow(frame: UIScreen.main.bounds) let episodes = [ Episode(id: 1, title: "First episode"), Episode(id: 2, title: "Second episode") ] let player = PodcastPlayer() window.rootViewController = UIHostingController( rootView: EpisodesView(episodes: episodes) .environmentObject(player) ) self.window = window window.makeKeyAndVisible() }
} struct EpisodesView: View { @EnvironmentObject var player: PodcastPlayer let episodes: [Episode] var body: some View { List { Button( action: { if self.player.isPlaying { self.player.pause() } else { self.player.play() } }, label: { Text(player.isPlaying ? "Pause": "Play") } ) ForEach(episodes) { episode in Text(episode.title) } } }
}

Как видите, мы должны передать объект PodcastPlayer через модификатор environmentObject нашего View. Делая это, мы можем легко получить доступ к PodcastPlayer, определив его с помощью обертки @EnvironmentObject. @EnvironmentObject использует функцию динамического поиска членов, чтобы найти экземпляр класса PodcastPlayer в Environment, поэтому вам не нужно передавать его через init-метод EpisodesView. Environment является правильным способом внедрения зависимостей в SwiftUI.

@Environment

Как мы уже говорили в предыдущей главе, мы можем передавать пользовательские объекты в Environment View-иерархии внутри SwiftUI. Но SwiftUI уже имеет Environment, заполненную общесистемными настройками. Мы можем легко получить к ним доступ с помощью обертки @Environment.

struct CalendarView: View { @Environment(\.calendar) var calendar: Calendar @Environment(\.locale) var locale: Locale @Environment(\.colorScheme) var colorScheme: ColorScheme var body: some View { return Text(locale.identifier) }
}

Помечая наши свойства оберткой @Environment, мы получаем доступ и подписываемся на изменения общесистемных настроек. Как только Locale, Calendar или ColorScheme системы меняются, SwiftUI воссоздает наш CalendarView.

Заключение

Сегодня мы поговорили о Property Wrappers, предоставляемых SwiftUI. @State, @Binding, @EnvironmentObject и @ObservedObject играют огромную роль в SwiftUI-разработке. Спасибо за внимание!

Теги
Показать больше

Похожие статьи

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть