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 ответ

Возможно, вы уже обдумывали это, поскольку это очень простое решение, но вы могли бы:

  1. Создайте собственное перечисление, определяющее фигуры:
      enum MyShape {
    case rectangle(offset: CGPoint, size: CGSize)
    // ...
}
  1. Используйте это перечисление вместо фактической формы:
      struct ObservableObjectButton {
    let tappableArea: MyShape
    let imageName: String
    let emoteName: String
    let message: String
}
  1. Создайте свой собственный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))
        // ...
        }
    }
}
  1. Теперь вы можете определить область нажатия в ObservableObjectButton:
      ObservableObjectButton(
    tappableArea: .rectangle(
        offset: CGPoint(x: 405, y: 660),
        size: CGSize(width: 515, height: 90),
    // ...
  1. И вы применяете свой модификатор к кнопке в представлении:
      ForEach(buttons, id: \.imageName) { button in
    Button { // ...
    }
    .contentShape(button.tappableArea) 

Это решение также правильно изолирует данные кнопок от деталей реализации (т. е. вашегоObservableObjectButtonне нужно знать, как именно вы делаете область касания в пользовательском интерфейсе).

Другие вопросы по тегам