Is there any way of using a gradient as foregroundColor of Text in SwiftUI?
Thanks for the answers in advance!
Is there any way of using a gradient as foregroundColor of Text in SwiftUI?
Thanks for the answers in advance!
.foregroundStyle()
(iOS15+)Text(“Gradient”)
.foregroundStyle(
.linearGradient(
colors: [.red, .blue],
startPoint: .top,
endPoint: .bottom
)
)
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(
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)
}
}
UIViewRepresentable
–
Puton 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"))
LinearGradient
view is greedy and uses all available space. That's why your sample shows it flush with the top of the screen and not in the center as a simple Text
alone would be. Also, the greedy gradient runs the full height of the space it takes, but the mask is only using a bit at the top. That is why you don't see a full blue at the bottom. –
Eastsoutheast 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 🌚
// 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
)
}
}
foreground
; @available
attributes, so you can still support any OS; An extension version, which is as short as yours; Gist (i like gists). And I do not see cons 🌚 But worth mentioning that our logic is similar, so any implementation is better than other top answers in this topic. But still not top liked... –
Etom extension
version (right above usage example) will work anyway 🙂 –
Etom _CustomForegound
example should work developer.apple.com/videos/play/wwdc2020/10033. Also, both our approaches use pure SwiftUI btw 🌚 –
Etom .foregroundStyle()
(iOS15+)Text(“Gradient”)
.foregroundStyle(
.linearGradient(
colors: [.red, .blue],
startPoint: .top,
endPoint: .bottom
)
)
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()
}
}
You can apply it on any sort of view
:
Also, you can apply all gradients or any sort of view on it:
It would make sense to create this as a TextStyle
, like LabelStyle
and ButtonStyle
, but strangely SwiftUI left Text
out of the styling modifiers for some reason. A custom one could be created going forward until SwiftUI releases an API (?):
protocol TextStyle: ViewModifier {}
extension View {
func textStyle<T: TextStyle>(_ modifier: T) -> some View {
self.modifier(modifier)
}
}
With this in place, custom text styles can be created (overlay/mask technique inspired by accepted answer):
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
)
}
}
Then you can use it the same way you would use LabelStyle
or ButtonStyle
:
Text("This has a custom linear gradient mask")
.textStyle(.linearGradient([.red, .purple, .blue, .yellow]))
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
© 2022 - 2024 — McMap. All rights reserved.