Сохранение ShapeStyle в переменную в Swift

Вопрос или проблема

В моем приложении есть различные кнопки. Они разделяют многие качества, но отличаются по цвету. Я пытаюсь написать класс, который охватывает каждый из стилей кнопок, делая ясным, что единственное, что меняется – это их цвета.

Проблема в том, что я также хотел бы иметь возможность использовать .tint для одной из них. Но это ShapeStyle, а не цвет.

struct CommonButtonStyle: ButtonStyle {
    @Environment(\.isEnabled) var isEnabled: Bool
    
    @Environment(\.isFocused) var isFocused: Bool
    
    var labelColor: Color
    
    var enabledColor: some ShapeStyle
    var pressedColor: Color
    
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .font(.system(size: 14, weight: .bold))
            .padding(.horizontal, 16)
            .padding(.vertical, 10)
            .foregroundStyle(labelColor)
            .frame(maxHeight: 40)
            .background((isEnabled && configuration.isPressed) ? pressedColor : enabledColor)
            .clipShape(Capsule())
            .containerShape(Capsule())
            .opacity(isEnabled ? 1 : 0.5)
            .focusable()
    }
}

Проблема здесь заключается в несовпадении типов возвращаемых значений и декларации var enabledColor: some ShapeStyle, так как компилятор жалуется, что Свойство объявляет непрозрачный тип возвращаемого значения, но не имеет инициализирующего выражения, из которого можно было бы вывести основной тип.

Существует ли способ заставить эту переменную хранить Color или ShapeStyle, чтобы я мог сделать

CommonButtonStyle(labelColor: .black, enabledColor: .tint, pressedColor: .white)

и

CommonButtonStyle(labelColor: .black, enabledColor: .blue, pressedColor: .white)

Это не разрешено. Потому что .background имеет содержимое как ShapeStyle или View, а не оба. Вы можете провести небольшой эксперимент, чтобы проверить это, что-то вроде:

func getSomeView() -> some View {
    if isEnabled {
        Text("Halo") //<- ❌ ошибка здесь
    } else {
        Button {
        } label: {
            Text("Hello")
        }
    }
}

Таким образом, в этом сценарии .background вообще не будет работать. Она не может принимать два разных типа в качестве своего содержимого. Вы должны найти решение, чтобы как-то разделить это на два отдельных .background.

.background((isEnabled && configuration.isPressed) ? pressedColor : enabledColor)

Спасибо @Mojitaba за его решение.

С этого момента вы можете использовать условие if, такое как:

struct CommonButtonStyle<Background: ShapeStyle>: ButtonStyle {
    ...
    var enabledColor: Background
    var pressedColor: Color
    
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            ...
            .apply {
                if isEnabled && configuration.isPressed {
                    $0.background(pressedBg)
                } else {
                    $0.background(enableBg)
                }
            }
            ...
    }
}

var body: some View {
    Button {
    } label: {
        Text("Button 1")
    }
    .buttonStyle(CommonButtonStyle(
        labelColor: .black,
        enabledColor: LinearGradient(gradient: Gradient(colors: [.yellow, .purple]),
                                     startPoint: .leading,
                                     endPoint: .trailing),
        pressedColor: .white)
    )
}

Вывод:

введите описание изображения здесь

Ответ или решение

В данной статье мы рассмотрим, как создать класс стиля для кнопок в SwiftUI, который будет поддерживать различные стили фона, включая использование ShapeStyle, такого как .tint, и цвета, такие как .blue. Мы также обсудим, как обойти возникающие ошибки компиляции, связанные с разными типами, чтобы обеспечить гибкость и совместимость нашего кода.

Понимание проблемы

Ваша задача заключается в том, чтобы создать универсальный стиль кнопки, который будет использовать различные цвета и стили в зависимости от состояния кнопки (активна или нажата). Исходная проблема заключается в том, что вы попытаетесь присвоить два разных типа (например, Color и ShapeStyle) одной и той же переменной, что вызывает ошибку компиляции:

Property declares an opaque return type, but has no initializer expression from which to infer an underlying type.

Решение

Чтобы создать класс стиля для кнопок, который может принимать как Color, так и ShapeStyle, мы можем воспользоваться обобщениями в Swift, используя ShapeStyle как обобщенный тип.

Пример кода

Ниже приведен пример реализации стиля кнопки с использованием обобщенных типов в Swift:

import SwiftUI

struct CommonButtonStyle<Background: ShapeStyle>: ButtonStyle {
    @Environment(\.isEnabled) var isEnabled: Bool
    var labelColor: Color
    var enabledColor: Background
    var pressedColor: Color

    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .font(.system(size: 14, weight: .bold))
            .padding(.horizontal, 16)
            .padding(.vertical, 10)
            .foregroundStyle(labelColor)
            .frame(maxHeight: 40)
            .apply {
                if isEnabled && configuration.isPressed {
                    $0.background(pressedColor)
                } else {
                    $0.background(enabledColor)
                }
            }
            .clipShape(Capsule())
            .containerShape(Capsule())
            .opacity(isEnabled ? 1 : 0.5)
            .focusable()
    }
}

struct ContentView: View {
    var body: some View {
        Button {
            // Действие кнопки
        } label: {
            Text("Нажми на меня")
        }
        .buttonStyle(CommonButtonStyle(
            labelColor: .black,
            enabledColor: LinearGradient(gradient: Gradient(colors: [.yellow, .purple]),
                                         startPoint: .leading,
                                         endPoint: .trailing),
            pressedColor: .white))
    }
}

Объяснение кода

  1. Обобщенный тип: Мы объявляем CommonButtonStyle как обобщенный стиль кнопки с помощью Background: ShapeStyle. Это позволяет нам использовать любой тип, соответствующий протоколу ShapeStyle, включая цветовые градиенты или стандартные цвета.

  2. Состояние кнопки: Используем переменные окружения @Environment(\.isEnabled) для определения, активна ли кнопка, и configuration.isPressed для отслеживания, нажата ли кнопка.

  3. Условная логика: Мы добавляем фон кнопки с использованием тернарного оператора, проверяя, находится ли кнопка в состоянии нажатия или активна.

Применение

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

.buttonStyle(CommonButtonStyle(
    labelColor: .black,
    enabledColor: .tint,  // использовать .tint как ShapeStyle
    pressedColor: .white))

или

.buttonStyle(CommonButtonStyle(
    labelColor: .black,
    enabledColor: .blue,  // использовать Color
    pressedColor: .white))

Заключение

Создание универсального стиля кнопок в SwiftUI с поддержкой различных типов фона — это мощный инструмент, который позволяет разработчикам более гибко управлять интерфейсами. Использование обобщений и протоколов Swift значительно расширяет возможности вашего кода, делая его более адаптируемым и менее подверженным ошибкам. Рассмотренные примеры обеспечивают надежное решение вашей проблемы, позволяя вам создавать стильные и функциональные кнопки в вашем приложении.

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

Капча загружается...