SwiftUI 3.0. Шестая часть

10 октября 2021

В этой подборке статей поговорим о том, как раскрасить SF Symbols по своему усмотрению, обсудим тему блокировки свайпа сверху вниз при работе с модальным окном и узнаем что нового привнес SwiftUI 3.0 при работе с Alert и ActionSheet.

SwiftUI 3.0. Изменение цветов и прозрачности SF Symbols

Теперь для изображений из библиотеки SF Symbols можно задавать свои цвета. Делается это при помощи атрибута foregroundColor(). Более того при помощи .renderingMode(.original) эти изображения можно делать многоцветными. А для более точной передачи оттенков отдельных частей изображения можно использовать иерархический вариант или использовать полностью настраиваемую палитру.

Иерархический рендеринг использует прозрачность для создания разных оттенков, чтобы обеспечить дополнительную глубину и четкость:


Image(systemName: "theatermasks")
    .symbolRenderingMode(.hierarchical)
    .font(.system(size: 144))

Иерархический рендеринг работает в сочетании с цветом самого изображения, поэтому его можно использовать в сочетании с модификатором foregroundColor:


Image(systemName: "theatermasks")
    .symbolRenderingMode(.hierarchical)
    .foregroundColor(.blue)
    .font(.system(size: 144))

Параметр .palette в сочетании с модификатором foregroundStyle() дает еще больше возможностей, позволяя использовать более одного цвета для изображения:


Image(systemName: "shareplay")
    .symbolRenderingMode(.palette)
    .foregroundStyle(.blue, .black)
    .font(.system(size: 144))

Распределение цветов по определенным элементам изображения зависит от количества слоёв конкретного изображения. Разные изображения состоят из разного количества слоёв и в зависимости от этого цвета могут распределяться по разному. Для символов содержащих три элемента достаточно добавить еще один цвет:


Image(systemName: "person.3.sequence.fill")
    .symbolRenderingMode(.palette)
    .foregroundStyle(.blue, .green, .red)
    .font(.system(size: 144))

Цвета можно смешивать, создавая градиенты градиенты и более сложные оттенки:

та можно смешивать, создавая градиенты градиенты и более сложные оттенки:


Image(systemName: "person.3.sequence.fill")
    .symbolRenderingMode(.palette)
    .foregroundStyle(
        .linearGradient(colors: [.red, .black], startPoint: .top, endPoint: .bottomTrailing),
        .linearGradient(colors: [.green, .black], startPoint: .top, endPoint: .bottomTrailing),
        .linearGradient(colors: [.blue, .black], startPoint: .top, endPoint: .bottomTrailing)
    )
    .font(.system(size: 144))

 

SwiftUI 3.0. Блокирование свайпа сверху вниз при закрытии модального экрана

Новый модификатор interactiveDismissDisabled() в SwiftUI, позволяет блокировать закрытие модального окна свайпом сверху вниз:


struct ExampleSheet: View {
    @Environment(\.presentationMode) var presentationMode

    var body: some View {
        VStack {
            Text("Sheet view")

            Button("Dismiss", action: close)
        }
        .interactiveDismissDisabled()
    }

    func close() {
        presentationMode.wrappedValue.dismiss()
    }
}

struct ContentView: View {
    @State private var showingSheet = false

    var body: some View {
        Button("Show Sheet") {
            showingSheet.toggle()
        }
        .sheet(isPresented: $showingSheet, content: ExampleSheet.init)
    }
}

Использование модификатора interactiveDismissDisabled() в таком виде просто блокирует закрытие окна свайпом сверху вниз:

 

 

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


struct ExampleSheet: View {
    @Environment(\.presentationMode) var presentationMode
    @State private var termsAccepted = false

    var body: some View {
        VStack {
            Text("Terms and conditions")
                .font(.title)
            Text("Lots of legalese here.")
            Toggle("Accept", isOn: $termsAccepted)
        }
        .padding()
        .interactiveDismissDisabled(!termsAccepted)
    }

    func close() {
        presentationMode.wrappedValue.dismiss()
    }
}

struct ContentView: View {
    @State private var showingSheet = false

    var body: some View {
        Button("Show Sheet") {
            showingSheet.toggle()
        }
        .sheet(isPresented: $showingSheet, content: ExampleSheet.init)
    }
}

 

SwiftUI 3.0. Alert in SwiftUI

С версии iOS 15 при работе с алертом используются новые стили кнопок, которые так же доступны с этой же версии ОС:


struct ContentView: View {
    @State private var showingAlert = false

    var body: some View {
        Button("Show Alert") {
            showingAlert = true
        }
        .alert("Important message", isPresented: $showingAlert) {
            Button("OK", role: .cancel) { }
        }
    }
}

Здесь мы прикрепили к предупреждению кнопку "ОК", которой назначили роль .cancel.

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


struct ContentView: View {
    @State private var showingAlert = false

    var body: some View {
        Button("Show Alert") {
            showingAlert = true
        }
        .alert("Important message", isPresented: $showingAlert) {
            Button("First") { }
            Button("Second") { }
            Button("Third") { }
        }
    }
}

 

Поскольку это новые кнопки SwiftUI, то им можно назначить любые роли, например .destructive, чтобы заголовки кнопок были красного цвета:

Если необходимо сделать поддержку iOS 14 и 13, то придется использовать структуру Alert, которая выглядит следующим образом:


Alert(
    title: Text("Important message"),
    message: Text("Wear sunscreen"), 
    dismissButton: .default(Text("Got it!"))
)

Структура определяет заголовок и сообщение, как в UIAlertController, а затем добавляет кнопку отмены со стилем по умолчанию и текстом «Got it!»:


struct ContentView: View {
    @State private var showingAlert = false

    var body: some View {
        Button("Show Alert") {
            showingAlert = true
        }
        .alert(isPresented: $showingAlert) {
            Alert(title: Text("Important message"), message: Text("Wear sunscreen"), dismissButton: .default(Text("Got it!")))
        }
    }
}

Есть еще один способ создания алертов, связанный с привязкой к опциональному стейт свойству, тип которого удовлетворяет протоколу Identifiable.

У этого подхода есть два преимущества:

  1. Через связанное с алертом стейт свойство можно передавать любые данные, для дальнейшего их отображения в алерте.
  2. SwiftUI автоматически извлекает опциональное значение когда то становится доступным, поэтому нет необходимости в том, чтобы самостоятельно его извлекать.

struct TVShow: Identifiable {
    var id: String { name }
    let name: String
}

struct ContentView: View {
    @State private var selectedShow: TVShow?

    var body: some View {
        VStack(spacing: 20) {
            Text("What is your favorite TV show?")
                .font(.headline)

            Button("Select Ted Lasso") {
                selectedShow = TVShow(name: "Ted Lasso")
            }

            Button("Select Bridgerton") {
                selectedShow = TVShow(name: "Bridgerton")
            }
        }
        .alert(item: $selectedShow) { show in
            Alert(title: Text(show.name), message: Text("Great choice!"), dismissButton: .cancel())
        }
    }
}

 

SwiftUI 3.0. Action sheet in SwiftUI

Начиная с версии iOS 15 для работы с меню пользовательских действий (action sheet) используется модификатор confirmationDialog(). Для более ранних версий ОС используется тип данных ActionSheet. Мы рассмотрим оба способа работы с этим элементом интерфейса.

Для создания меню пользовательских действий при помощи confirmDialog(), необходимо задать заголовок меню и определить должен ли он отображаться. Само меню нужно связать с логическим свойством от значения которого будет зависеть должно оно отображаться или нет:


struct ContentView: View {
    @State private var showingOptions = false
    @State private var selection = "None"

    var body: some View {
        VStack {
            Text(selection)

            Button("Confirm paint color") {
                showingOptions = true
            }
            .confirmationDialog("Select a color", isPresented: $showingOptions, titleVisibility: .visible) {
                Button("Red") {
                    selection = "Red"
                }

                Button("Green") {
                    selection = "Green"
                }

                Button("Blue") {
                    selection = "Blue"
                }
            }
        }
    }
}

 

Если не задать параметр titleVisibility, то SwiftUI будет автоматически определять стоит ли его отображать в зависимости от контекста.

Кнопкам в меню пользовательских действий можно определить роли. Если для кнопки определить роль .destructive то её название будет красным.

Поскольку этот новый API более гибкий по сравнению с ActionSheet, то мы можем оптимизировать логику работы с кнопками, выполнив в цикле ForEach:


struct ContentView: View {
    @State private var showingOptions = false
    @State private var selection = "None"

    var body: some View {
        VStack {
            Text(selection)

            Button("Confirm paint color") {
                showingOptions = true
            }
            .confirmationDialog("Select a color", isPresented: $showingOptions, titleVisibility: .visible) {
                ForEach(["Red", "Green", "Blue"], id: \.self) { color in
                    Button(color) {
                        selection = color
                    }
                }
            }
        }
    }
}

Если вам нужно настроить таргетинг на работу с iOS 14 или ниже, то для отображения меню пользовательских действий придется использовать тип ActionSheet:


struct ContentView: View {
    @State private var showingOptions = false
    @State private var selection = "None"

    var body: some View {
        VStack {
            Text(selection)

            Button("Show Options") {
                showingOptions = true
            }
            .actionSheet(isPresented: $showingOptions) {
                ActionSheet(
                    title: Text("Select a color"),
                    buttons: [
                        .default(Text("Red")) {
                            selection = "Red"
                        },

                        .default(Text("Green")) {
                            selection = "Green"
                        },

                        .default(Text("Blue")) {
                            selection = "Blue"
                        },
                    ]
                )
            }
        }
    }
}

В статье использованы материалы из источников:

 

Содержание