·6 min read

Exploring Cursor: Quickly Creating a Swift Package


This chapter is taken from my book, "AI Assisted Coding for iOS Development". You can buy it to learn further about Cursor:

Exploring AI Assisted Coding


Over the past few weeks, I have been working on numerous Swift packages, and thanks to some extra AI assistance, I managed to ship them faster than I normally would. I used Cursor, and this post is about my process of creating GhostingKit, an unofficial Swift SDK for the Ghost Content API.

Creating GhostingKit

GhostingKit is an unofficial library I created for interacting with the Ghost Content API. You can check out the project here:

GitHub - rryam/GhostingKit: Unofficial Swift SDK for Ghost API

Unofficial Swift SDK for Ghost API. Contribute to rryam/GhostingKit development by creating an account on GitHub.

Creating the Package

To get started, I used a few basic bash commands to create the package and make it executable:

mkdir GhostingKit          # Create directory for the package
cd GhostingKit             # Change into that directory
swift package init --type executable  # Initialize as an executable package
swift build                   # Build the package
swift run                     # Run the executable

Opening in Cursor

Once the package was set up, I opened the project in Cursor. After installing the Cursor shell command, I used the terminal to open the project with a single line:

This opened the directory directly in Cursor, ready for editing.

cursor "$PWD"

Updating Package.swift

Next, I updated the Package.swift file to define the platforms I wanted to support. Since GhostingKit depends on async/await but does not use any latest SwiftUI syntax, I still set the minimum iOS version to 16+. Here is the relevant section of Package.swift:

// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
 
import PackageDescription
 
/// Package definition for GhostingKit, a Swift library for interacting with the Ghost Content API.
///
/// This package provides a convenient way to integrate Ghost content into Swift applications,
/// supporting various Apple platforms including iOS, macOS, tvOS, watchOS, and visionOS.
let package = Package(
  name: "GhostingKit",
  platforms: **[.iOS(.v16),](https://ghost.org/docs/content-api/)**
 
.**
 
Ghost’s RESTful Content API delivers published content to the world and can be accessed by any client to render a website. Read more on Ghost Docs.
 
I added the relevant link to Cursor’s settings, indexed the docs, and could reference them directly while writing code:
 
**[Exploring Cursor: Accessing External Documentation using @Doc](/posts/exploring-cursor-accessing-external-documentation-using-doc)**
 
For example, I accessed the docs like this: @GhostAPI.
 

I want to create a package for Ghost Content API and here is the documentation. @GhostAPI

All I had to do was wait and see the magic unfold. In Cursor Rules, I added this simple lousy line:
 

write documentation in detail like apple

Since I prefer my packages to be compatible with Swift 6.0, I used the `actor` instead of creating a class. Here is a snippet of the `GhostingKit` actor:
 
```swift
import Foundation
 
/// An actor representing the Ghost Content API client.
///
/// The `GhostingKit` actor provides methods to interact with Ghost's RESTful Content API,
/// allowing read-only access to published content. It simplifies the process of fetching
/// posts, pages, tags, authors, tiers, and settings from a Ghost site.
///
/// - Important: This actor requires a valid API key and admin domain to function correctly.
///
/// - Note: The Content API is designed to be fully cacheable, allowing frequent data fetching without limitations.
public actor GhostingKit {
  /// The base URL for the Ghost Content API.
  private let baseURL: URL
 
  /// The API key used for authentication.
  private let apiKey: String
 
  /// The API version to use for requests.
  private let apiVersion: String
 
  /// The URL session used for network requests.
  private let urlSession: URLSession
 
  /// Initializes a new instance of the GhostingKit actor.
  ///
  /// - Parameters:
  ///   - adminDomain: The admin domain of the Ghost site (e.g., "example.ghost.io").
  ///   - apiKey: The Content API key for authentication.
  ///   - apiVersion: The API version to use (default is "v5.0").
  ///   - urlSession: The URL session to use for network requests (default is shared session).
  ///
  /// - Important: Ensure you're using the correct admin domain and HTTPS protocol for consistent behavior.
  public init(
    adminDomain: String,
    apiKey: String,
    apiVersion: String = "v5.0",
    urlSession: URLSession = .shared
  ) {
    self.baseURL = URL(string: "https://\(adminDomain)/ghost/api/content/")!
    self.apiKey = apiKey
    self.apiVersion = apiVersion
    self.urlSession = urlSession
  }
 
  /// Performs a GET request to the specified endpoint.
  ///
  /// - Parameters:
  ///   - endpoint: The API endpoint to request (e.g., "posts", "pages", "tags").
  ///   - parameters: Additional query parameters for the request.
  ///
  /// - Returns: The response data from the API.
  ///
  /// - Throws: An error if the network request fails or returns an invalid response.
  private func get(
    _ endpoint: String,
    parameters: [String: String] = [:]
  ) async throws -> Data {
    var components = URLComponents(url: baseURL.appendingPathComponent(endpoint), resolvingAgainstBaseURL: true)!
    var queryItems = [URLQueryItem(name: "key", value: apiKey)]
    queryItems += parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
    components.queryItems = queryItems
 
    var request = URLRequest(url: components.url!)
    request.addValue("v\(apiVersion)", forHTTPHeaderField: "Accept-Version")
 
    let (data, response) = try await urlSession.data(for: request)
 
    guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode) else {
      throw NSError(domain: "GhostingKit", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid response"])
    }
 
    return data
  }

I will not sugar-coat it. Not everything worked perfectly from the start. I had to iterate on methods for handling posts, tags, pages, and authors. I still prefer doing some manual work.

Creating the Sample Project

With everything in place, I wanted to create the sample project quickly. When I quickly, I meant it.

I opened Composer again and asked it to create a sample project in SwiftUI using the two relevant files. The first one contained all the methods to call, and the second one had all the tests written in it, with a demo API domain and key.

However, not everything worked out of the box. The sample project could not fetch responses because the two files I mentioned were unavailable locally. Again, my mistake. After some manual adjustments, I got everything working as taste.

Within 10 minutes. Even though it is a tiny app, I was proud to complete it so quickly.

How I am shipping with Cursor: pic.twitter.com/2YxxqKsjr0

— Rudrank Riyam (@rudrankriyam) October 18, 2024

Moving Forward

I am pretty impressed by the speed at which I could create functional Swift packages, from initial setup to a working sample project. Cursor is not the Midas touch (yet), but I imagine something like AI agents that autonomously handle the manual work I did.

Again, Cursor is not intended as an Xcode replacement. Use it for what it is great for.

Happy tab tab tab!

 

Post Topics

Explore more in these categories:

Related Articles

Exploring AI: Cosine Similarity for RAG using Accelerate and Swift

Learn how to implement cosine similarity using Accelerate framework for iOS and macOS apps. Build Retrieval-Augmented Generation (RAG) systems breaking down complex mathematics into simple explanations and practical Swift code examples. Optimize document search with vector similarity calculations.

Exploring Cursor: Accessing External Documentation using @Doc

Boost coding productivity with Cursor's @Doc feature. Learn how to index external documentation directly in your workspace, eliminating tab-switching and keeping you in flow.

Exploring Cursor: Autocompletion with Tab

Discover how to supercharge your iOS development workflow with Cursor, an AI-powered alternative to Xcode. Learn to leverage powerful autocompletion features, and integrate the Sweetpad extension.