SwiftUI 中的条件属性

Posted

技术标签:

【中文标题】SwiftUI 中的条件属性【英文标题】:Conditional property in SwiftUI 【发布时间】:2019-12-19 09:31:48 【问题描述】:

如何根据条件添加其他属性?

下面的代码出现错误Cannot assign value of type 'some View' (result of 'Self.overlay(_:alignment:)') to type 'some View' (result of 'Self.onTapGesture(count:perform:)')

import SwiftUI

struct ConditionalProperty: View 
    @State var overlay: Bool
    var body: some View 
        var view = Image(systemName: "photo")
            .resizable()
            .onTapGesture(count: 2, perform: self.tap)
        if self.overlay 
            view = view.overlay(Circle().foregroundColor(Color.red))
        
        return view
    

    func tap() 
        // ...
    

【问题讨论】:

【参考方案1】:

感谢this post,我找到了一个界面非常简单的选项:

Text("Hello")
    .if(shouldBeRed)  $0.foregroundColor(.red) 

由此扩展启用:

extension View 
    @ViewBuilder
    func `if`<Transform: View>(_ condition: Bool, transform: (Self) -> Transform) -> some View 
        if condition  transform(self) 
        else  self 
    

这是一篇很棒的博客文章,其中包含有关条件修饰符的其他提示:https://fivestars.blog/swiftui/conditional-modifiers.html

【讨论】:

这是最好的解决方案。【参考方案2】:

在 SwiftUI 术语中,您不是在添加属性。您正在添加一个修饰符。

这里的问题是,在 SwiftUI 中,每个修饰符都会返回一个取决于它正在修改的内容的类型。

var view = Image(systemName: "photo")
    .resizable()
    .onTapGesture(count: 2, perform: self.tap)

// view has some deduced type like GestureModifier<SizeModifier<Image>>

if self.overlay 
    let newView = view.overlay(Circle().foregroundColor(Color.red))
    // newView has a different type than view, something like
    // OverlayModifier<GestureModifier<SizeModifier<Image>>>

由于newView 的类型与view 不同,您不能只指定view = newView

解决此问题的一种方法是始终使用overlay 修饰符,但在您不希望它可见时使叠加层透明:

var body: some View 
    return Image(systemName: "photo")
        .resizable()
        .onTapGesture(count: 2, perform: self.tap)
        .overlay(Circle().foregroundColor(overlay ? .red : .clear))

另一种处理方法是使用类型橡皮擦AnyView

var body: some View 
    let view = Image(systemName: "photo")
        .resizable()
        .onTapGesture(count: 2, perform: self.tap)
    if overlay 
        return AnyView(view.overlay(Circle().foregroundColor(.red)))
     else 
        return AnyView(view)
    

我从 Apple 工程师那里看到的建议是避免使用 AnyView,因为它的效率低于使用完全类型化的视图。例如,this tweet 和 this tweet。

【讨论】:

AnyView的实际性能测试在使用时没有发现任何负面影响:medium.com/swlh/…【参考方案3】:

Rob Mayoff已经explained pretty well为什么会出现这种行为并提出了两种解决方案(透明覆盖或使用AnyView)。第三种更优雅的方式是使用_ConditionalContent。

创建_ConditionalContent 的一种简单方法是在组或堆栈中编写if else 语句。以下是使用组的示例:

import SwiftUI

struct ConditionalProperty: View 
    @State var overlay: Bool
    var body: some View 
        Group 
            if overlay 
                base.overlay(Circle().foregroundColor(Color.red))
             else 
                base
            
        
    
    
    var base: some View 
        Image("photo")
            .resizable()
            .onTapGesture(count: 2, perform: self.tap)
    

    func tap() 
        // ...
    

【讨论】:

请注意,默认情况下,SwiftUI 将视图的静态类型视为其动画标识符 (explained by Joe Groff here)。在您的解决方案中,两个子视图(有/无覆盖)因此被视为不相关的视图。如果有人应用动画,它在您的解决方案中的应用可能与在我的解决方案中不同。这不一定是错误的,只是需要注意的事情。可以使用 .id 修饰符来解决它。【参考方案4】:

这是一个类似于 Kiran Jasvanee 发布的答案,但经过简化:

struct ContentView: View 

    var body: some View 
        Text("Hello, World!")
            .modifier(ConditionalModifier(isBold: true))
    


struct ConditionalModifier: ViewModifier 

    var isBold: Bool

    func body(content: Content) -> some View 
        Group 
            if self.isBold 
                content.font(.custom("HelveticaNeue-Bold", size: 14))
            
            else
                content.font(.custom("HelveticaNeue", size: 14))
            
        
    

不同的是不需要添加这个扩展:

extension View 
    func conditionalView(_ value: Bool) -> some View 
        self.modifier(ConditionalModifier(isBold: value))
  

ConditionalModifier 结构体中,您还可以消除组视图,而使用@ViewBuilder 装饰器,如下所示:

struct ConditionalModifier: ViewModifier 

    var isBold: Bool

    @ViewBuilder
    func body(content: Content) -> some View 
        // Group 
            if self.isBold 
                content.font(.custom("HelveticaNeue-Bold", size: 14))
            
            else
                content.font(.custom("HelveticaNeue", size: 14))
            
        // 
    

如果您想有条件地显示视图,则必须使用组视图(或其他一些父视图)或 @ViewBuilder 装饰器。

这是另一个可以在粗体和非粗体之间切换按钮文本的示例:

struct ContentView: View 

    @State private var makeBold = false

    var body: some View 

        Button(action: 
            self.makeBold.toggle()
        , label: 
            Text("Tap me to Toggle Bold")
                .modifier(ConditionalModifier(isBold: makeBold))
        )

    


struct ConditionalModifier: ViewModifier 

    var isBold: Bool

    func body(content: Content) -> some View 
        Group 
            if self.isBold 
                content.font(.custom("HelveticaNeue-Bold", size: 14))
            
            else
                content.font(.custom("HelveticaNeue", size: 14))
            
        
    

【讨论】:

【参考方案5】:

我认为实现Conditional Properties 的首选方法如下。如果您应用了动画效果很好。我尝试了其他一些方法,但它会影响我应用的动画。

您可以添加您的条件来代替我保留的 false .conditionalView(false)

下面,你可以切换到true查看'Hello, World!'是否会显示为BOLD

struct ContentView: View 
    var body: some View 
        Text("Hello, World!")
        .conditionalView(false)
        // or use:
        // .modifier(ConditionalModifier(isBold: false))

    


extension View 
    func conditionalView(_ value: Bool) -> some View 
        self.modifier(ConditionalModifier(isBold: value))
  


struct ConditionalModifier: ViewModifier 
    var isBold: Bool
    func body(content: Content) -> some View 
    Group 
        if self.isBold 
            content.font(.custom("HelveticaNeue-Bold", size: 14))
        else
            content.font(.custom("HelveticaNeue", size: 14))
        
    
  

【讨论】:

View 不需要 conditionalView 扩展名。使用.modifier(ConditionalModifier(isBold : viewIsBold))直接初始化ConditionalModifier结构体 @PeterSchorn 我想我明白了你的意思,建议你也添加你的答案。【参考方案6】:

就这么简单

import SwiftUI

struct SomeView: View 
  let value: Double
  let maxValue: Double
  
  private let lineWidth: CGFloat = 1
  
  var body: some View 
    Circle()
      .strokeBorder(Color.yellow, lineWidth: lineWidth)
      .overlay(optionalOverlay)
  
  
  private var progressEnd: CGFloat 
    CGFloat(value) / CGFloat(maxValue)
  
  
  private var showProgress: Bool 
    value != maxValue
  
  
  @ViewBuilder private var optionalOverlay: some View 
    if showProgress 
      Circle()
        .inset(by: lineWidth / 2)
        .trim(from: 0, to: progressEnd)
        .stroke(Color.blue, lineWidth: lineWidth)
        .rotationEffect(.init(degrees: 90))
    
  


struct SomeView_Previews: PreviewProvider 
  static var previews: some View 
    Group 
      SomeView(value: 40, maxValue: 100)
      
      SomeView(value: 100, maxValue: 100)
    
      .previewLayout(.fixed(width: 100, height: 100))
  

【讨论】:

以上是关于SwiftUI 中的条件属性的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 因“前提条件失败:属性未能设置初始值:85”而崩溃

如何将我的自定义 ButtonStyle 添加到 swiftui3 中的快捷方式 swiftui .buttonstyle() 属性

SwiftUI 中的惰性属性

观察自定义视图 SwiftUI 中的属性变化

删除嵌套属性中的元素时更新 SwiftUI 列表

可以直接使用 Publisher 作为 SwiftUI 中的 @ObjectBinding 属性吗?