SwiftUI. Есть ли способ сохранить фигуру, а затем вызвать ее для .contentShape?
Итак, я пытаюсь настроить МНОГО кнопок, которые при нажатии делают одно и то же, но имеют разную форму содержимого. Некоторые из них представляют собой пользовательские фигуры, некоторые — прямоугольники, и все они имеют модификаторы. Я хотел настроить цикл ForEach, который создавал бы кнопку с определенными свойствами, передаваемыми из другого файла, и все это работало, за исключением форм содержимого.
У меня есть структура, которая будет содержать всю информацию о каждой кнопке:
struct ObservableObjectButton {
let tappableArea: any Shape
let imageName: String
let emoteName: String
let message: String
}
И класс, содержащий значения @Published, к которым необходимо получить доступ во всем приложении. Он содержит демонстрационный экземпляр указанной выше структуры, поэтому я могу протестировать его с помощью только одной кнопки:(Примечание: Location — это собственный тип, который содержит массив ObserveableObjects и несколько других свойств. LocationName — это перечисление, которое позволяет мне получить доступ к любому Местоположение по названию.)
class RootController: ObservableObject {
@Published var currentEmoteName = "Thoughtful Susie"
@Published var currentMessage = "This is default text."
@Published var currentMode = PlayMode.none
@Published var currentLocation = allLocations[.backyard]!
func observeObject(emoteName: String, message: String) {
showComment = true
currentEmoteName = emoteName
currentMessage = message
}
static let allLocations: [LocationName: Location] = [
.backyard: Location(
backgroundImageName: "test",
observeableObjects: [
ObservableObjectButton(
tappableArea: Rectangle()
.offset(x: 405, y: 660)
.size(width: 515, height: 90),
imageName: "Test",
emoteName: "test",
message: "Test"
)
]
)
]
}
И, наконец, файл SwiftUI, который содержит ForEach для отображения только кнопок в текущем местоположении:
@StateObject private var rootController = RootController()
if rootController.currentMode == .observe {
let buttons = rootController.currentLocation.observeableObjects
ForEach(buttons, id: \.imageName) { button in
Button {
rootController.observeObject(
emoteName: button.emoteName,
message: button.message
)
} label: {
Image(button.imageName)
}
.contentShape(button.tappableArea)
}
}
Модификатор contentShape совершенно недоволен таким расположением. В таком виде кода выдается ошибка «Нет точных совпадений со статическим методом buildExpression» в строке contentShape. Если я перенесу фигуру, на которую пытаюсь сослаться, в представление, которое она вызывает, например:
if rootController.currentMode == .observe {
let buttons = rootController.currentLocation.observeableObjects
let tappableArea = Rectangle()
.offset(x: 405, y: 660)
.size(width: 515, height: 90)
ForEach(buttons, id: \.imageName) { button in
Button {
rootController.observeObject(
emoteName: button.emoteName,
message: button.message
)
} label: {
Image(button.imageName)
}
.contentShape(tappableArea)
}
}
Тогда код работает нормально, без ошибок. Единственное отличие, которое я вижу, заключается в том, что в этом методе форма сохраняется как «некоторая форма», тогда как в моем более раннем методе она сохраняется как «любая форма». Я не могу найти способ сохранить его как что-либо еще, что позволило бы мне сохранить все мои пользовательские кнопки, и попытка преобразовать его в «некую форму» или «Форма» приводит к новой ошибке: «любая форма» не может быть сконструирован, поскольку у него нет доступных инициализаторов».
Я не могу сказать, что имею какое-либо представление о разнице между «Форма», «Некоторая форма», «Любая форма» и «Любая форма», и я тоже не могу найти этому объяснения. Для тех, кто ДЕЙСТВИТЕЛЬНО знает разницу или почему это не работает, пожалуйста, скажите мне, что вы можете! Я не могу придумать другого способа заставить мой код работать.
1 ответ
Возможно, вы уже обдумывали это, поскольку это очень простое решение, но вы могли бы:
- Создайте собственное перечисление, определяющее фигуры:
enum MyShape {
case rectangle(offset: CGPoint, size: CGSize)
// ...
}
- Используйте это перечисление вместо фактической формы:
struct ObservableObjectButton {
let tappableArea: MyShape
let imageName: String
let emoteName: String
let message: String
}
- Создайте свой собственный
contentShape
модификатор, который принимаетMyShape
в качестве аргумента и применяет его соответствующим образом:
extension View {
func contentShape(myShape: MyShape) -> some View {
switch myShape {
case let .rectangle(offset, size):
return self
.contentShape(Rectangle()
.offset(x: offset.x, y: offset.y)
.size(width: size.width, height: size.height))
// ...
}
}
}
- Теперь вы можете определить область нажатия в ObservableObjectButton:
ObservableObjectButton(
tappableArea: .rectangle(
offset: CGPoint(x: 405, y: 660),
size: CGSize(width: 515, height: 90),
// ...
- И вы применяете свой модификатор к кнопке в представлении:
ForEach(buttons, id: \.imageName) { button in
Button { // ...
}
.contentShape(button.tappableArea)
Это решение также правильно изолирует данные кнопок от деталей реализации (т. е. вашегоObservableObjectButton
не нужно знать, как именно вы делаете область касания в пользовательском интерфейсе).