SwiftUI view that writes text inside a circle. This is very specific to a project I am working on and not really customizable. It can be extended if you want to.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CircleText.swift | |
// | |
// Created by Cornelius Schiffer on 07.05.22. | |
import SwiftUI | |
extension String { | |
func size(usingFont font: UIFont) -> CGSize { | |
let attributes = [NSAttributedString.Key.font: font] | |
let size = self.size(withAttributes: attributes) | |
return size | |
} | |
} | |
extension Font { | |
init(_ uiFont: UIFont) { | |
self = Font(uiFont as CTFont) | |
} | |
} | |
struct CircleText: View { | |
private struct Letter { | |
let character: String | |
var offset: Double | |
} | |
private let radius: Double | |
private let font: Font | |
private let textSize: CGSize | |
private let text: [Letter] | |
init(text: String, radius: Double, font: UIFont) { | |
self.radius = radius | |
self.textSize = text.size(usingFont: font) | |
self.font = Font(font) | |
let circumference = 2 * radius * CGFloat.pi | |
let arcLength = circumference / 360 | |
let totalCharacters = text.count | |
var letters: [Letter] = [] | |
var position: Double = 0 | |
// Using the average width for each character plus some bonus | |
// This is not ideal, because not every character is the same width. Good enough for now. | |
let averageWidthPerCharacter = (self.textSize.width / Double(totalCharacters)).rounded(.up) * 1.3 | |
let charWidthDegrees = averageWidthPerCharacter / arcLength | |
let maxNumberOfCharacters = Int(circumference / averageWidthPerCharacter / 2) | |
// Rotate left by half the string length to top-center align the text | |
let centeringOffset = -(averageWidthPerCharacter * Double(min(maxNumberOfCharacters - 2, totalCharacters) - 1) / arcLength) / 2 | |
// Store characters and position in degrees on the circle | |
for (index, character) in text.enumerated() { | |
// Cut off string if it is longer than the maximum number of characters | |
if totalCharacters > maxNumberOfCharacters && index > maxNumberOfCharacters - 4 { | |
letters.append(Letter(character: "…", offset: position + centeringOffset)) | |
break | |
} | |
// Append character and its position | |
let charText = String(character) | |
letters.append(Letter(character: charText, offset: position + centeringOffset)) | |
position += charWidthDegrees | |
} | |
self.text = letters | |
} | |
var body: some View { | |
ZStack { | |
ForEach(Array(text.enumerated()), id: \.offset) { _, letter in | |
Text(letter.character) | |
.offset(y: -radius + textSize.height / 2) | |
.rotationEffect(Angle(degrees: letter.offset)) | |
} | |
} | |
.font(font) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage: