A simple, lightweight SwiftUI package for collecting user feedback (bug reports and feature requests) directly in your iOS app using GitHub Issues as the backend.
β
Zero Dependencies - Pure SwiftUI and GitHub API
β
Anonymous Feedback - No user accounts required
β
Built-in Voting System - Users can upvote feature requests
β
Duplicate Prevention - One vote per device per issue
β
Device Info Collection - Automatic device/app version reporting
β
Clean UI Components - Ready-to-use SwiftUI views
β
Private Repository Support - Keep feedback internal
β
Real-time Updates - Live vote counts and issue lists
- In Xcode, go to File β Add Package Dependencies
- Enter the repository URL:
https://github.com/yourusername/github-feedback-sdk
- Click Add Package
- Create a repository (can be private) for storing feedback
- Generate a Personal Access Token:
- Go to GitHub β Settings β Developer settings β Personal access tokens
- Create a Fine-grained token with these permissions for your repository:
- Issues: Write
- Metadata: Read
- Contents: Read
import SwiftUI
struct ContentView: View {
// Configure your GitHub credentials
private let gitHubCredentials = GitHubCredentials(
owner: "your-username", // Your GitHub username
repo: "feedback-repo", // Your repository name
token: "github_pat_xxx..." // Your GitHub token
)
private lazy var gitHubService = GitHubService(credentials: gitHubCredentials)
var body: some View {
NavigationStack {
TabView {
// Your main app
YourMainView()
.tabItem {
Label("Home", systemImage: "house")
}
// Feedback system
IssuesListView(gitHubService: gitHubService)
.tabItem {
Label("Feedback", systemImage: "exclamationmark.bubble")
}
}
}
}
}struct SettingsView: View {
@State private var showFeedback = false
private let gitHubService = GitHubService(credentials: yourCredentials)
var body: some View {
List {
Button("Send Feedback") {
showFeedback = true
}
}
.sheet(isPresented: $showFeedback) {
FeedbackFormView(gitHubService: gitHubService)
}
}
}public struct GitHubCredentials {
public init(owner: String, repo: String, token: String)
}Parameters:
owner: Your GitHub username or organizationrepo: Repository name where issues will be createdtoken: GitHub Personal Access Token
@MainActor
public class GitHubService: ObservableObject {
public var issues: [GitHubIssue] = []
public var isLoading: Bool = false
public var errorMessage: String?
public init(credentials: GitHubCredentials)
public func loadIssues(type: IssueType = .all) async
public func createIssue(title: String, description: String, type: IssueType, deviceInfo: String) async throws -> Int
public func addVote(to issueNumber: Int) async throws
}@MainActor
public class VotingService: ObservableObject {
public func hasVoted(for issueNumber: Int) -> Bool
public func addVote(to issueNumber: Int, using gitHubService: GitHubService) async throws
}Complete feedback interface with issue browsing, voting, and submission.
public struct IssuesListView: View {
public init(gitHubService: GitHubService)
}Standalone form for submitting bug reports and feature requests.
public struct FeedbackFormView: View {
public init(gitHubService: GitHubService)
}public enum IssueType: String, CaseIterable {
case all = "All"
case bugs = "Bugs"
case features = "Feature Requests"
}- Issue Creation: User feedback creates GitHub issues with automatic device info
- Voting System: Vote counts are stored in issue descriptions as
π Votes: X - Local Tracking: UserDefaults prevents duplicate votes per device
- Clean Display: Device info and vote metadata are hidden from users
- Real-time Sync: Issues and vote counts update automatically
Issues created by the SDK include:
User's feedback description
---
**Device Information:**
Device: iPhone15,2
iOS Version: 17.0
App Version: 1.0.0 (1)
Device ID: ABC-123-DEF
*Submitted via mobile app*
---
π Votes: 5// Override device info collection
let customDeviceInfo = """
Device: \(DeviceInfo.getDeviceModel())
OS: \(DeviceInfo.getIOSVersion())
App: \(DeviceInfo.getAppVersion())
Custom Field: \(yourCustomData)
"""
try await gitHubService.createIssue(
title: title,
description: description,
type: .bugs,
deviceInfo: customDeviceInfo
)// Load only feature requests
await gitHubService.loadIssues(type: .features)
// Load only bugs
await gitHubService.loadIssues(type: .bugs)
// Load all feedback
await gitHubService.loadIssues(type: .all)do {
try await gitHubService.createIssue(...)
} catch GitHubError.failedToCreate {
// Handle creation failure
} catch GitHubError.invalidResponse {
// Handle API errors
} catch VotingError.alreadyVoted {
// Handle duplicate votes
}// Option 1: Environment variables
let token = ProcessInfo.processInfo.environment["GITHUB_TOKEN"] ?? ""
// Option 2: Secrets file (add to .gitignore)
struct Secrets {
static let githubToken = "your_token_here"
}
// Option 3: Property file
if let path = Bundle.main.path(forResource: "Secrets", ofType: "plist"),
let plist = NSDictionary(contentsOfFile: path),
let token = plist["GitHubToken"] as? String {
// Use token
}# Secrets
Secrets.swift
Secrets.plist
Config.xcconfig
# GitHub token files
github-token.txt
credentials.jsonCreate these labels in your GitHub repository for better organization:
- π
bug(red) - For bug reports - β¨
feature-request(blue) - For feature requests - π±
user-submitted(yellow) - For app-submitted issues - β
completed(green) - For implemented features - π
in-progress(orange) - For work in progress
Create .github/ISSUE_TEMPLATE/bug_report.md:
---
name: Bug Report
about: Report a bug from the mobile app
labels: bug, user-submitted
---
**Device Info:**
- Device:
- iOS Version:
- App Version:
**Description:**
<!-- Bug description -->
**Steps to Reproduce:**
1.
2.
3.
**Expected vs Actual Behavior:**
<!-- What should happen vs what actually happened -->No issues loading:
- Verify your GitHub token has correct permissions
- Check that repository name and owner are correct
- Ensure repository exists and is accessible
Voting not working:
- Confirm token has "Issues: Write" permission
- Check network connectivity
- Verify issue exists and is accessible
Duplicate vote prevention not working:
- UserDefaults data may have been cleared
- Device identifier may have changed (rare)
// Enable debug logging
await gitHubService.loadIssues(type: .all)
print("Loaded \(gitHubService.issues.count) issues")
// Check voting status
let votingService = VotingService()
print("User has voted on: \(votingService.getVotedIssues())")- iOS 17.0+
- Xcode 15.0+
- Swift 5.9+