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() 属性