SwiftUI - 有没有办法存储形状然后为 .contentShape 调用它?

问题描述 投票:0回答:1

因此,我尝试设置很多按钮,这些按钮在点击时执行相同的操作,但具有不同的内容形状。其中一些是自定义形状,一些是矩形,并且都具有修饰符。我想设置一个 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 修改器对这种安排完全不满意。使用代码时,它在 contentShape 行上给出错误“对静态方法“buildExpression”的引用没有精确匹配”。如果我引入形状,我会尝试引用它所调用的视图,如下所示:

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)
     }
}

然后代码运行得很好,没有错误。我能看到的唯一区别是,使用此方法,形状存储为“某些形状”,而使用我之前的方法,它存储为“任何形状”。我找不到任何方法将其存储为允许我存储所有自定义按钮的任何其他方法,并尝试将其转换为“某些形状”或“形状”会产生一个新错误:“'任何形状'不能是构造是因为它没有可访问的初始值设定项”。

我不能说我知道“形状”、“某些形状”、“任何形状”和“任何形状”之间的区别,而且我也找不到对此的解释。对于任何知道其中区别或为什么这不起作用的人,请告诉我你能做什么!我实在想不出另一种方法来让我的代码正常工作。

更新:解决方案注释 感谢 timbre timbre 提供的解决方案,我已将其标记为答案。然而,在实现此解决方案时,我在步骤 #3 中遇到了错误:“函数声明了不透明返回类型‘some View’,但其主体中的返回语句没有匹配的基础类型”。 对于遇到同样问题的任何人,解决方案是在函数之前添加“@ViewBuilder”,然后从代码中删除所有“return”。我重新格式化的代码如下所示:

extension View {
    @ViewBuilder func contentShape(_ tappableArea: TappableArea) -> some View {
        switch tappableArea {
        case let .rectangle(offset, size):
            self.contentShape(
                Rectangle()
                    .offset(x: offset.x, y: offset.y)
                    .size(width: size.width, height: size.height)
            )
        //Repeat for all sequential buttons
        }
    }
}
xcode button swiftui shapes swift-property-wrapper
1个回答
0
投票

也许您已经考虑过这一点,因为这是一个非常简单的解决方案,但您可以:

  1. 创建您自己的枚举,它定义形状:
enum MyShape {
    case rectangle(offset: CGPoint, size: CGSize)
    // ...
}
  1. ObservableObjectButton
    中使用该枚举而不是实际形状:
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
不需要知道如何在 UI 中使区域可点击)。

© www.soinside.com 2019 - 2024. All rights reserved.