Всем привет! Меня зовут Дмитрий Демми, компания AGIMA. Мы часто разрабатываем приложения для банков или еком-продуктов. И в большинстве из них нужно заполнять поля: вписывать имя, контакты, адрес, номера документов, банковских карт или реквизиты. Иногда таких граф бывает много, и чтобы пользователям было удобно переключаться между ними, в iOS-разработке используется property wrapper @FocusState. Если вы пока не сталкивались с таким, то ниже всё объясняю и показываю.

Property wrapper @FocusState появляется в SwiftUI начиная с iOS 15. Он сильно упростил управление фокусом для view и улучшил взаимодействие пользователя с приложением. Ниже расскажу, как создать UI-элемент, который включает в себя @FocusState и модификатор .toolbar для переключения фокуса между полями ввода.
Его можно переиспользовать в разных частях приложения. Это снижает дублирование кода, что особенно важно на проектах с большим количеством форм, анкет и других подобных решений, перечисленных выше.
Первым делом создадим view-контейнер для полей ввода, в котором настроим кнопки для toolbar-клавиатуры.
struct ContainerView<Content: View>: View { @ViewBuilder let content: () -> Content var body: some View { VStack { content() } .toolbar { ToolbarItem(placement: .keyboard) { toolbarItem } } } var toolbarItem: some View { HStack { Spacer() Button("Назад") { moveToPreviousField() } Button("Далее") { moveToNextField() } } }
Далее добавляем PreferenceKey и EnvironmentKey для взаимодействия нашего родительского и дочерних view.
struct FocusedFieldPreferences: PreferenceKey { static var defaultValue: [UUID] = [] static func reduce(value: inout [UUID], nextValue: () -> [UUID]) { value.append(contentsOf: nextValue()) } } struct FocusFieldEnvironment: EnvironmentKey { static let defaultValue: Binding<UUID?> = .constant(nil) } extension EnvironmentValues { var focusField: Binding<UUID?> { get { self[FocusFieldEnvironment.self] } set { self[FocusFieldEnvironment.self] = newValue } } }
С помощью FocusedFieldPreferences в ContainerView будем получать массив uuid всех дочерних view, а в FocusFieldEnvironment будем передавать uuid только того дочернего view, которое должно быть в состоянии фокуса.
Теперь дорабатываем наш ContainerView:
struct ContainerView<Content: View>: View { @ViewBuilder let content: () -> Content @State var currentIndex: Int = 0 @State var childViewsIDs: [UUID?] = [] @State var focusedFieldID: UUID? var body: some View { VStack { content() } .onPreferenceChange(FocusedFieldPreferences.self) { ids in focusedFieldID = ids.first childViewsIDs = ids } .environment(\.focusField, $focusedFieldID) .toolbar { ToolbarItem(placement: .keyboard) { toolbarItem } } }
Добавляем функции для кнопок тулбара клавиатуры:
private func moveToPreviousField() { guard let currentID = focusedFieldID, let currentIndex = childViewsIDs.firstIndex(of: currentID), !childViewsIDs.isEmpty else { return } let previousIndex = (currentIndex - 1 + childViewsIDs.count) % childViewsIDs.count focusedFieldID = childViewsIDs[previousIndex] } private func moveToNextField() { guard let currentID = focusedFieldID, let currentIndex = childViewsIDs.firstIndex(of: currentID), !childViewsIDs.isEmpty else { return } let nextIndex = (currentIndex + 1) % childViewsIDs.count focusedFieldID = childViewsIDs[nextIndex] }
Последним шагом нужно добавить ViewModifier для дочерних view, которым мы хотим добавить поддержку управления фокусом.
struct FocusableModifier: ViewModifier { @State private var id = UUID() @FocusState private var isFocused: Bool @Environment(\.focusField) private var focusFromEnvironment func body(content: Content) -> some View { content .preference(key: FocusedFieldPreferences.self, value: [id]) .onChange(of: focusFromEnvironment.wrappedValue) { newValue in if newValue == id { isFocused = true } } .onChange(of: isFocused) { value in if value { focusFromEnvironment.wrappedValue = id } } .focused($isFocused) } } extension View { func focusable() -> some View { self.modifier(FocusableModifier()) }
Теперь достаточно применить этот модифаер к нужной view.
struct CustomTextField: View { var body: some View { TextField("Введите текст", text: .constant("")) .focusable() } }
В итоге получаем что-то вроде этого:

Если у вас остались вопросы, буду рад ответить. Также делитесь опытом в комментариях и подписывайтесь на телеграм-канал моего коллеги Саши Ворожищева — там всё про мобильную разработку.
Что еще почитать
ссылка на оригинал статьи https://habr.com/ru/articles/932844/
Добавить комментарий