如何更改自定义视图的背景颜色,类似于 SwiftUI 视图

huangapple go评论51阅读模式
英文:

How do I change the background color of a Custom View like SwiftUI Views

问题

我想让我的自定义视图的行为类似于SwiftUI视图,为了简化,我考虑了背景和前景颜色。现在,与其分别定义视图的每个子视图的前景/背景颜色,我想要像SwiftUI视图那样在外部进行更改。为了简化,这里只有一个子视图:Text;在我的尝试中。

import SwiftUI

struct TestButton: View {
    var text: String
    @State private var foregroundColor: Color = .primary
    @State private var backgroundColor: Color = .white
    @State private var id = UUID()

    var body: some View {
        Text(text)
            .foregroundColor(foregroundColor )
            .padding()
            .background(backgroundColor)
            .cornerRadius(10)
            .id(id)
            .accentColor(foregroundColor)
    }

    public func foregroundColor(_ color: Color)  {
        foregroundColor = color

        id = UUID()
    }

    public func backgroundFill(_ color: Color) {
        backgroundColor = color
        id = UUID()
    }
}

struct TestButton_Previews: PreviewProvider {
    static var previews: some View {
        TestButton(text: "Test")
            .foregroundColor(.red)
            .backgroundFill(.green)
    }
}

这在Xcode 14.3的画布上不起作用,尽管我期望它能够工作。所以,我转到模拟器上看是否有区别,结果与画布上的一样。

我们如何设计一个自定义视图,以便我们可以像SwiftUI的View一样使用颜色更改?

英文:

I wanted my Custom views to behave like SwiftUI Views and for simplicity I'm considering the background and foreground colors. Now, instead of individually defining the foreground/background color of each subview of a view, I want to change it externally as in SwiftUI views. For simplicity, there is one subView: Text; on my tries.

import SwiftUI

struct TestButton: View {
    var text: String
    @State private var foregroundColor: Color = .primary
    @State private var backgroundColor: Color = .white
    @State private var id = UUID()

    var body: some View {
        Text(text)
            .foregroundColor(foregroundColor )
            .padding()
            .background(backgroundColor)
            .cornerRadius(10)
            .id(id)
            .accentColor(foregroundColor)
    }

    public func foregroundColor(_ color: Color)  {
        foregroundColor = color

        id = UUID()
    }

    public func backgroundFill(_ color: Color) {
        backgroundColor = color
        id = UUID()
    }
}

struct TestButton_Previews: PreviewProvider {
    static var previews: some View {
        TestButton(text: "Test")
            .foregroundColor(.red)
            .backgroundFill(.green)
    }
}

This doesn't work on Xcode 14.3 canvas, although I was expecting to work. So, I've turned into emulator to see there is a difference and the result was same as in canvas.

How can we design a Custom View so that we can use color changes as in SwiftUI's View?

答案1

得分: 3

你的问题非常有趣,因为其中有些是相对简单的,有些是不可能的,它们之间存在非常令人惊讶的界限。我将从非常简单的情况开始,逐步展示到不可能的情况。

首先,让我们从将.foregroundColor()映射到文本颜色开始。这非常简单,实际上不需要任何代码。它 "只是有效 "。

struct TestButton: View {
    var text: String

    // 修复这个。它不应该是变量或 State。
    // 它需要是常量。但实际上,对于这个示例来说并不重要
    private let id = UUID()

    var body: some View {
        Text(text)
            .padding()
            .cornerRadius(10)
            .id(id)
    }
}

struct TestButton_Previews: PreviewProvider {
    static var previews: some View {
        TestButton(text: "Test")
            .foregroundColor(.red)
    }
}

这里不需要任何变量。.foregroundColor()的值直接传递给Text,无需任何干预。神奇!

那么,如何为 backgroundFill 重新创建这种神奇呢?使用 Environment。这是这些设置如何传递到层次结构的方式。首先,创建一个环境键,并扩展 EnvironmentValues 来访问它:

private struct BackgroundFillKey: EnvironmentKey {
    static let defaultValue: Color = .white
}

extension EnvironmentValues {
    var backgroundFill: Color {
        get { self[BackgroundFillKey.self] }
        set { self[BackgroundFillKey.self] = newValue }
    }
}

然后,使用 @Environment 来访问它:

struct TestButton: View {
    var text: String

    @Environment(\.backgroundFill)
    var backgroundFill: Color

    @State private var id = UUID()

    var body: some View {
        Text(text)
            .padding()
            .background(backgroundFill)
            .cornerRadius(10)
            .id(id)
    }
}

为了方便起见,添加一个用于设置它的 View 扩展:

extension View {
    func backgroundFill(_ color: Color) -> some View {
        environment(\.backgroundFill, color)
    }
}

然后使用它:

struct TestButton_Previews: PreviewProvider {
    static var previews: some View {
        TestButton(text: "Test")
            .foregroundColor(.red)
            .backgroundFill(.green)
    }
}

这就是 SwiftUI 管理自己的 Views 的方式。

但我遗漏了你问题的一部分,即这个修饰符:

.accentColor(foregroundColor)

在你的示例中,这实际上并不重要。Text 不会以这种方式使用 accentColor。但我认为你正在问一个非常一般性的问题,即 "我如何将 accentColor 设置为 foregroundColor?" 并且更广泛地说, "我如何访问 foregroundColor?"

答案是不可以。该环境值标记为内部。我不知道为什么。这里有一些有用的评论:How To Read Foreground and Tint Colors in SwiftUI Views,以及一个可能的解决方法/黑客。但最终,它并不是公开可用的。

当然,你可以按照上面对 .backgroundFill 的解释创建自己的 .foregroundTextButtonColor,但它不会与 SwiftUI 的 .foregroundColor 集成。

英文:

Your question is very interesting because of what is fairly straight-forward and what is impossible, and the very surprising dividing line between them. I'll build this up from absolutely trivial to impossible.

First, start with mapping .foregroundColor() to the color of the text. That is trivial and in fact requires no code at all. It "just works."

struct TestButton: View {
    var text: String

    // Fixed this. It should not be variable or State.
    // It needs to be constant. But it doesn't actually matter for this example
    private let id = UUID()

    var body: some View {
        Text(text)
            .padding()
            .cornerRadius(10)
            .id(id)
    }
}

struct TestButton_Previews: PreviewProvider {
    static var previews: some View {
        TestButton(text: "Test")
            .foregroundColor(.red)
    }
}

There's no need for any variables. The value of .foregroundColor() is passed directly to Text with no intervention at all. Magic!

How do you recreate that magic for backgroundFill? With Environment. This is how these settings are passed down the hierarchy. First, create an environment key, and extend EnvironmentValues to access it:

private struct BackgroundFillKey: EnvironmentKey {
    static let defaultValue: Color = .white
}

extension EnvironmentValues {
    var backgroundFill: Color {
        get { self[BackgroundFillKey.self] }
        set { self[BackgroundFillKey.self] = newValue }
    }
}

Then, use @Environment to access it:

struct TestButton: View {
    var text: String

    @Environment(\.backgroundFill)
    var backgroundFill: Color

    @State private var id = UUID()

    var body: some View {
        Text(text)
            .padding()
            .background(backgroundFill)
            .cornerRadius(10)
            .id(id)
    }
}

For convenience, add an extension to View to set it:

extension View {
    func backgroundFill(_ color: Color) -> some View {
        environment(\.backgroundFill, color)
    }
}

And then use it:

struct TestButton_Previews: PreviewProvider {
    static var previews: some View {
        TestButton(text: "Test")
            .foregroundColor(.red)
            .backgroundFill(.green)
    }
}

And this is how SwiftUI manages its own Views.

But I've left out one bit of your question, which is this modifier:

        .accentColor(foregroundColor)

In your example, this doesn't actually matter. Text doesn't use accentColor this way. But I think you're asking a very general question, which is "how do I set accentColor to be foregroundColor?" And a little more broadly, "how do I access foregroundColor?"

The answer is you don't. That environment value is marked internal. I don't know why. Here is some useful commentary: How To Read Foreground and Tint Colors in SwiftUI Views, and a possible workaround/hack. But ultimately, it's not publicly available.

You can, of course, create your own .foregroundTextButtonColor following the above explanation of .backgroundFill, but it won't integrate with SwiftUI's .foregroundColor.

huangapple
  • 本文由 发表于 2023年6月1日 05:36:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76377455.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定