我正在绘制一个弯曲的文本视图,我希望每个字母之间的间距相等。
到目前为止,我尝试将字体更新为
.font(.system(size: 14, design: .monospaced))
,这使字母之间的间距相等,但它不是正确的字体。
目前,间距如下所示:
理想情况下,我希望间距看起来像这样(使用正确的自定义字体):
struct ContentView: View {
@State private var letterWidths: [Int: Double] = [:]
private let title = "AVENIRNEXT"
var body: some View {
ZStack {
ForEach(Array(title.enumerated()), id: \.offset) { index, letter in
VStack {
Text(String(letter))
.font(.custom("AvenirNext-DemiBold", size: 14))
.kerning(3)
.background(
GeometryReader { geometry in // using this to get the width of each letter
Color.clear
.preference(
key: LetterWidthPreferenceKey.self,
value: geometry.size.width
)
}
)
.onPreferenceChange(LetterWidthPreferenceKey.self, perform: { width in
letterWidths[index] = width
})
Spacer()
}
.rotationEffect(fetchAngle(at: index))
}
.frame(width: 300, height: 300 * 0.75)
.rotationEffect(.degrees(335))
}
}
func fetchAngle(at letterPosition: Int) -> Angle {
let timesPi: (Double) -> Double = { $0 * .pi }
let radius: Double = 125
let circumference = timesPi(radius)
let finalAngle = timesPi(
letterWidths
.filter { $0.key < letterPosition }
.map(\.value)
.reduce(0, +) / circumference
)
return .radians(finalAngle)
}
}
struct LetterWidthPreferenceKey: PreferenceKey {
static var defaultValue: Double = 0
static func reduce(value: inout Double, nextValue: () -> Double) {
value = nextValue()
}
}
这里为您提供了一个解决方案,它使用覆盖来获取基础文本的框架大小,并获取文本中每个字符的框架大小和相对位置。有了这些信息,就可以计算每个字符所需的角度和偏移量。
结果始终以垂直轴为中心,因此如果您需要以不同角度显示弯曲文本,只需旋转结果即可。
struct CurvedText: View {
let string: String
let radius: CGFloat
var body: some View {
// Show the full text in plain form, hidden
Text(string)
.lineLimit(1)
.fixedSize()
.hidden()
.overlay {
GeometryReader { fullText in
let textWidth = fullText.size.width
let arcAngle = radius == 0 ? 0 : (textWidth / radius)
let startAngle = -(arcAngle / 2)
// Build the text using single characters
HStack(spacing: 0) {
ForEach(Array(string.enumerated()), id: \.offset) { index, character in
// Each character in the HStack is hidden
Text(String(character))
.hidden()
.overlay {
// Overlay with the same character, this time
// visible and with rotation and offset
GeometryReader { charSpace in
let frame = charSpace.frame(in: .named("FullText"))
let fraction = frame.midX / textWidth
let angle = startAngle + (fraction * arcAngle)
let xOffset = (textWidth / 2) - frame.midX
Text(String(character))
.offset(y: -radius)
.rotationEffect(.radians(angle))
.offset(x: xOffset)
}
}
}
}
.fixedSize()
.frame(width: textWidth)
}
}
.coordinateSpace(name: "FullText")
.offset(y: radius)
}
}
struct ContentView: View {
var body: some View {
VStack {
CurvedText(
string: "The quick brown fox",
radius: 120
)
CurvedText(
string: "jumps over the lazy dog",
radius: 120
)
.font(.footnote)
ZStack {
CurvedText(
string: "AvenirNext-DemiBold",
radius: 100
)
.font(.custom("AvenirNext-DemiBold", size: 14))
.kerning(3)
.padding(.leading, 150)
.padding(.bottom, 75)
CurvedText(
string: "AvenirNext-DemiBold",
radius: 100
)
.font(.custom("AvenirNext-DemiBold", size: 14))
.kerning(3)
.rotationEffect(.degrees(-90))
.padding(.trailing, 150)
}
.padding(.top, 100)
.padding(.trailing, 75)
}
}
}