Gradient as foreground color of Text in SwiftUI

后端 未结 6 1057
终归单人心
终归单人心 2021-02-08 12:55

Is there any way of using a gradient as foregroundColor of Text in SwiftUI?

Thanks for the answers in advance!

相关标签:
6条回答
  • 2021-02-08 13:00

    I have updated my answer with new answer, you can try with that. Old one Answer is still available.

    New Answer

    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(gradient: .init(colors: colors),
                                        startPoint: .topLeading,
                                        endPoint: .bottomTrailing))
                .mask(self)
        }
    }
    

    Output


    Old Answer

    In SwiftUI You can also do it, as below using concept of Add gradient color to text

    GradientView :

    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

    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)
        }
    }
    
    0 讨论(0)
  • 2021-02-08 13:00

    I would say it isn't possible in pure SwiftUI. You need to use a modified UILabel and wrap it via UIViewRepresentable to use it in SwiftUI.

    I scraped code from previous answers together to make a basic example for a horizontal gradient:

    class GradientLabel: UILabel {
        var gradientColors: [CGColor] = []
    
        override func drawText(in rect: CGRect) {
            if let gradientColor = drawGradientColor(in: rect, colors: gradientColors) {
                self.textColor = gradientColor
            }
            super.drawText(in: rect)
        }
    
        private func drawGradientColor(in rect: CGRect, colors: [CGColor]) -> UIColor? {
            let currentContext = UIGraphicsGetCurrentContext()
            currentContext?.saveGState()
            defer { currentContext?.restoreGState() }
    
            let size = rect.size
            UIGraphicsBeginImageContextWithOptions(size, false, 0)
            guard let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(),
                                            colors: colors as CFArray,
                                            locations: nil) else { return nil }
    
            let context = UIGraphicsGetCurrentContext()
            context?.drawLinearGradient(gradient,
                                        start: CGPoint.zero,
                                        end: CGPoint(x: size.width, y: 0),
                                        options: [])
            let gradientImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            guard let image = gradientImage else { return nil }
            return UIColor(patternImage: image)
        }
    }
    
    struct MyTextView: UIViewRepresentable {
    
        var width: CGFloat
    
        func makeUIView(context: Context) -> GradientLabel {
            let label = GradientLabel()
            label.gradientColors = [UIColor.blue.cgColor, UIColor.red.cgColor]
            label.lineBreakMode = .byWordWrapping
            label.numberOfLines = 0
            label.preferredMaxLayoutWidth = width
            label.text = "Here's a lot of text for you to display. It won't fit on the screen."
            return label
        }
    
        func updateUIView(_ view: GradientLabel, context: Context) {
        }
    }
    
    struct ContentView: View {
    
        var body: some View {
            GeometryReader { geometry in
                MyTextView(width: geometry.size.width)
            }
        }
    }
    

    Reference to the GradientLabel class

    Reference to wrapping a UILabel to use in SwiftUI

    I hope this helps!

    0 讨论(0)
  • 2021-02-08 13:12

    You can use this to have gradient as foreground color of your Text:

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

    Hope this helps :) you can also use this link for your reference: https://www.hackingwithswift.com/quick-start/swiftui/how-to-render-a-gradient

    0 讨论(0)
  • 2021-02-08 13:15

    I guess that should help. Works with text, images and any other views.

    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)
        }
    }
    
    

    Personaly I like that approach the most. But also you can combine it into:

    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)
        }
    }
    

    Usage example

    0 讨论(0)
  • 2021-02-08 13:15

    You can assign any gradient or other type of view as a self-size mask like:

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

    with this simple tiny extension:

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

    Demo


    0 讨论(0)
  • 2021-02-08 13:22

    This can be easily done in pure SwiftUI without using UIViewRepresentable. You need to mask a gradient with your text:

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

    0 讨论(0)
提交回复
热议问题