Word Wheel (beta)
Embed a fully native Word Wheel puzzle into your mobile app with a single line of code.
WordWheelView(model: .example)
.grid(radius: 120)What's included
Quick integration
Embed a fully native Word Wheel puzzle into your mobile app with a single line of code.
Customise every piece
Replace the input display, letter tiles, and action buttons independently with your own views.
Word callbacks
React to every submitted word — validate it against your answers and track found words in your own state.
Control grid radius
Fix the distance from the centre letter to each surrounding tile with a single modifier, or let it scale automatically.
Quick integration
Pass a puzzle model into the view to render a fully playable Word Wheel — letter tiles and action buttons — in a single line.
WordWheelView(model: .example)Control grid radius
Set the distance from the centre letter to each surrounding tile. When not set the radius scales automatically with the available space. Supply an explicit value to fix the spacing regardless of how much room the wheel is given.
WordWheelView(model: .example)
.grid(radius: 120)Customise input display
Replace the word-in-progress banner above the wheel with your own view.
WordWheelView(model: .example)
.inputView(MyInputView.self)Your custom view receives the word currently being built and the total number of letters on the wheel.
struct MyInputView: View, WordWheelInputViewProtocol {
var word: String
var isValid: Bool
var letterCount: Int
init(word: String, isValid: Bool, letterCount: Int) {
self.word = word
self.isValid = isValid
self.letterCount = letterCount
}
var body: some View {
HStack {
Text(word.isEmpty ? "Start typing…" : word)
.font(.title2.bold())
.foregroundStyle(isValid ? .green : .primary)
.frame(maxWidth: .infinity, alignment: .leading)
Text("\(word.count) / \(letterCount)")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}Customise letter tiles
Replace the circular letter tiles around the wheel and at the centre with your own view.
WordWheelView(model: .example)
.input(cell: WordWheelInput.self)Your custom tile conforms to a protocol that receives the letter, whether it is the centre tile, whether it has already been used in the current attempt, and a tap handler to call when tapped.
struct WordWheelInput: View, WordWheelInputProtocol {
var letter: String
var isMain: Bool
var isUsed: Bool
var onTap: () -> Void
init(letter: String, isMain: Bool, isUsed: Bool, onTap: @escaping () -> Void) {
self.letter = letter
self.isMain = isMain
self.isUsed = isUsed
self.onTap = onTap
}
var body: some View {
ZStack {
Circle()
.fill(isUsed ? .gray.opacity(0.4) : (isMain ? .indigo : .blue))
Text(letter)
.font(isMain ? .title2.bold() : .headline)
.foregroundStyle(.white)
}
.onTapGesture {
guard !isUsed else { return }
onTap()
}
}
}Customise action buttons
Replace the Submit, Delete, and Clear buttons below the wheel with your own view.
WordWheelView(model: .example)
.actionButton(cell: WordWheelActionButton.self)Your custom button conforms to a protocol that receives the button label and a tap handler. The same view is reused for all three actions — use the label to differentiate their appearance.
struct WordWheelActionButton: View, WordWheelActionButtonProtocol {
var label: String
var onTap: () -> Void
init(label: String, onTap: @escaping () -> Void) {
self.label = label
self.onTap = onTap
}
var body: some View {
Button(action: onTap) {
Text(label)
.font(.subheadline.bold())
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(label == "Submit" ? .green : (label == "Delete" ? .orange : .red.opacity(0.8)))
.foregroundStyle(.white)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
}Word callbacks
React to every word the player submits — validate it against your answer list and track found words in your own state.
WordWheelView(model: .example)
.input(cell: WordWheelInput.self)
.actionButton(cell: WordWheelActionButton.self)
.onWordSubmitted { word in
if acceptableAnswers.contains(word) && !foundWords.contains(word) {
foundWords.append(word)
}
}Explore the examples
Explore a fully working iOS and Android sample project you can run immediately.