iOS SDK
Build a custom UI
Use AppNorthClient when your app needs its own list, search, or composer. It handles HTTP encoding, project identifiers, identity headers, and response decoding. Your UI owns loading, error, retry, and pagination states.
Load boards and ideas
Read boards to obtain their slugs, statuses, and participation policies. Search uses query; status filtering uses the server's status slug.
import AppNorth
func searchIdeas(client: AppNorthClient, board: String, text: String) async throws -> AppNorthPage<AppNorthIdea> {
try await client.ideas(board: board, query: text, sort: .recent, limit: 20)
}
func comments(client: AppNorthClient, idea: AppNorthIdea) async throws -> AppNorthPage<AppNorthComment> {
try await client.comments(for: idea.id)
}idea(_:), comments, votes, and comment creation accept an idea ID or slug. Use the returned AppNorthIdea rather than constructing response models: most response types have no public memberwise initializer.
Load the next page
Pass nextCursor back unchanged. Stop when it is nil, and reset the cursor when filters or sorting change. The client clamps page sizes to 1–50, with a default of 20.
import AppNorth
func nextPage(
client: AppNorthClient,
board: String,
previous: AppNorthPage<AppNorthIdea>
) async throws -> AppNorthPage<AppNorthIdea>? {
guard let cursor = previous.nextCursor else { return nil }
return try await client.ideas(board: board, sort: .recent, cursor: cursor)
}Use the same board, sort, search, and status on every page of one query. Discard responses from an old query or account after the user changes context.
Submit and retry
Create one UUID per user intention and retain it until that operation succeeds or is abandoned. Pass that same key on retry; a new key can create another item.
import AppNorth
enum CommunitySubmissionError: Error { case notAllowed }
func submitIdea(
client: AppNorthClient,
board: AppNorthBoard,
title: String,
description: String,
retryKey: String
) async throws -> AppNorthCreation<AppNorthIdea> {
guard client.canParticipate(board.policies.submission) else {
throw CommunitySubmissionError.notAllowed
}
return try await client.createIdea(
board: board.slug,
title: title,
description: description,
idempotencyKey: retryKey
)
}Handle CommunitySubmissionError.notAllowed with your app’s sign-in or disabled state. canParticipate checks local policy and expiry; only the server verifies the signature.
setVote(on:voted:) expresses the desired state. Render its returned voted and voteCount, and use applied to distinguish a change from a repeated request. createComment(on:body:authorName:idempotencyKey:) returns a creation result with the same moderation flag as ideas.
Handle failures
Catch AppNorthError.server(status:code:fields:) to distinguish identity failures, validation errors, and rate limits. Keep the entered text on failure. Do not automatically retry every 4xx response; renew an expired identity through your authenticated backend first. See errors and offline writes.
Verify
Load a board and an idea from its response, then retrieve that idea's comments.
Verify pagination using actual
nextCursorvalues, including the finalnilcursor.Retry the same creation with the same key and unchanged payload; confirm only one item exists.
Test disabled and identified policies, moderated submissions, and a failed request without losing the draft.