我正在绘制一个以弧形为中心的弯曲文本视图,我希望每个字母之间的间距相等。
我尝试将文本字体更新为
.font(.system(size: 14, design: .monospaced))
,这使字母之间的间距相等,但它不是正确的字体。
目前,间距如下所示:
理想情况下,它应该是这样的(但使用正确的字体):
struct ContentView: View {
@State private var letterWidths: [Int: Double] = [:]
private let size: CGFloat = 300
var body: some View {
ZStack {
ArcShape(start: 180, end: 230)
.frame(width: size, height: size)
.foregroundColor(.gray)
textView("AVENIR", start: 180, end: 230)
ArcShape(start: 233, end: 300)
.frame(width: size, height: size)
.foregroundColor(.gray)
textView("DEMIBOLD", start: 233, end: 300)
}
.frame(width: size)
}
func textView(_ title: String, start: CGFloat, end: CGFloat) -> some View {
ZStack {
let firstLetterAngle = calculateLetterAngle(at: 0)
let lastLetterAngle = calculateLetterAngle(at: title.count - 1)
let titleAngleDifference: Angle = lastLetterAngle - firstLetterAngle
let segmentAngleDifference: Angle = .degrees(end) - .degrees(start)
let titleOffsetAngle = (segmentAngleDifference - titleAngleDifference) / 2
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(calculateLetterAngle(at: index))
}
// to center title in arc shape
.frame(width: size, height: size * 0.75)
.rotationEffect(titleOffsetAngle + .degrees(start) + .degrees(90))
}
}
private func calculateLetterAngle(at letterPosition: Int) -> Angle {
let times2Pi: (CGFloat) -> CGFloat = { $0 * 2 * .pi }
let radius: CGFloat = 125
let circumference = times2Pi(radius)
let letterAngle = times2Pi(
letterWidths
.filter { $0.key < letterPosition }
.map(\.value)
.reduce(0, +) / circumference
)
return .degrees(letterAngle * (180 / .pi)) // to convert radians to degrees
}
}
struct LetterWidthPreferenceKey: PreferenceKey {
static var defaultValue: Double = 0
static func reduce(value: inout Double, nextValue: () -> Double) {
value = nextValue()
}
}
extension UIBezierPath {
public convenience init(roundedArcCenter center: CGPoint, innerRadius: CGFloat, outerRadius: CGFloat, startAngle: Angle, endAngle: Angle, cornerRadiusPercentage: CGFloat) {
let maxCornerRadiusBasedOnInnerArcLength = abs((endAngle - startAngle).radians) * innerRadius / 2
let maxCornerRadiusBasedOnOuterArcLength = abs((endAngle - startAngle).radians) * outerRadius / 2
let maxCornerRadiusBasedOnEndCapLength = (outerRadius - innerRadius) / 2
let outerCornerRadius = min(2 * .pi * outerRadius * cornerRadiusPercentage, maxCornerRadiusBasedOnOuterArcLength, maxCornerRadiusBasedOnEndCapLength)
let outerCornerRadiusPercentage = outerCornerRadius / (2 * .pi * outerRadius)
let innerCornerRadius = min(2 * .pi * innerRadius * outerCornerRadiusPercentage, maxCornerRadiusBasedOnInnerArcLength, maxCornerRadiusBasedOnEndCapLength)
let innerInsetAngle = Angle(radians: innerCornerRadius / innerRadius)
let outerInsetAngle = Angle(radians: outerCornerRadius / outerRadius)
self.init()
var arcStartAngle = (startAngle + outerInsetAngle).radians
var arcEndAngle = (endAngle - outerInsetAngle).radians
addArc(
withCenter: .zero,
radius: outerRadius,
startAngle: min(arcStartAngle, arcEndAngle),
endAngle: max(arcStartAngle, arcEndAngle),
clockwise: true
)
addCorner(
to: .pointOnCircle(radius: outerRadius - outerCornerRadius, angle: endAngle),
controlPoint: .pointOnCircle(radius: outerRadius, angle: endAngle)
)
addLine(to: .pointOnCircle(radius: innerRadius + innerCornerRadius, angle: endAngle))
addCorner(
to: .pointOnCircle(radius: innerRadius, angle: endAngle - innerInsetAngle),
controlPoint: .pointOnCircle(radius: innerRadius, angle: endAngle)
)
arcStartAngle = (endAngle - innerInsetAngle).radians
arcEndAngle = (startAngle + innerInsetAngle).radians
addArc(
withCenter: .zero,
radius: innerRadius,
startAngle: max(arcStartAngle, arcEndAngle),
endAngle: min(arcStartAngle, arcEndAngle),
clockwise: false
)
addCorner(
to: .pointOnCircle(radius: innerRadius + innerCornerRadius, angle: startAngle),
controlPoint: .pointOnCircle(radius: innerRadius, angle: startAngle)
)
addLine(to: .pointOnCircle(radius: outerRadius - outerCornerRadius, angle: startAngle))
addCorner(
to: .pointOnCircle(radius: outerRadius, angle: startAngle + outerInsetAngle),
controlPoint: .pointOnCircle(radius: outerRadius, angle: startAngle)
)
apply(.init(translationX: center.x, y: center.y))
}
private func addCorner(to: CGPoint, controlPoint: CGPoint) {
let circleApproximationConstant = 0.551915
addCurve(
to: to,
controlPoint1: currentPoint + (controlPoint - currentPoint) * circleApproximationConstant,
controlPoint2: to + (controlPoint - to) * circleApproximationConstant
)
}
}
private extension CGPoint {
static func pointOnCircle(radius: CGFloat, angle: Angle) -> CGPoint {
CGPoint(x: radius * Darwin.cos(angle.radians), y: radius * Darwin.sin(angle.radians))
}
static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
}
static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
CGPoint(x: lhs.x * rhs, y: lhs.y * rhs)
}
}
public extension CGRect {
var center: CGPoint {
CGPoint(x: size.width / 2.0, y: size.height / 2.0)
}
}
您问这个问题是因为不清楚如何使用我为您的其他问题提供的解决方案?
像这样:
var textRadius: CGFloat {
(size / 2) - 42
}
var body: some View {
ZStack {
ArcShape(start: 180, end: 230)
.frame(width: size, height: size)
.foregroundColor(.gray)
CurvedText(
string: "AVENIR",
radius: textRadius
)
.offset(y: -textRadius)
.rotationEffect(.degrees(((180 + 230) / 2) + 90))
ArcShape(start: 233, end: 300)
.frame(width: size, height: size)
.foregroundColor(.gray)
CurvedText(
string: "DEMIBOLD",
radius: textRadius
)
.offset(y: -textRadius)
.rotationEffect(.degrees(((233 + 300) / 2) + 90))
}
.foregroundColor(.white)
.frame(width: size)
}