Skip to content
AppNorth
Log in

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.

View Markdown

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.

CommunityQueries.swift
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.

NextIdeaPage.swift
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.

SubmitIdea.swift
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

  1. Load a board and an idea from its response, then retrieve that idea's comments.

  2. Verify pagination using actual nextCursor values, including the final nil cursor.

  3. Retry the same creation with the same key and unchanged payload; confirm only one item exists.

  4. Test disabled and identified policies, moderated submissions, and a failed request without losing the draft.