Add a Feature Request Board to Your iOS App
Let users submit and upvote ideas right inside your app. We'll build a native SwiftUI voting board from scratch — then look at the parts that are deceptively hard to do yourself.
See feature voting & feedback boards in action
Watch: add feedback boards & a changelog to your iOS app
"Shout out to FeaturesVote! Integration was done in under a minute"
Alexandre Negrel,
Founder at Prisme Analytics
A feature request board turns scattered "you should add…" messages into a ranked list of what users actually want. The SwiftUI for it is straightforward — a List, an upvote button, and a store. We'll build the whole thing in four steps, then be honest about what it takes to make it work across thousands of users.
Model a feature request
Each request needs a title, a vote count, and whether the current user has voted. A simple Identifiable struct is enough to drive a SwiftUI List.
import SwiftUI
struct FeatureRequest: Identifiable {
let id = UUID()
var title: String
var votes: Int
var hasVoted: Bool
}Hold state in an ObservableObject
A store publishes the list and handles voting and adding. Toggling a vote updates the count and re-sorts so the most-wanted requests rise to the top.
@MainActor
final class FeatureStore: ObservableObject {
@Published var requests: [FeatureRequest] = []
func toggleVote(_ request: FeatureRequest) {
guard let i = requests.firstIndex(where: { $0.id == request.id }) else { return }
requests[i].hasVoted.toggle()
requests[i].votes += requests[i].hasVoted ? 1 : -1
requests.sort { $0.votes > $1.votes } // most-wanted first
}
func add(_ title: String) {
requests.insert(
FeatureRequest(title: title, votes: 1, hasVoted: true),
at: 0
)
}
}Build the upvote button
A compact vertical vote control — a triangle and a count — that fills in when the user has voted. This is the heart of any voting board.
struct VoteButton: View {
let votes: Int
let hasVoted: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
VStack(spacing: 2) {
Image(systemName: hasVoted ? "arrowtriangle.up.fill" : "arrowtriangle.up")
Text("\(votes)").font(.caption.bold())
}
.frame(width: 44)
.foregroundStyle(hasVoted ? AnyShapeStyle(.tint) : AnyShapeStyle(.secondary))
}
.buttonStyle(.plain)
}
}Assemble the board
Put it together in a List inside a NavigationStack, with a + button to submit new ideas via a sheet. That's a working board — for a single device.
struct FeatureBoard: View {
@StateObject private var store = FeatureStore()
@State private var showNew = false
var body: some View {
NavigationStack {
List(store.requests) { request in
HStack(spacing: 16) {
VoteButton(votes: request.votes, hasVoted: request.hasVoted) {
store.toggleVote(request)
}
Text(request.title)
}
}
.navigationTitle("Feature Requests")
.toolbar {
Button { showNew = true } label: { Image(systemName: "plus") }
}
.sheet(isPresented: $showNew) {
NewRequestView { store.add($0) }
}
}
}
}What the tutorial leaves out
The board above works on one device. A board that's actually useful needs three things the UI doesn't give you.
Cross-user sync
Local state only counts one person's votes. A real board needs a backend so everyone sees the same list and tallies.
Dedup & moderation
Users submit the same idea ten different ways. You need merging, and a way to hide spam before it shows.
Status & the feedback loop
Planned, in progress, shipped — plus notifying the people who voted when their request lands. That's what keeps users coming back.
The same board, hosted — in one line
If you'd rather not build and run the backend, Features.Vote gives you the same native SwiftUI board with cross-user sync, dedup, statuses and voter notifications already handled — plus a roadmap and changelog from the same data.
import SwiftUI
import FeaturesVote
struct FeatureBoard: View {
var body: some View {
// A hosted, cross-user board: votes sync, duplicates
// merge, statuses update, voters get notified on ship.
FeaturesVote.VotingBoardView()
}
}
// One-time setup:
// FeaturesVote.configure(with: "your-project-slug")Frequently Asked Questions
Still not convinced?
Here's a full price comparison with all top competitors
Is it lacking a feature you need?
Chances are, we're already working on it. Check our roadmap
Okay, okay! Sign me up!
Start building the right features today ⚡️