Вопрос или проблема
В моем приложении есть различные кнопки. Они разделяют многие качества, но отличаются по цвету. Я пытаюсь написать класс, который охватывает каждый из стилей кнопок, делая ясным, что единственное, что меняется – это их цвета.
Проблема в том, что я также хотел бы иметь возможность использовать .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))
}
}
Объяснение кода
-
Обобщенный тип: Мы объявляем
CommonButtonStyle
как обобщенный стиль кнопки с помощьюBackground: ShapeStyle
. Это позволяет нам использовать любой тип, соответствующий протоколуShapeStyle
, включая цветовые градиенты или стандартные цвета. -
Состояние кнопки: Используем переменные окружения
@Environment(\.isEnabled)
для определения, активна ли кнопка, иconfiguration.isPressed
для отслеживания, нажата ли кнопка. -
Условная логика: Мы добавляем фон кнопки с использованием тернарного оператора, проверяя, находится ли кнопка в состоянии нажатия или активна.
Применение
Данный подход позволяет легко изменять стиль кнопок без необходимости дублировать код. Вы можете передать любые объекты, соответствующие ShapeStyle
, создавая стиль кнопки, который отвечает вашим требованиям. Например:
.buttonStyle(CommonButtonStyle(
labelColor: .black,
enabledColor: .tint, // использовать .tint как ShapeStyle
pressedColor: .white))
или
.buttonStyle(CommonButtonStyle(
labelColor: .black,
enabledColor: .blue, // использовать Color
pressedColor: .white))
Заключение
Создание универсального стиля кнопок в SwiftUI с поддержкой различных типов фона — это мощный инструмент, который позволяет разработчикам более гибко управлять интерфейсами. Использование обобщений и протоколов Swift значительно расширяет возможности вашего кода, делая его более адаптируемым и менее подверженным ошибкам. Рассмотренные примеры обеспечивают надежное решение вашей проблемы, позволяя вам создавать стильные и функциональные кнопки в вашем приложении.