Прототип вью и модификатор ´redacted´ в SwiftUI

Прототип вью и модификатор ´redacted´ в SwiftUI

Делаем прототип вью в SwiftUI. Скелет интерфейса, пока контент загружается.


Имя Автора
Автор Никита Россик Увлекаюсь разработкой под .

Содержание


В iOS 14 и SwiftUI 2 добавили модификатор .redacted(reason:), с помощью которого можно сделать прототип вью:

VStack {
    Label("Swift Playground", systemImage: "swift")
    Label("Swift Playground", systemImage: "swift")
        .redacted(reason: .placeholder)
}
Прототип вью.

Когда пригодится прототип:

1. Показать вью, контент которой будет доступен после загрузки.

2. Показать недоступное или частично доступное содержимое.

3. Использовать вместо ProgressView(), о которой я рассказал в гайде.

Давайте рассмотрим сложный пример:

struct Device {

    let name: String
    let systemIcon: String
    let description: String
}

extension Device {

    static let airTag: Self =
        .init(
            name: "AirTag",
            systemIcon: "airtag",
            description: "Суперлёгкий способ находить свои вещи. Прикрепите один трекер AirTag к ключам, а другой — к рюкзаку. И теперь их видно на карте в приложении «Локатор»."
        )
}

У модели есть название, системная иконка и описание. Я вынес airTag в расширение, а сейчас мы создадим отдельную вью:

struct DeviceView: View {
    let device: Device
    
    var body: some View {
        VStack(spacing: 20) {
            HStack {
                Image(systemName: device.systemIcon)
                    .resizable()
                    .frame(width: 42, height: 42)
                Text(device.name)
                    .font(.title2)
            }
            VStack {
                Text(device.description)
                    .font(.footnote)
                
                Button("Перейти к покупке") {}
                .buttonStyle(.bordered)
                .padding(.vertical)
            }
        }
        .padding(.horizontal)
    }
}

Добавляем DeviceView в основную вью:

struct ContentView: View {

    var body: some View {
        DeviceView(device: .airTag)
            .redacted(reason: .placeholder)
    }
}
Результат `DeviceView`.

Слева вью без модификатора. Справа — с ним. Давайте для наглядности добавим переключатель:

struct ContentView: View {

    @State private var toggleRedacted: Bool = false
    
    var body: some View {
        VStack {
            DeviceView(device: .airTag)
                .redacted(reason: toggleRedacted ? .placeholder : [])
            
            Toggle("Toggle redacted", isOn: $toggleRedacted)
                .padding()
        }
    }
}



Unredacted

Если вы хотите не скрывать контент, примените модификатор unredacted():

VStack(spacing: 20) {
    HStack {
        Image(systemName: device.systemIcon)
            .resizable()
            .frame(width: 42, height: 42)
        Text(device.name)
            .font(.title2)
    }
    .unredacted()
    
    VStack {
        Text(device.description)
            .font(.footnote)
            // Какой-то код ниже
Отображение с `Unredacted`.

В примере иконка и название девайса не скрыты.


Кликабельность

Кнопка остаётся кликабельной и работает даже после того, как применили модификатор:

VStack {
    Text(device.description)
        .font(.footnote)
    
    Button("Перейти к покупке") {
        print("Кнопка кликабельна!")
    }
    .buttonStyle(.bordered)
    .padding(.vertical)
}


Поведением кнопки управляйте вручную, ниже покажу, как это сделать.


Причины редактирования

Apple спроектировала структуру RedactionReasons, которая отвечает за причину редактирования, применяемую ко вью.

Есть варианты privacy и placeholder. Первый отвечает за данные, которые скрыты как приватная информация, а placeholder отвечает за обобщённый прототип.

Как можно реализовать кастомную причину:

extension RedactionReasons {

	static let name = RedactionReasons(rawValue: 1 << 20)
	static let description = RedactionReasons(rawValue: 2 << 20)
}

Реализуем с помощью протокола OptionSet.


Environment

У окружения есть проперти .redactionReasons — текущая причина редактирования, применяемая к иерархии вью. Изменим DevicesView с помощью unredacted(when:):

struct DeviceView: View {

    let device: Device
    @Environment(.redactionReasons) var reasons 
    
    var body: some View {
        VStack(spacing: 20) {
            HStack {
                Image(systemName: device.systemIcon)
                    .resizable()
                    .frame(width: 42, height: 42)
                Text(device.name)
                    .unredacted(when: !reasons.contains(.name))
                    .font(.title2)
            }
            
            VStack {
                Text(device.description)
                    .unredacted(when: !reasons.contains(.description))
                    .font(.footnote)
                
                Button("Перейти к покупке") {
                    print("Кнопка не кликабельна!")
                }
                .disabled(!reasons.isEmpty)
                .buttonStyle(.bordered)
                .padding(.vertical)
            }
        }
        .padding(.horizontal)
    }
}

Я добавил кастомный метод unredacted(when:) для демонстрации свойства reasons:

extension View {
    @ViewBuilder
    func unredacted(when condition: Bool) -> some View {
        switch condition {
        case true: unredacted()
        case false: redacted(reason: .placeholder)
        }
    }
}

Если переключить, кнопка станет некликабельной.

Кастомный `unredacted`.

Собственный API

Начнём с реализации своих причин:

enum Reasons {

    case blurred
    case standart
    case sensitiveData
}

Реализуем вью-модификаторы, подходящие под причины выше:

struct Blurred: ViewModifier {

    func body(content: Content) -> some View {
        content
            .padding()
            .blur(radius: 4)
            .background(.thinMaterial, in: Capsule())
    }
}

struct Standart: ViewModifier {

    func body(content: Content) -> some View {
        content
            .padding()
    }
}

struct SensitiveData: ViewModifier {

    func body(content: Content) -> some View {
        VStack {
            Text("Are you over 18 years old?")
                .bold()
            
            content
                .padding()
                .frame(width: 160, height: 160)
                .overlay(.black, in: RoundedRectangle(cornerRadius: 20))
        }
    }
}

Чтобы увидеть результат из модификаторов выше в live preview, нужен код:

struct Blurred_Previews: PreviewProvider {

    static var previews: some View {
        Text("Hello, world!")
            .modifier(Blurred())
    }
}
Отображение с `Blurred`-модификатором.

Я взял Blurred-модификатор. Перейдём к следующему модификатору вью RedactableModifier:

struct RedactableModifier: ViewModifier {

    let reason: Reasons?
    
    init(with reason: Reasons) { self.reason = reason }
    
    @ViewBuilder
    func body(content: Content) -> some View {
        switch reason {
        case .blurred: content.modifier(Blurred())
        case .standart: content.modifier(Standart())
        case .sensitiveData: content.modifier(SensitiveData())
        case nil: content
        }
    }
}

У структуры есть reason-свойство, которое принимает опциональное перечисление Reasons.

Последний шаг — реализовать метод к протоколу View:

extension View {

    func redacted(with reason: Reasons?) -> some View {
        modifier(RedactableModifier(with: reason ?? .standart))
    }
}

Я не стал делать отдельную вью, в которой буду вызывать модификаторы. Вместо этого поместил всё в live preview:

struct RedactableModifier_Previews: PreviewProvider {

    static var previews: some View {
        VStack(spacing: 30) {
            Text("Usual content")
                .redacted(with: nil)
            Text("How are good your eyes?")
                .redacted(with: .blurred)
            Text("Sensitive data")
                .redacted(with: .sensitiveData)
        }
    }
}

Результат:

Отображение после применения `RedactableModifier`.

Другие туториалы

Жизненный цикл ´UIViewController´

Рассмотрим когда вызываются методы контроллера и что можно делать внутри них. Когда настраивать вьюхи и данные.

Как очистить UserDefaults для Mac Catalyst

Как очистить данные для приложения Catalyst включая AppGroup, Realm и UserDefaults.

Альтернативные иконки для тестов Product Page Optimization

Как добавить альтернативные иконки для A/B тестов на странице приложения.

Действия на сочетания клавиш в SwiftUI

Знакомимся с модификатором keyboardShortcut. Добавим модификаторы для клавиш .command, .option, .shift

В telegram-канале приходят уведомления о новых туториалах. В чате для iOS разработчиков ответят на вопросы.

Открыть Telegram-канал