Swift 将操作作为()->Void传递给组件是一个好主意吗?

Swift 将操作作为()->Void传递给组件是一个好主意吗?,swift,swiftui,Swift,Swiftui,我是swfitUI新手,我正在构建一个有点像这样的组件: //我的解决方案 结构TodoItem{ 变量标题:字符串 var操作:->无效? var body:一些观点{ HStack{ 文本标题 如果让动作=动作{ 按钮操作:操作,标签:Imageimage } } } } 但是我的队友不同意这一点,他们认为我不应该将动作传递给组件,而是应该像这样使用ViewBuilder //我队友的解决方案 结构TodoItem:视图,其中内容:视图{ 变量标题:字符串 var-content:conte

我是swfitUI新手,我正在构建一个有点像这样的组件:

//我的解决方案 结构TodoItem{ 变量标题:字符串 var操作:->无效? var body:一些观点{ HStack{ 文本标题 如果让动作=动作{ 按钮操作:操作,标签:Imageimage } } } } 但是我的队友不同意这一点,他们认为我不应该将动作传递给组件,而是应该像这样使用ViewBuilder

//我队友的解决方案 结构TodoItem:视图,其中内容:视图{ 变量标题:字符串 var-content:content @可插入的公共inittitle:String,@ViewBuilder content:->content{ self.title=标题 self.content=内容 } var body:一些观点{ HStack{ 文本标题 所容纳之物 } } } 他们说它更快捷,但我不明白,在使用方面,在我的解决方案中,任何使用此组件的人只需要关心标题和动作,在我的队友中,用户需要关心标题、动作以及如何构建按钮,显然我的解决方案更易于使用,我没有发现我的解决方案有任何缺点


我的队友的解决方案比我的好吗?为什么?

如果你发现自己正在使用AnyView,那么你已经离开了SwiftUI的快乐之路。在文档的部分中列出它是有原因的

AnyView打破了很多SwiftUI的优化。当你别无选择时,它作为逃生舱存在

您的代码看起来像我迄今为止从苹果看到的所有示例@当您的目标是成为调用者生成的视图的容器,并且希望实现细节由调用者决定时,ViewBuilder是有意义的。HStack就是一个很好的例子。如果组件应该封装视图实现细节,那么它应该使用传递的属性生成视图本身,这就是您正在做的。因此,本例中的问题是,如何创建一个通用工具,供不同的调用者以多种不同的方式使用?如果没有,我不知道您为什么会通过ViewBuilder

您的更新删除了AnyView,这一问题有了很大的改变。在这种情况下,可以归结到我上面的最后一段:如果TodoItem是一个通用容器,调用者需要为其提供内容,那么ViewBuilder就很好。这假设TodoItem是关于布局的,而不是关于显示的,如HStack

但若视图是关于显示的,比如按钮或文本,那个么你们应该传递它的属性,让它管理它的内部。请注意,该按钮允许您传入标签ViewBuilder,但通常不需要它

像TodoItem这样的名字听起来很像后者;它看起来像一个自定义视图,应该管理自己的外观。但主要的问题是:有多少调用者将不同的视图传递给这个ViewBuilder?如果所有调用者传递几乎相同的视图,或者只有一个调用者,那么它应该是像按钮一样的属性。如果有许多调用者传递不同类型的内容视图,那么它应该使用像HStack这样的视图生成器


两者都不是更快的。它们在SwiftUI中解决了不同的问题。

如果您发现自己正在使用AnyView,那么您已经离开了SwiftUI的快乐之路。在文档的部分中列出它是有原因的

AnyView打破了很多SwiftUI的优化。当你别无选择时,它作为逃生舱存在

您的代码看起来像我迄今为止从苹果看到的所有示例@当您的目标是成为调用者生成的视图的容器,并且希望实现细节由调用者决定时,ViewBuilder是有意义的。HStack就是一个很好的例子。如果组件应该封装视图实现细节,那么它应该使用传递的属性生成视图本身,这就是您正在做的。因此,本例中的问题是,如何创建一个通用工具,供不同的调用者以多种不同的方式使用?如果没有,我不知道您为什么会通过ViewBuilder

您的更新删除了AnyView,这一问题有了很大的改变。在这种情况下,可以归结到我上面的最后一段:如果TodoItem是一个通用容器,调用者需要为其提供内容,那么ViewBuilder就很好。这假设TodoItem是关于布局的,而不是关于显示的,如HStack

但若视图是关于显示的,比如按钮或文本,那个么你们应该传递它的属性,让它管理它的内部。请注意,该按钮允许您传入标签ViewBuilder,但通常不需要它

像TodoItem这样的名字听起来很像后者;它看起来像一个自定义视图,应该管理自己的外观。但主要的问题是:有多少调用者将不同的视图传递给这个ViewBuilder?如果所有调用者传递几乎相同的视图,或者只有一个调用者,那么它应该是像按钮一样的属性 . 如果有许多调用者传递不同类型的内容视图,那么它应该使用像HStack这样的视图生成器


两者都不是更快的。它们解决了SwiftUI中的不同问题。

您的方法是正确的,只有更改let无法工作,因此请参见下面的更正:

struct TodoItem {

  var title: String
  var image: String          // << this might also needed
  var action: (() -> Void)?

  var body: some View {
    HStack {
      Text(title)
      if action != nil {      // << here !!
        Button(action: action!, label: { Image(image) })
      }
    }
  }
}
使用Xcode 11.4/iOS 13.4进行测试


关于队友的替补:在成员中存储视图不是一种快捷方式。。。所以,修复第二个变体后,我会将ViewBuilder存储到成员中,并使用它将视图注入到主体中。但无论如何,这是一种更糟糕的方法,因为它破坏了UI组件的完整性。

您的方法是正确的,只有更改let不会起作用,所以请参见下面的更正:

struct TodoItem {

  var title: String
  var image: String          // << this might also needed
  var action: (() -> Void)?

  var body: some View {
    HStack {
      Text(title)
      if action != nil {      // << here !!
        Button(action: action!, label: { Image(image) })
      }
    }
  }
}
使用Xcode 11.4/iOS 13.4进行测试


关于队友的替补:在成员中存储视图不是一种快捷方式。。。所以,修复第二个变体后,我会将ViewBuilder存储到成员中,并使用它将视图注入到主体中。但无论如何,这是一种更糟糕的方法,因为它破坏了UI组件的完整性。

我没有发现任何文档说AnyView破坏了很多SwiftUI的优化,你从哪里找到的?来自AnyView文档:每当与AnyView一起使用的视图类型发生变化时,旧的层次结构就会被破坏,并为新类型创建一个新的层次结构。避免这种情况是SwiftUI优化的主要部分。哦,只有当视图类型发生变化时才会这样做,我同意我们需要明智地使用AnyView。即使没有变化,它也会影响视图层次结构。SwiftUI拥有如此复杂类型的原因是它通过泛型类型跟踪整个视图层次结构。它利用这一点来推断哪些部分相互影响,从而消除层次结构中的冗余部分,这就是为什么视图可以如此便宜的原因。AnyView是一种类型的橡皮擦;它故意删除该类型信息,迫使SwiftUI保留AnyView,不管它是否需要它,因为它无法证明它里面或下面可能有什么。文档对AnyView的描述是类型擦除视图。这是因为类型只在运行时确定,而不是在编译时确定。这就是类型橡皮擦的意思。编译器无法对AnyView中的内容进行推理。它确实可以解释ZStack内部的内容。当然,有时不能将AnyView替换为组,例如,当类型在编译时未知时,这就是它存在的原因。例如,如果类型将由运行时加载的配置数据选择。它并不是偶然出现在不常使用的视图中的。它解决了一个特殊的问题。我没有发现任何文档说AnyView破坏了很多SwiftUI的优化,你在哪里找到的?从AnyView文档:每当与AnyView一起使用的视图类型发生变化时,旧的层次结构就会被破坏,并为新类型创建新的层次结构。避免这种情况是SwiftUI优化的主要部分。哦,只有当视图类型发生变化时才会这样做,我同意我们需要明智地使用AnyView。即使没有变化,它也会影响视图层次结构。SwiftUI拥有如此复杂类型的原因是它通过泛型类型跟踪整个视图层次结构。它利用这一点来推断哪些部分相互影响,从而消除层次结构中的冗余部分,这就是为什么视图可以如此便宜的原因。AnyView是一种类型的橡皮擦;它故意删除该类型信息,迫使SwiftUI保留AnyView,不管它是否需要它,因为它无法证明它里面或下面可能有什么。文档对AnyView的描述是类型擦除视图。这是因为类型只在运行时确定,而不是在编译时确定。这就是类型橡皮擦的意思。编译器无法对AnyView中的内容进行推理。它确实可以解释ZStack内部的内容。当然,有时不能将AnyView替换为组,例如,当类型在编译时未知时,这就是它存在的原因。例如,如果类型将由运行时加载的配置数据选择。它并不是偶然出现在不常使用的视图中的。它解决了一个特殊的问题。我相信Swift 5.2刚刚解决了let问题。从Twitter记忆中提取;最近我还没有尝试过。@RobNapier,包含控制流语句的闭包不能与函数生成器“ViewBuilder”配合使用,因为它是设计的!谢谢我在想这个,这是不同的:我相信Swift 5.2刚刚解决了let问题。从Twitter记忆中提取;最近我还没有尝试过。@RobNapier,包含控制流语句的闭包不能与函数生成器“ViewBuilder”配合使用,因为它是设计的!谢谢我在想这个,这是不同的: