这里是一个 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)
}
}
}
我建议使用粗体。它适用于合理大小的字体和笔画宽度。对于更大的尺寸,您可能需要添加更多角度的文本偏移以覆盖该区域。
我找到了另一个创建笔划的技巧,但它只在你想要的笔划宽度不超过 1 时有效
Text("Hello World")
.shadow(color: .black, radius: 1)
我用了
shadow
,但确保半径只是1,以获得相同的效果
我认为没有办法“开箱即用”。
到目前为止(测试版 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 应用程序。
你可以用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")
设置说明这里.
在改为使用此解决方案之前,我使用了相当多的“偏移”文本解决方案,并且发现它的效果要好得多。并且它还有一个额外的好处,即允许内部空心的轮廓文本而无需下载包以获得简单的效果。
它的工作原理是堆叠 .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()
)
}
}
同样,这不是一个“完美”的解决方案,但它是一个简单有效的解决方案。
⚠️ 编辑:清理 Xcode 缓存后……它不再工作了😕 我找不到修复它的方法。
+
或 x
形状以获得更好的外观)。当我尝试更大的笔触时,标签变得可见,而且看起来很糟糕。在大多数情况下,辅助功能也没有得到妥善处理。
这就是为什么我试图想出一个 vanilla SwiftUI,非常简单但有效的解决方案。
我的主要想法是使用
.blur(radius: radius, opaque: true)
进行完美的笔划。
玩了所有修改器几个小时后,我找到了一个 8 行解决方案,我相信你会喜欢它。 由于模糊是不透明,它也是像素化,我找不到避免这种情况的方法。还有,第二个
drawingGroup
加了个奇怪的圆角方形,不知道为什么
特点 | 工作? |
---|---|
香草 SwiftUI | ✅ |
自定义大小笔画 | ✅ |
像素大小描边 | ❌(单位看不懂) |
彩色描边 | ✅ |
不透明斯托克颜色 | ✅ |
圆角笔划 | ❌ |
没有描边剪裁 | ✅ |
完美填充 | ✅ |
原文颜色保护 | ✅ |
无障碍 | ✅ |
无像素化 | ❌ |
适用于任何
|
✅ |
可读,评论... | ✅ |
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是这样的:
现在坏掉了,笔划黑底了:
我建议在所有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()
}
}
我看到了 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))
}
}