【问题标题】:Gradient as foreground color of Text in SwiftUI渐变作为 SwiftUI 中文本的前景色
【发布时间】:2020-03-18 08:28:14
【问题描述】:

有没有什么方法可以在 SwiftUI 中使用渐变作为文本的前景色?

提前感谢您的回答!

【问题讨论】:

标签: swift user-interface swiftui


【解决方案1】:

这可以在纯SwiftUI 中轻松完成,而无需使用UIViewRepresentable。您需要用文本遮盖渐变:

LinearGradient(gradient: Gradient(colors: [.pink, .blue]),
               startPoint: .top,
               endPoint: .bottom)
    .mask(Text("your text"))

【讨论】:

  • 不幸的是,LinearGradient 视图可能是贪婪的并使用了所有可用空间。这就是为什么您的示例显示它与屏幕顶部齐平,而不是像单独的简单Text 那样位于中心。此外,贪婪渐变会在其占用的空间的整个高度上运行,但蒙版仅在顶部使用一点。这就是为什么您在底部看不到完整的蓝色。
【解决方案2】:

我已经用新答案更新了我的答案,你可以试试。旧的答案仍然可用。

新答案

import SwiftUI

struct GradientText: View {
    var body: some View {
        Text("Gradient foreground")
            .gradientForeground(colors: [.red, .blue])
            .padding(.horizontal, 20)
            .padding(.vertical)
            .background(Color.green)
            .cornerRadius(10)
            .font(.title)
       }
}

extension View {
    public func gradientForeground(colors: [Color]) -> some View {
        self.overlay(
            LinearGradient(
                colors: colors,
                startPoint: .topLeading,
                endPoint: .bottomTrailing)
        )
            .mask(self)
    }
}

输出


旧答案

SwiftUI你也可以这样做,如下使用Add gradient color to text的概念

渐变视图:

struct GradientView: View {
    var body: some View {
        VStack {
            GradientLabelWrapper(width: 150) //  you can give as you want
                .frame(width: 200, height: 200, alignment: .center) // set frame as you want
        }
    }
}

GradientLabelWrapper:

struct GradientLabelWrapper: UIViewRepresentable {

    var width: CGFloat
    var text: String?
    typealias UIViewType = UIView
    
    func makeUIView(context: UIViewRepresentableContext<GradientLabelWrapper>) -> UIView {
    
        let label = UILabel()
        label.lineBreakMode = .byWordWrapping
        label.numberOfLines = 0
        label.preferredMaxLayoutWidth = width
        label.text = text ?? ""
        label.font = UIFont.systemFont(ofSize: 25) //set as you need
        label.applyGradientWith(startColor: .red, endColor: .blue)
        return label
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<GradientLabelWrapper>) {
    }
} 

UILabel : 扩展

extension UILabel {

    func applyGradientWith(startColor: UIColor, endColor: UIColor) {
        
        var startColorRed:CGFloat = 0
        var startColorGreen:CGFloat = 0
        var startColorBlue:CGFloat = 0
        var startAlpha:CGFloat = 0
        
        if !startColor.getRed(&startColorRed, green: &startColorGreen, blue: &startColorBlue, alpha: &startAlpha) {
            return
        }
        
        var endColorRed:CGFloat = 0
        var endColorGreen:CGFloat = 0
        var endColorBlue:CGFloat = 0
        var endAlpha:CGFloat = 0
        
        if !endColor.getRed(&endColorRed, green: &endColorGreen, blue: &endColorBlue, alpha: &endAlpha) {
            return
        }
        
        let gradientText = self.text ?? ""
        
        let textSize: CGSize = gradientText.size(withAttributes: [NSAttributedString.Key.font:self.font!])
        let width:CGFloat = textSize.width
        let height:CGFloat = textSize.height
        
        UIGraphicsBeginImageContext(CGSize(width: width, height: height))
        
        guard let context = UIGraphicsGetCurrentContext() else {
            UIGraphicsEndImageContext()
            return
        }
        
        UIGraphicsPushContext(context)
        
        let glossGradient:CGGradient?
        let rgbColorspace:CGColorSpace?
        let num_locations:size_t = 2
        let locations:[CGFloat] = [ 0.0, 1.0 ]
        let components:[CGFloat] = [startColorRed, startColorGreen, startColorBlue, startAlpha, endColorRed, endColorGreen, endColorBlue, endAlpha]
        rgbColorspace = CGColorSpaceCreateDeviceRGB()
        glossGradient = CGGradient(colorSpace: rgbColorspace!, colorComponents: components, locations: locations, count: num_locations)
        let topCenter = CGPoint.zero
        let bottomCenter = CGPoint(x: 0, y: textSize.height)
        context.drawLinearGradient(glossGradient!, start: topCenter, end: bottomCenter, options: CGGradientDrawingOptions.drawsBeforeStartLocation)
        
        UIGraphicsPopContext()
        
        guard let gradientImage = UIGraphicsGetImageFromCurrentImageContext() else {
            UIGraphicsEndImageContext()
            return
        }
        
        UIGraphicsEndImageContext()
        self.textColor = UIColor(patternImage: gradientImage)
    }
}

【讨论】:

  • 在小部件中不起作用。小部件仅适用于纯 Swift。
  • @MojtabaHosseini。我更新了我的ans,你可以检查一下。让我知道是否有效?
  • 不,小部件仅适用于纯 swift。如果你使用UIViewRepresentable,它会变成白色
  • 不错的更新,效果很好,喜欢这个扩展!
【解决方案3】:

我想这应该会有所帮助。适用于文本、图像和任何其他视图。

import SwiftUI

// MARK: - API
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension View {
    public func foreground<Overlay: View>(_ overlay: Overlay) -> some View {
        _CustomForeground(overlay: overlay, for: self)
    }
}

// MARK: - Implementation
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
private struct _CustomForeground<Content: View, Overlay: View>: View {
    let content: Content
    let overlay: Overlay
    
    internal init(overlay: Overlay, for content: Content) {
        self.content = content
        self.overlay = overlay
    }
    
    var body: some View {
        content.overlay(overlay).mask(content)
    }
}

我个人最喜欢这种方法。但你也可以将它组合成:

import SwiftUI

// MARK: - API
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension View {
    public func foreground<Overlay: View>(_ overlay: Overlay) -> some View {
        self.overlay(overlay).mask(self)
    }
}

使用示例?

// MARK: - Example
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
struct GradientTextDemo: View {
    var body: some View {
        Text("Gradient foreground")
            .foreground(makeGradient())
            .padding(.horizontal, 32)
            .padding(.vertical)
            .background(Color.black)
            .cornerRadius(12)
    }
    
    func makeGradient() -> some View {
        LinearGradient(
            gradient: .init(colors: [.red, .orange]),
            startPoint: .topLeading,
            endPoint: .bottomTrailing
        )
    }
}

See gist

【讨论】:

  • 对于这么简单的任务来说,这太复杂了。 SwiftUI 已经支持这一点,我认为 A simple extension would be enough
  • 不要认为我的实现很复杂,我仍然喜欢 API ? 优点:逻辑被隔离到具有显式名称的视图中,返回一些前景视图,而不仅仅是 ZStack;没有尺码,只涉及口罩;方法本身的清晰命名 - foreground@available 属性,所以你仍然可以支持任何操作系统;一个扩展版本,和你的一样短;要点(我喜欢要点)。而且我没有看到缺点?但值得一提的是,我们的逻辑是相似的,因此任何实现都比本主题中的其他最佳答案更好。但仍然不是最喜欢的......
  • 合理,但extension 版本(就在使用示例上方)无论如何都可以工作?
  • 据我了解,您实际上可以使用自定义视图,因此即使_CustomForegound 示例也应该可以工作developer.apple.com/videos/play/wwdc2020/10033。此外,我们的两种方法都使用纯 SwiftUI 顺便说一句?
【解决方案4】:

您可以将任何渐变或其他类型的视图指定为自定义大小的蒙版,例如:

Text("Gradient is on FIRE !!!")
    .selfSizeMask(
        LinearGradient(
            gradient: Gradient(colors: [.red, .yellow]),
            startPoint: .bottom,
            endPoint: .top)
    )

使用这个简单的小扩展:

extension View {
    func selfSizeMask<T: View>(_ mask: T) -> some View {
        ZStack {
            self.opacity(0)
            mask.mask(self)
        }.fixedSize()
    }
}


? 奖金 1

您可以将其应用于任何类型的view


? 奖金 2

此外,您可以在其上应用所有渐变或任何类型的视图:

【讨论】:

    【解决方案5】:

    SwiftUI 中的新功能:修饰符 .foregroundStyle() (iOS15+)

    Text(“Gradient”)
        .foregroundStyle(
            .linearGradient(
                colors: [.red, .blue],
                startPoint: .top,
                endPoint: .bottom
            )
        )
    

    【讨论】:

    • 仅适用于 iOS 15 及更高版本。
    • @MohamedWasiq true,更新了答案。谢谢!
    【解决方案6】:

    将其创建为TextStyle 是有意义的,例如LabelStyleButtonStyle,但奇怪的是SwiftUI 出于某种原因将Text 排除在样式修饰符之外。在 SwiftUI 发布 API(?)之前,可以创建自定义的:

    protocol TextStyle: ViewModifier {}
    
    extension View {
        func textStyle<T: TextStyle>(_ modifier: T) -> some View {
            self.modifier(modifier)
        }
    }
    

    有了这个,就可以创建自定义文本样式(受公认答案启发的覆盖/遮罩技术):

    struct LinearGradientTextStyle: TextStyle {
        let colors: [Color]
        let startPoint: UnitPoint
        let endPoint: UnitPoint
    
        func body(content: Content) -> some View {
            content
                .overlay(
                    LinearGradient(
                        colors: colors,
                        startPoint: .topLeading,
                        endPoint: .bottomTrailing
                    )
                )
                .mask(content)
        }
    }
    
    extension TextStyle where Self == LinearGradientTextStyle {
        static func linearGradient(
            _ colors: [Color],
            startPoint: UnitPoint = .top,
            endPoint: UnitPoint = .bottom
        ) -> Self {
            LinearGradientTextStyle(
                colors: colors,
                startPoint: startPoint,
                endPoint: endPoint
            )
        }
    }
    

    然后您可以像使用 LabelStyleButtonStyle 一样使用它:

    Text("This has a custom linear gradient mask")
        .textStyle(.linearGradient([.red, .purple, .blue, .yellow]))
    

    【讨论】:

      【解决方案7】:

      您可以使用它来将渐变作为文本的前景色:

      Text("Hello World")
                      .padding()
                      .foregroundColor(.white)
                      .background(LinearGradient(gradient: Gradient(colors: [.white, .black]), startPoint: .top, endPoint: .bottom))
      

      希望这会有所帮助 :) 您也可以使用此链接作为参考:https://www.hackingwithswift.com/quick-start/swiftui/how-to-render-a-gradient

      【讨论】:

      • 很遗憾,我之前尝试过它只是给你一个错误,但感谢您的努力!
      猜你喜欢
      • 2020-12-13
      • 1970-01-01
      • 1970-01-01
      • 2022-12-21
      • 2020-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多