Третья версия SwiftUI принесла нам несколько модификаторов представления (view modifiers), которые позволяют нам одинаково обрабатывать семантически похожие операции для разных представлений. Например од��им из таких модификаторов представления является onSubmit, который мы можем использовать для управления как формами, так и полями поиска (search fields). На этой неделе мы поговорим о другом модификаторе представления, который SwiftUI предоставляет нам для отображения диалоговых окон подтверждения (confirmationDialog).

Диалог подтверждения (confirmation dialog) — это очень распространенный шаблон UI/UX, который мы обычно используем для подтверждения любых опасных действий в наших приложениях. Например, мы можем выводить диалоговое окно подтверждения перед удалением каких-либо конфиденциальных данных из приложения.

struct ContentView: View {
    @StateObject var viewModel = ViewModel()

    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.messages, id: \.self) { message in
                    Text(message)
                        .swipeActions {
                            Button(
                                role: .destructive,
                                action: { 
                                    withAnimation {
                                        viewModel.delete(message) 
                                    } 
                                }
                            ) {
                                Image(systemName: "trash")
                            }
                        }
                }
            }
            .navigationTitle("Messages")
            .onAppear { viewModel.fetch() }
        }
    }
}

Как вы можете видеть в приведенном выше примере, у нас есть экран, показывающий список сообщений из модели представления. Для предоставления действий, связанных с элементом списка, мы используем свайпы. В нашем случае мы показываем деструктивную кнопку, которая удаляет сообщение, как только вы ее нажимаете. Давайте посмотрим, как мы можем вывести диалоговое окно подтверждения с помощью нового модификатора представления confirmationDialog.

struct ContentView: View {
    @StateObject var viewModel = ViewModel()
    @State private var confirmationShown = false

    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.messages, id: \.self) { message in
                    Text(message)
                        .swipeActions {
                            Button(
                                role: .destructive,
                                action: { confirmationShown = true }
                            ) {
                                Image(systemName: "trash")
                            }
                        }
                        .confirmationDialog(
                            "Are you sure?",
                             isPresented: $confirmationShown
                        ) { 
                            Button("Yes") {
                                withAnimation { 
                                    viewModel.delete(message) 
                                }
                            }
                        }
                }
            }
            .navigationTitle("Messages")
            .onAppear { viewModel.fetch() }
        }
    }
}

Мы добавляем модификатор confirmationDialog к представлению Text, которое мы хотим удалить. Для отображения диалогового окна подтверждения потребуется несколько параметров:

  1. Первый - это заголовок конкретного диалогового окна подтверждения. Это может быть Text или LocalizedStringKey.

  2. Второй - привязка к логическому значению, которое указывает, когда следует отображать диалоговое окно подтверждения.

  3. Третий - это замыкание @ViewBuilder, которое мы можем использовать для предоставления доступных действий с помощью представлений кнопок. Имейте в виду, что мы можем использовать только кнопки с текстом.

Вам не нужно предоставлять кнопку отмены. SwiftUI делает это автоматически для любого диалогового окна подтверждения. Но вы все равно можете предложить кнопку с ролью .cancel, чтобы заменить кнопку отмены по умолчанию.

.confirmationDialog("Are you sure?", isPresented: $confirmationShown) {
    Button("Yes") {
        withAnimation { 
            viewModel.delete(message) 
        }
    }

    Button("No", role: .cancel) {}
}

Вам не нужно изменять значение привязки на false чтобы закрыть диалоговое окно подтверждения. SwiftUI закрывает диалоговое окно подтверждения, как только пользователь выполняет любое из указанных действий.

Я должен упомянуть, что система может переупорядочивать кнопки в зависимости от их ролей и важности. SwiftUI применяет более высокий приоритет для действия по умолчанию. Вы можете сделать любое из предоставленных действий действием по умолчанию, используя модификатор представления keyboardShortcut.

.confirmationDialog("Are you sure?", isPresented: $confirmationShown) {
    Button("Yes") {
        withAnimation { 
            viewModel.delete(message) 
        }
    }.keyboardShortcut(.defaultAction)

    Button("No", role: .cancel) {}
}

SwiftUI обрабатывает различные среды соответственно - диалог подтверждения отображается как всплывающее окно при запуске в классах стандартного размера и в качестве списка действий в компактных классах.

Чтобы узнать больше о всплывающих окнах и списках действий в SwiftUI, почитайте мою статью «Alerts, Action Sheets, Modals and Popovers in SwiftUI».

Модификатор представления confirmationDialog также предоставляет нам возможность контролировать titleVisibility представленного диалога. Параметр titleVisibility принимает инстанс перечисления Visibility с одним из следующих значений: automatic, visible, и hidden.

.confirmationDialog(
    "Are you sure?",
    isPresented: $confirmationShown,
    titleVisibility: .visible
) {
    Button("Yes") {
        withAnimation { 
            viewModel.delete(message) 
        }
    }.keyboardShortcut(.defaultAction)

    Button("No", role: .cancel) {}
}

Мы также можем добавить дополнительное сообщение под заголовком с помощью параметра сообщения, который принимает другое замыкание @ViewBuilder, чтобы создать представление для отображения пользовательского сообщения.

.confirmationDialog(
    "Are you sure?",
    isPresented: $confirmationShown,
    titleVisibility: .visible
) {
    Button("Yes") {
        withAnimation { 
            viewModel.delete(message) 
        }
    }.keyboardShortcut(.defaultAction)

    Button("No", role: .cancel) {}
} message: {
    Text("This action cannot be undone")
}

Модификатор представления confrimationDialog позволяет нам предоставлять дополнительные данные для передачи в замыкания @ViewBuilder как для сообщения, так и для действий.

.confirmationDialog(
    "Are you sure?",
    isPresented: $confirmationShown,
    titleVisibility: .visible,
    presenting: message
) { message in
    Button("Yes, delete: \(message)") {
        withAnimation { 
            viewModel.delete(message) 
        }
    }.keyboardShortcut(.defaultAction)

    Button("No", role: .cancel) {}
} message: { message in
    Text(message)
}

Чтобы узнать больше о преимуществах замыканий ViewBuilder в SwiftUI, читайте мою публикацию «The power of @ViewBuilder in SwiftUI».

Мне очень нравится новый модификатор представления confirmationDialog за тот уровень гибкости, что он обеспечивает при кастомизации пользовательского опыта в наших приложениях. Надеюсь, вам понравилась эта статья. Не стесняйтесь подписываться на меня в Твиттере и задавать свои вопросы, связанные с этой темой. Спасибо за внимание, до встречи на следующей неделе!


Материал подготовлен в рамках специализации «iOS Developer».

Всех желающих приглашаем на открытый урок «Новые инструменты Swift, для работы с асинхронностью Async/Away/Actor». На открытом уроке поговорим о новых инструментах Swift 5.5 по работе с асинхронными задачами.

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

РЕГИСТРАЦИЯ