【发布时间】:2020-03-18 08:28:14
【问题描述】:
有没有什么方法可以在 SwiftUI 中使用渐变作为文本的前景色?
提前感谢您的回答!
【问题讨论】:
标签: swift user-interface swiftui
有没有什么方法可以在 SwiftUI 中使用渐变作为文本的前景色?
提前感谢您的回答!
【问题讨论】:
标签: swift user-interface swiftui
【讨论】:
LinearGradient 视图可能是贪婪的并使用了所有可用空间。这就是为什么您的示例显示它与屏幕顶部齐平,而不是像单独的简单Text 那样位于中心。此外,贪婪渐变会在其占用的空间的整个高度上运行,但蒙版仅在顶部使用一点。这就是为什么您在底部看不到完整的蓝色。
我已经用新答案更新了我的答案,你可以试试。旧的答案仍然可用。
新答案
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)
}
}
【讨论】:
UIViewRepresentable,它会变成白色
我想这应该会有所帮助。适用于文本、图像和任何其他视图。
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
)
}
}
【讨论】:
foreground; @available 属性,所以你仍然可以支持任何操作系统;一个扩展版本,和你的一样短;要点(我喜欢要点)。而且我没有看到缺点?但值得一提的是,我们的逻辑是相似的,因此任何实现都比本主题中的其他最佳答案更好。但仍然不是最喜欢的......
extension 版本(就在使用示例上方)无论如何都可以工作?
_CustomForegound 示例也应该可以工作developer.apple.com/videos/play/wwdc2020/10033。此外,我们的两种方法都使用纯 SwiftUI 顺便说一句?
您可以将任何渐变或其他类型的视图指定为自定义大小的蒙版,例如:
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()
}
}
您可以将其应用于任何类型的view:
此外,您可以在其上应用所有渐变或任何类型的视图:
【讨论】:
将其创建为TextStyle 是有意义的,例如LabelStyle 和ButtonStyle,但奇怪的是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
)
}
}
然后您可以像使用 LabelStyle 或 ButtonStyle 一样使用它:
Text("This has a custom linear gradient mask")
.textStyle(.linearGradient([.red, .purple, .blue, .yellow]))
【讨论】:
您可以使用它来将渐变作为文本的前景色:
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
【讨论】: