Skip to content

Instantly share code, notes, and snippets.

@Shukuyen
Created May 8, 2022 05:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shukuyen/dfc4fd6a8d346be4ef5736af5cf8324a to your computer and use it in GitHub Desktop.
Save Shukuyen/dfc4fd6a8d346be4ef5736af5cf8324a to your computer and use it in GitHub Desktop.
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.
//
// 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)
}
}
@Shukuyen
Copy link
Author

Shukuyen commented May 8, 2022

Example usage:

CircleText(
  text: "Your text here", 
  radius: 50, 
  font: UIFont.systemFont(ofSize: 10)
)
.foregroundColor(.white.opacity(0.8))
.background {
  Circle()
  .fill(.black)
  .frame(width: 100, height: 100)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment