如何在 SwiftUI 中制作文字描边?

问题描述 投票:0回答:8

我正在尝试在 SwiftUI 中进行文字描边或在我的文字上添加边框,而不是

Text()
项目中的字母。

可能吗?

我想用边框做这个效果:
(来源:noelshack.com

text border swiftui outline stroke
8个回答
27
投票

这里是一个 100% SwiftUI 的解决方案。不完美,但它可以工作,它使您可以完全 SwiftUI 控制结果视图。

import SwiftUI

struct SomeView: View {
    var body: some View {
        StrokeText(text: "Sample Text", width: 0.5, color: .red)
            .foregroundColor(.black)
            .font(.system(size: 12, weight: .bold))

    }
}

struct StrokeText: View {
    let text: String
    let width: CGFloat
    let color: Color

    var body: some View {
        ZStack{
            ZStack{
                Text(text).offset(x:  width, y:  width)
                Text(text).offset(x: -width, y: -width)
                Text(text).offset(x: -width, y:  width)
                Text(text).offset(x:  width, y: -width)
            }
            .foregroundColor(color)
            Text(text)
        }
    }
}

我建议使用粗体。它适用于合理大小的字体和笔画宽度。对于更大的尺寸,您可能需要添加更多角度的文本偏移以覆盖该区域。


14
投票

我找到了另一个创建笔划的技巧,但它只在你想要的笔划宽度不超过 1 时有效

Text("Hello World")
   .shadow(color: .black, radius: 1)

我用了

shadow
,但确保半径只是1,以获得相同的效果


12
投票

我认为没有办法“开箱即用”。
到目前为止(测试版 5)我们只能将笔画应用于

Shapes

例如:

struct SomeView: View {
    var body: some View {
        Circle().stroke(Color.red)
    }
}

但同样不适用于

Text
.

UIViewRepresentable

另一种方法是通过

UIKit
将好的 ol'
NSAttributedString
\
UIViewRepresentable
与 SwiftUI 一起使用。

像这样:

import SwiftUI
import UIKit

struct SomeView: View {
    var body: some View {
        StrokeTextLabel()
    }
}

struct StrokeTextLabel: UIViewRepresentable {
    func makeUIView(context: Context) -> UILabel {
        let attributedStringParagraphStyle = NSMutableParagraphStyle()
        attributedStringParagraphStyle.alignment = NSTextAlignment.center
        let attributedString = NSAttributedString(
            string: "Classic",
            attributes:[
                NSAttributedString.Key.paragraphStyle: attributedStringParagraphStyle,
                NSAttributedString.Key.strokeWidth: 3.0,
                NSAttributedString.Key.foregroundColor: UIColor.black,
                NSAttributedString.Key.strokeColor: UIColor.black,
                NSAttributedString.Key.font: UIFont(name:"Helvetica", size:30.0)!
            ]
        )

        let strokeLabel = UILabel(frame: CGRect.zero)
        strokeLabel.attributedText = attributedString
        strokeLabel.backgroundColor = UIColor.clear
        strokeLabel.sizeToFit()
        strokeLabel.center = CGPoint.init(x: 0.0, y: 0.0)
        return strokeLabel
    }

    func updateUIView(_ uiView: UILabel, context: Context) {}
}

#if DEBUG
struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}
#endif

结果

当然,您必须调整

NSAttributedString
的属性(大小、字体、颜色等)以生成所需的输出。为此,我会推荐Visual Attributed String macOS 应用程序。


5
投票

你可以用SwiftFX做到这一点

import SwiftUI
import SwiftFX

struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
            .fxEdge()
    }
}

这是 Swift 包:

.package(url: "https://github.com/hexagons/SwiftFX.git", from: "0.1.0")

设置说明这里.


2
投票

在改为使用此解决方案之前,我使用了相当多的“偏移”文本解决方案,并且发现它的效果要好得多。并且它还有一个额外的好处,即允许内部空心的轮廓文本而无需下载包以获得简单的效果。

它的工作原理是堆叠 .shadow 并保持较低的半径以在对象周围创建一条实线。如果您想要更粗的边框,则需要向扩展添加更多 .shadow 修饰符,但对于我所有的文本需求,这确实做得很好。另外,它也适用于图片。

它并不完美,但我喜欢简单的解决方案,这些解决方案停留在 SwifUI 的领域并且可以轻松实现。

最后,outline Bool 参数应用了一个倒置掩码(SwiftUI 缺少的其他东西),我也提供了该扩展。

extension View {
@ViewBuilder
func viewBorder(color: Color = .black, radius: CGFloat = 0.4, outline: Bool = false) -> some View {
    if outline {
        self
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .invertedMask(
                self
            )
    } else {
        self
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
            .shadow(color: color, radius: radius)
    }

}

}

extension View {
func invertedMask<Content : View>(_ content: Content) -> some View {
    self
        .mask(
            ZStack {
                self
                    .brightness(1)
                content
                    .brightness(-1)
            }.compositingGroup()
            .luminanceToAlpha()
        )
}

}

同样,这不是一个“完美”的解决方案,但它是一个简单有效的解决方案。


2
投票

⚠️ 编辑:清理 Xcode 缓存后……它不再工作了😕 我找不到修复它的方法。

其他答案很好,但也有缺点(对于我试过的):

  • 它们要么创建许多层,需要复杂的计算(堆叠阴影)。
  • 他们要么使用覆盖技术,下面有四个版本的文本(以
    +
    x
    形状以获得更好的外观)。当我尝试更大的笔触时,标签变得可见,而且看起来很糟糕。

在大多数情况下,辅助功能也没有得到妥善处理。

我的香草 SwiftUI 解决方案

这就是为什么我试图想出一个 vanilla SwiftUI,非常简单但有效的解决方案

我的主要想法是使用

.blur(radius: radius, opaque: true)
进行完美的笔划。

玩了所有修改器几个小时后,我找到了一个 8 行解决方案,我相信你会喜欢它。 由于模糊是不透明,它也是像素化,我找不到避免这种情况的方法。还有,第二个

drawingGroup
加了个奇怪的圆角方形,不知道为什么

特点

特点 工作?
香草 SwiftUI
自定义大小笔画
像素大小描边 ❌(单位看不懂)
彩色描边
不透明斯托克颜色
圆角笔划
没有描边剪裁
完美填充
原文颜色保护
无障碍
无像素化
适用于任何
View
可读,评论...

代码

extension View {

    /// Adds a stroke around the text. This method uses an opaque blur, hence the `radius` parameter.
    ///
    /// - Parameters:
    ///   - color: The stroke color. Can be non-opaque.
    ///   - radius: The blur radius. The value is not in pixels or points.
    ///             You need to try values by hand.
    /// - Warning:
    ///   - The opaque blur is pixelated, I couldn't find a way to avoid this.
    ///   - The second `drawingGroup` allows stroke opacity, but adds a
    ///     strange rounded square shape.
    ///
    /// # Example
    ///
    /// ```
    /// Text("Lorem ipsum")
    ///     .foregroundColor(.red)
    ///     .font(.system(size: 20, weight: .bold, design: .rounded))
    ///     .stroked(color: Color.blue.opacity(0.5), radius: 0.5)
    /// ```
    ///
    /// # Copyright
    ///
    /// CC BY-SA 4.0 [Rémi BARDON](https://github.com/RemiBardon)
    /// (posted on [Stack Overflow](https://stackoverflow.com/a/67348676/10967642))
    @ViewBuilder
    public func stroked(color: Color, radius: CGFloat) -> some View {
        ZStack {
            self
                // Add padding to avoid clipping
                // (3 is a a number I found when trying values… it comes from nowhere)
                .padding(3*radius)
                // Apply padding
                .drawingGroup()
                // Remove any color from the text
                .colorMultiply(.black)
                // Add an opaque blur around the text
                .blur(radius: radius, opaque: true)
                // Remove black background and allow color with opacity
                .drawingGroup()
                // Invert the black blur to get a white blur
                .colorInvert()
                // Multiply white by whatever color
                .colorMultiply(color)
                // Disable accessibility for background text
                .accessibility(hidden: true)
            self
        }
    }

}

截图

还在工作的时候,stroke是这样的:

现在坏掉了,笔划黑底了:


0
投票

我建议在所有8个方向绘制轮廓文本:

struct OutlinedText: View {
    var text: String
    var width: CGFloat
    var color: Color
    
    var body: some View {
        let diagonal: CGFloat = 1/sqrt(2) * width
        ZStack{
            ZStack{
                // bottom right
                Text(text).offset(x:  diagonal, y:  diagonal)
                // top left
                Text(text).offset(x: -diagonal, y: -diagonal)
                // bottom left
                Text(text).offset(x: -diagonal, y:  diagonal)
                // top right
                Text(text).offset(x:  diagonal, y: -diagonal)
                // left
                Text(text).offset(x:  -width, y: 0)
                // right
                Text(text).offset(x:  width, y: 0)
                // top
                Text(text).offset(x:  0, y: -width)
                // bottom
                Text(text).offset(x:  0, y: width)
            }
            .foregroundColor(color)
            Text(text)
        }.padding()
    }
}

0
投票

我看到了 Wendy 的回答,您可以使用 .shadow() 修饰符对其进行改进以增加笔触的大小

import SwiftUI

struct StrokeStyle: ViewModifier {

    var color: Color
    var lineWidth: Int

    func body(content: Content) -> some View {
        applyShadow(
            content: AnyView(content),
            lineWidth: lineWidth
        )
    }

    func applyShadow(content: AnyView, lineWidth: Int) -> AnyView {
        if lineWidth == 0 {
            return content
        } else {
            return applyShadow(
                content: AnyView(
                    content.shadow(
                        color: color,
                        radius: 1
                    )
                ),
                lineWidth: lineWidth - 1
            )
        }
    }
}

extension View {
    func strokeBorder(color: Color, lineWidth: Int) -> some View {
        self.modifier(LHSStrokeStyle(color: color, lineWidth: lineWidth))
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.