16

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

使用下面的代码,我得到了错误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() {
        // ...
    }
}
4

6 回答 6

27

感谢这篇文章,我找到了一个界面非常简单的选项:

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

警告:条件视图修饰符带有一些注意事项,您可能想在阅读后重新考虑使用上面的代码:https ://www.objc.io/blog/2021/08/24/conditional-view-modifiers/

于 2020-07-17T22:07:51.250 回答
14

在 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,因为它的效率低于使用完全类型化的视图。例如,这条推文这条推文

于 2019-08-12T19:59:09.100 回答
13

Rob Mayoff已经很好地解释了为什么会出现这种行为并提出了两种解决方案(透明覆盖或使用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() {
        // ...
    }
}
于 2019-08-12T20:03:50.097 回答
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结构中,您还可以消除 Group 视图,而使用@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))
            }
        }
    }
}
于 2020-04-16T15:14:04.827 回答
0

就这么简单

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))
  }
}
于 2020-11-09T16:45:02.207 回答
0

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

您可以添加您的条件来代替我保留的 .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))
        }
    }
  }
}
于 2020-03-24T13:39:14.447 回答