SwiftUI的ViewModifier用于自定义视图

10
有没有办法创建一个修饰符来更新在被修改的视图中的@State private var
我有一个自定义视图,可以返回带有“动态”背景颜色的Text 或者带有“动态”前景颜色的Circle
struct ChildView: View {
    var theText = ""
    
    @State private var color = Color(.purple)
    
    var body: some View {
        HStack {
            if theText.isEmpty {          // If there's no theText, a Circle is created
                Circle()
                    .foregroundColor(color)
                    .frame(width: 100, height: 100)
            } else {                      // If theText is provided, a Text is created
                Text(theText)
                    .padding()
                    .background(RoundedRectangle(cornerRadius: 25.0)
                                    .foregroundColor(color))
                    .foregroundColor(.white)
            }
        }
    }
}

我在应用的不同部分中重复使用这个视图。如您所见,我需要指定的唯一参数是theText。因此,创建此ChildView的可能方式如下:

struct SomeParentView: View {
    var body: some View {
        VStack(spacing: 20) {
            ChildView()   // <- Will create a circle

            ChildView(theText: "Hello world!")   // <- Will create a text with background
        }
    }
}

在此输入图片描述

到目前为止都很简单。现在,我需要创建一个修改器或类似的东西,以便在父视图中,如果我需要更多定制化,我可以将@State private var color的值从.red更改为其他颜色。下面是我试图实现的示例:

struct SomeOtherParentView: View {
    var body: some View {
        HStack(spacing: 20) {
            ChildView()

            ChildView(theText: "Hello world!")
                .someModifierOrTheLike(color: Color.green)   // <- what I think I need
        }
    }
}

我知道我可以从那个变量中删除"private"关键字并在构造函数中作为参数传递"color"(例如:ChildView(theText: "Hello World", color: .green)),但我认为这不是解决这个问题的方法,因为如果我需要更多的子视图自定义,我最终会得到一个非常大的构造函数。
所以,你有什么想法可以实现我想要的效果吗?希望我解释清楚了 :) 谢谢!!!
3个回答

13

这是您的观点,修饰器只是生成另一个修改后的视图的函数,因此……这里有一些可能实现您想要的简单方法。

在 Xcode 12 / iOS 14 上测试通过

demo

struct ChildView: View {
    var theText = ""
    
    @State private var color = Color(.purple)
    
    var body: some View {
        HStack {
            if theText.isEmpty {          // If there's no theText, a Circle is created
                Circle()
                    .foregroundColor(color)
                    .frame(width: 100, height: 100)
            } else {                      // If theText is provided, a Text is created
                Text(theText)
                    .padding()
                    .background(RoundedRectangle(cornerRadius: 25.0)
                                    .foregroundColor(color))
                    .foregroundColor(.white)
            }
        }
    }
    
    // simply modify self, as self is just a value
    public func someModifierOrTheLike(color: Color) -> some View {
        var view = self
        view._color = State(initialValue: color)
        return view.id(UUID())
    }
}

谢谢@Asperi,太完美了!我之前对State(initialValue:)一无所知。这正是我一直在寻找的,感谢!!! :D - Aнгел
嗨@Asperi,以下情况下修饰符不起作用:在父视图中,将修饰符添加到子视图,并考虑使用基于状态布尔变量的动态颜色(例如:“@State var toggleColor = false”),因此对修饰符的调用看起来像这样:“. someModifierOrTheLike(color: toggleColor ? .red : .blue)”。在这种情况下,“someModifierOrTheLike”未更新子视图上的颜色。有什么想法吗? - Aнгел
2
当然,这超出了最初的问题范围...但请查看更新的变体,它涵盖了新情况。 - Asperi
1
@Asperi 你好! 你有没有解决方案可以像这样链接修饰符? 问题在于view.id()将自定义视图转换为一般的某些视图。 - Balázs Papp

4
使用自定义的ViewModifier确实是一种帮助向用户公开更简单界面的方法,但将定制参数传递给视图(除了使用init之外)的一般想法是通过使用具有.environment 的环境变量。
struct MyColorKey: EnvironmentKey {
   static var defaultValue: Color = .black
}

extension EnvironmentValues {
   var myColor: Color {
      get { self[MyColorKey] }
      set { self[MyColorKey] = newValue }
   }
}

那么你可以在你的视图中依赖它:

struct ChildView: View {
   @Environment(\.myColor) var color: Color

   var body: some View {
      Circle()
         .foregroundColor(color)
         .frame(width: 100, height: 100)
   }
}

使用方法如下:

ChildView()
   .environment(\.myColor, .blue)

您可以使用视图修饰符使其更加美观:

struct MyColorModifier: ViewModifier {
   var color: Color

   func body(content: Content) -> some View {
      content
         .environment(\.myColor, color)
   }
}

extension ChildView {
   func myColor(_ color: Color) { 
      self.modifier(MyColorModifier(color: color) 
   }
}


ChildView()
   .myColor(.blue)

当然,如果您有多个自定义设置或者该操作对用户来说太低级,则可以创建一个ViewModifier来公开其中的一部分,或者像SwiftUI使用.buttonStyle(_:)一样创建一个封装了样式的类型。

谢谢 @New Dev,它能用但并不是我要找的答案。Asperi给出了答案。感谢你的帮助!!! :D - Aнгел

0

以下是基于Asperi的答案,您可以如何链接方法:

struct ChildView: View {
    @State private var foregroundColor = Color.red
    @State private var backgroundColor = Color.blue

    var body: some View {
        Text("Hello World")
            .foregroundColor(foregroundColor)
            .background(backgroundColor)
    }

    func foreground(color: Color) -> ChildView {
        var view = self
        view._foregroundColor = State(initialValue: color)
        return view
    }

    func background(color: Color) -> ChildView {
        var view = self
        view._backgroundColor = State(initialValue: color)
        return view
    }
}

struct ParentView: View {
    var body: some View {
        ChildView()
            .foreground(color: .yellow)
            .background(color: .green)
            .id(UUID())
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接