Features.Vote - Build profitable features from user feedback | Product Hunt
SwiftUI tutorial · iOS 16+

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.

"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.

1

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 }
2

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 ) } }
3

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) } }
4

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

Okay, okay! Sign me up!

Start building the right features today ⚡️