A comprehensive iOS SDK for integrating customer engagement features including feedback collection, support tickets, help centers, roadmaps, release notes, status pages, and more.
- iOS 17.0+ / macOS 14.0+
- Swift 5.9+
- Xcode 15.0+
Add the following to your Package.swift:
dependencies: [
.package(url: "https://github.com/AppGram/apple-sdk.git", branch: "main")
]Or in Xcode:
- Go to File > Add Package Dependencies
- Enter:
https://github.com/AppGram/apple-sdk.git - Select Branch: main and add to your target
Initialize the SDK early in your app's lifecycle, typically in your App struct or AppDelegate:
import AppGramSDK
@main
struct MyApp: App {
init() {
AppGramSDK.shared.configure(
projectId: "your-project-id",
apiKey: "your-api-key" // Optional, for authenticated endpoints
)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}Identify users to associate feedback and support tickets:
AppGramSDK.shared.setUser(
userId: "user-123",
email: "user@example.com",
name: "John Doe"
)
// Clear user on logout
AppGramSDK.shared.clearUser()import SwiftUI
import AppGramSDK
struct ContentView: View {
var body: some View {
NavigationStack {
List {
NavigationLink("Feedback") {
try? AppGramSDK.shared.feedbackView()
}
NavigationLink("Support") {
try? AppGramSDK.shared.supportView()
}
NavigationLink("Help Center") {
try? AppGramSDK.shared.helpCenterView()
}
}
}
}
}While the SDK provides ready-to-use SwiftUI views, we encourage building your own UI using our headless services. This gives you full control over the user experience while we handle the API communication.
| Service | Access Method | Use Case |
|---|---|---|
FeedbackServiceProtocol |
getFeedbackService() |
Wishes, voting, comments |
SupportServiceProtocol |
getSupportService() |
Tickets, messages, forms |
HelpServiceProtocol |
getHelpService() |
Articles, collections, flows |
RoadmapServiceProtocol |
getRoadmapService() |
Roadmap items, columns |
ReleasesServiceProtocol |
getReleasesService() |
Release notes, features |
StatusServiceProtocol |
getStatusService() |
Service status, incidents |
SurveyServiceProtocol |
getSurveyService() |
Surveys, responses |
ContactFormServiceProtocol |
getContactFormService() |
Contact forms |
BlogServiceProtocol |
getBlogService() |
Blog posts, categories |
import SwiftUI
import AppGramSDK
struct CustomFeedbackView: View {
@State private var wishes: [Wish] = []
@State private var isLoading = true
var body: some View {
List(wishes) { wish in
VStack(alignment: .leading) {
Text(wish.title)
.font(.headline)
Text(wish.description)
.font(.subheadline)
.foregroundStyle(.secondary)
HStack {
Label("\(wish.voteCount)", systemImage: "arrow.up")
Spacer()
Text(wish.status.rawValue)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(.blue.opacity(0.1))
.cornerRadius(4)
}
}
}
.task { await loadWishes() }
}
func loadWishes() async {
do {
let service = try AppGramSDK.shared.getFeedbackService()
wishes = try await service.getWishes()
} catch {
print("Error: \(error)")
}
isLoading = false
}
}import SwiftUI
import AppGramSDK
struct CustomSupportView: View {
@State private var tickets: [SupportTicket] = []
var body: some View {
List(tickets) { ticket in
VStack(alignment: .leading, spacing: 8) {
HStack {
Text(ticket.subject)
.font(.headline)
Spacer()
StatusBadge(status: ticket.status)
}
Text(ticket.description)
.font(.subheadline)
.lineLimit(2)
Text(ticket.createdAt, style: .relative)
.font(.caption)
.foregroundStyle(.secondary)
}
}
.task { await loadTickets() }
}
func loadTickets() async {
// Tickets are stored locally after creation
tickets = SupportTicketStorage.shared.loadTickets()
}
func createTicket() async {
do {
let service = try AppGramSDK.shared.getSupportService()
let ticket = try await service.createTicket(
subject: "Help needed",
description: "I have a question about...",
priority: .normal,
email: "user@example.com",
attachmentUrls: nil
)
// Ticket is automatically saved to local storage
tickets.insert(ticket, at: 0)
} catch {
print("Error: \(error)")
}
}
}import SwiftUI
import AppGramSDK
struct CustomHelpView: View {
@State private var collections: [HelpCollection] = []
@State private var articles: [HelpArticle] = []
var body: some View {
List {
ForEach(collections) { collection in
Section(collection.name) {
ForEach(articlesFor(collection)) { article in
NavigationLink(article.title) {
ArticleDetailView(article: article)
}
}
}
}
}
.task { await loadHelp() }
}
func loadHelp() async {
do {
let service = try AppGramSDK.shared.getHelpService()
collections = try await service.getCollections()
articles = try await service.getArticles()
} catch {
print("Error: \(error)")
}
}
func articlesFor(_ collection: HelpCollection) -> [HelpArticle] {
articles.filter { $0.collectionId == collection.id }
}
}let service = try AppGramSDK.shared.getFeedbackService()
// Fetch wishes with optional filters
let wishes = try await service.getWishes(filters: WishFilters(
status: .open,
categoryId: nil,
searchQuery: "dark mode",
sortBy: .votes,
page: 1,
limit: 20
))
// Create a wish
let wish = try await service.createWish(
title: "Feature title",
description: "Feature description",
categoryId: "category-id"
)
// Vote
try await service.addVote(wishId: "wish-id")
try await service.removeVote(wishId: "wish-id")
// Comments
let comments = try await service.getComments(wishId: "wish-id")
let comment = try await service.addComment(
wishId: "wish-id",
content: "Great idea!",
parentId: nil
)
// Categories
let categories = try await service.getCategories()let service = try AppGramSDK.shared.getSupportService()
// Create ticket
let ticket = try await service.createTicket(
subject: "Subject",
description: "Description",
priority: .normal,
email: "user@email.com",
attachmentUrls: nil
)
// Magic link authentication (for viewing tickets via email)
try await service.sendMagicLink(userEmail: "user@email.com")
let result = try await service.verifyMagicLinkToken("token-from-email")
// Messages (requires access token from ticket creation)
let messages = try await service.getMessagesWithToken(
ticketId: ticket.id,
token: ticket.accessToken ?? ""
)
// Add message
let message = try await service.addMessageWithToken(
ticketId: ticket.id,
token: ticket.accessToken ?? "",
content: "Follow-up message"
)
// File upload
let url = try await service.uploadFile(
imageData,
fileName: "screenshot.png",
mimeType: "image/png"
)// Roadmap
let roadmapService = try AppGramSDK.shared.getRoadmapService()
let items = try await roadmapService.getRoadmapItems()
// Releases
let releasesService = try AppGramSDK.shared.getReleasesService()
let releases = try await releasesService.getReleases()
// Status
let statusService = try AppGramSDK.shared.getStatusService()
let overview = try await statusService.getStatusOverview()
// Blog
let blogService = try AppGramSDK.shared.getBlogService()
let posts = try await blogService.getPosts()If you prefer using our ready-made UI, the SDK provides SwiftUI views for all features:
Collect feature requests and feedback from users with voting capabilities:
// Display feedback view
let feedbackView = try AppGramSDK.shared.feedbackView()
// Or use the service directly
let feedbackService = try AppGramSDK.shared.getFeedbackService()
let wishes = try await feedbackService.getWishes()
// Create a new feature request
let wish = try await feedbackService.createWish(
title: "Dark mode support",
description: "Please add dark mode to the app",
categoryId: nil
)
// Vote on a wish
try await feedbackService.addVote(wishId: wish.id)Full support ticket system with local storage:
// Display support view
let supportView = try AppGramSDK.shared.supportView()
// Display a specific support form
let formView = try AppGramSDK.shared.supportFormView(formId: "form-123")
// Use the service directly
let supportService = try AppGramSDK.shared.getSupportService()
// Create a ticket
let ticket = try await supportService.createTicket(
subject: "Login issue",
description: "I can't log into my account",
priority: .normal,
email: "user@example.com",
attachmentUrls: nil
)Interactive help center with articles, flows, and decision trees:
// Basic help center
let helpView = try AppGramSDK.shared.helpCenterView()
// With custom configuration
let config = HelpCenterConfiguration(
yesButtonColor: .green,
noButtonColor: .red,
articleBehavior: .pushToStack
)
let helpView = try AppGramSDK.shared.helpCenterView(configuration: config)
// Access articles programmatically
let helpService = try AppGramSDK.shared.getHelpService()
let collections = try await helpService.getCollections()
let article = try await helpService.getArticle(slug: "getting-started")Display your product roadmap:
let roadmapView = try AppGramSDK.shared.roadmapView()
// Access roadmap data
let roadmapService = try AppGramSDK.shared.getRoadmapService()
let items = try await roadmapService.getRoadmapItems()Showcase new features and updates:
let releasesView = try AppGramSDK.shared.releasesView(
orgSlug: "your-org",
projectSlug: "your-project"
)
// With configuration
let config = ReleasesConfiguration(
showVersionBadge: true,
dateFormat: "MMM d, yyyy"
)
let releasesView = try AppGramSDK.shared.releasesView(
orgSlug: "your-org",
projectSlug: "your-project",
configuration: config
)Display system status and service health:
let statusView = try AppGramSDK.shared.statusPageView(slug: "status")
// With subscription callback
let config = StatusConfiguration(
onSubscribe: { email in
print("User subscribed: \(email)")
}
)
let statusView = try AppGramSDK.shared.statusPageView(
slug: "status",
configuration: config
)Custom contact forms:
let contactView = try AppGramSDK.shared.contactFormView(formId: "contact-123")Collect user feedback with surveys:
// Standard survey
let surveyView = try AppGramSDK.shared.surveyView(slug: "nps-survey")
// Typeform-style survey
let surveyView = try AppGramSDK.shared.surveyView(
slug: "onboarding",
style: .typeform
)Display blog posts and content:
let blogView = try AppGramSDK.shared.blogView()
let blogService = try AppGramSDK.shared.getBlogService()
let posts = try await blogService.getPosts()AppGramSDK.shared.configure(
projectId: "your-project-id",
theme: .ocean // .default, .forest, .sunset, .minimal, .classic, .slate, .neutral
)let customTheme = AppGramTheme(
colors: ColorPalette(
primary: .blue,
secondary: .purple,
accent: .orange,
background: .white,
text: .black,
cardBackground: .gray.opacity(0.1),
border: .gray.opacity(0.2),
success: .green,
warning: .yellow,
error: .red,
neutral50: Color(white: 0.98),
neutral100: Color(white: 0.96),
neutral200: Color(white: 0.90),
neutral300: Color(white: 0.83),
neutral400: Color(white: 0.64),
neutral500: Color(white: 0.45),
neutral600: Color(white: 0.32),
neutral700: Color(white: 0.25),
neutral800: Color(white: 0.15),
neutral900: Color(white: 0.09)
),
darkColors: nil // Optional dark mode colors
)
AppGramSDK.shared.configure(
projectId: "your-project-id",
theme: customTheme
)Automatically show feedback prompts or surveys based on conditions:
let popupConfig = PopupConfiguration(
trigger: .afterLaunches(5),
content: .feedback,
style: .sheet
)
AppGramSDK.shared.configure(
projectId: "your-project-id",
popupConfiguration: popupConfig
)
// Start monitoring
let popupManager = try AppGramSDK.shared.getPopupManager()
popupManager.start()
// Show manually
popupManager.showPopupManually()
// Stop monitoring
popupManager.stop().afterLaunches(Int)- After N app launches.afterDaysOfUsage(Int)- After N days of usage.repeatInterval(TimeInterval)- Repeat every N seconds.minimumInterval(TimeInterval)- Minimum time between popups
Display in-app announcements:
let announcementConfig = AnnouncementCardConfiguration(
backgroundColor: .blue,
primaryButtonColor: .white,
style: .card
)
AppGramSDK.shared.configure(
projectId: "your-project-id",
announcementConfiguration: announcementConfig
)
// Show announcements
AppGramSDK.shared.showAnnouncements()
// Or use the manager
let manager = try AppGramSDK.shared.getAnnouncementManager()
manager.presentAnnouncements()Embeddable widgets for your UI:
let widgetConfig = AGWidgetConfiguration(
type: .feedback,
style: .card,
size: .medium,
ctaButton: AGWidgetConfiguration.CTAStyle(
title: "Share Feedback",
backgroundColor: .blue,
foregroundColor: .white,
fontWeight: .semibold,
cornerRadius: 8
)
)
let widgetView = try AppGramSDK.shared.widgetView(configuration: widgetConfig).feedback- Feedback submission.roadmap- Roadmap preview.status- Status indicator.custom- Custom content
.card- Full card layout.minimal- Minimal design.compact- Compact view.banner- Banner style
All views have UIKit equivalents:
import UIKit
import AppGramSDK
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let feedbackVC = try? AppGramSDK.shared.feedbackViewController()
if let vc = feedbackVC {
navigationController?.pushViewController(vc, animated: true)
}
}
}Available UIKit methods:
feedbackViewController()supportViewController()helpCenterViewController()roadmapViewController()statusPageViewController()releasesViewController()contactFormViewController()surveyViewController()blogViewController()
do {
let service = try AppGramSDK.shared.getFeedbackService()
let wishes = try await service.getWishes()
} catch AppGramError.notConfigured {
print("SDK not configured")
} catch AppGramError.networkError(let message) {
print("Network error: \(message)")
} catch AppGramError.unauthorized {
print("Unauthorized - check API key")
} catch {
print("Error: \(error)")
}| Error | Description |
|---|---|
notConfigured |
SDK not initialized |
networkError(String) |
Network request failed |
invalidResponse |
Malformed server response |
decodingError(String) |
JSON parsing failed |
validationError(String) |
Input validation failed |
uploadFailed |
File upload error |
unauthorized |
Authentication required |
forbidden |
Access denied |
notFound |
Resource not found |
serverError(Int) |
Server error with HTTP code |
Enable debug logging during development:
// Logs are automatically enabled in DEBUG builds
// View logs in Xcode console with "AppGram" filterThe SDK is thread-safe and can be accessed from any thread. UI-related operations are automatically dispatched to the main thread.
import SwiftUI
import AppGramSDK
@main
struct MyApp: App {
init() {
// Configure SDK
AppGramSDK.shared.configure(
projectId: "your-project-id",
theme: .default,
apiKey: "your-api-key"
)
// Set user if logged in
if let user = AuthManager.shared.currentUser {
AppGramSDK.shared.setUser(
userId: user.id,
email: user.email,
name: user.name
)
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State private var showFeedback = false
@State private var showSupport = false
var body: some View {
NavigationStack {
List {
Section("Engage") {
Button("Share Feedback") { showFeedback = true }
Button("Get Support") { showSupport = true }
}
Section("Info") {
NavigationLink("Roadmap") {
try? AppGramSDK.shared.roadmapView()
}
NavigationLink("Help Center") {
try? AppGramSDK.shared.helpCenterView()
}
NavigationLink("Status") {
try? AppGramSDK.shared.statusPageView(slug: "status")
}
}
}
.navigationTitle("Settings")
.sheet(isPresented: $showFeedback) {
NavigationStack {
try? AppGramSDK.shared.feedbackView()
}
}
.sheet(isPresented: $showSupport) {
NavigationStack {
try? AppGramSDK.shared.supportView()
}
}
}
}
}Copyright 2026 AppGram. All rights reserved.