ChatGPT + iOS App integration 📱🤖

SwiftUI

Tales Silveira
Poatek
4 min readJan 15, 2024

--

Using AI on mobile applications is probably a good idea, considering that it’s really easy to apply and could bring great features. This is how I did it on my game app.

First of all, you must have an OpenAI account to generate your API key.

Be aware that you have to pay for your tokens, so, my advice is to purchase credit before creating the API Key (there’re some possible issues when using previous generated keys)

Step 1: Getting the API Key:
Create your account and go to the API keys section in order to create your secret key: https://platform.openai.com/api-keys

With the new API key in hands, let's work on our code. For this purpose, I'll not separate things into different layers or worry about architecture at all. Since each project has it's own way of creating Network layers and storing keys and URLs, let's just be practical!

Step 2: Creating the Network base class

class GPTNetwork {

// MARK: - Stored Properties
private let APIKey = "sk-yourKey"
private let url = "https://api.openai.com/v1/chat/completions"
private let basePrompt = "I need 100 words about this subject in Swift formatted array"
var request: URLRequest?

// MARK: - Initializers
init() {
setupRequest()
}

// MARK: - Private Methods
private func setupRequest() {
guard let apiUrl = URL(string: url) else { return }
self.request = URLRequest(url: apiUrl)
self.request?.httpMethod = "POST"
self.request?.setValue("application/json", forHTTPHeaderField: "Content-Type")
self.request?.setValue("Bearer \(APIKey)", forHTTPHeaderField: "Authorization")
}
}

This is a simple Network class that stores some important config properties. What to do with them?

  1. APIKey: Obviously replace for your own (make sure it contains "sk")
  2. URL: Use this very same
  3. Base prompt: This one is optional. I'm using it because, for my app, the user is going to ask only for the subject, so I'm responsible for giving ChatGPT the whole prompt.

Step 3: Configuring the prompt

// MARK: - Private Methods
extension GPTNetwork {

private func setupRequestData(with subject: String) -> [String: Any] {
return [
"model": "gpt-3.5-turbo",
"messages": [
["role": "system", "content": "You are interacting with a language model."],
["role": "user", "content": "Subject: \(subject). \(basePrompt)"]
]
]
}
}

This is where you could customize the most.

As I said before, I'm asking the user for a subject and adding my base prompt to it, so that's why I've created a setup method. If you have a static prompt, you could make this whole requestData as a stored property, or maybe a computed property with get/set. Adjust it to your needs.

Step 4: Creating the response get method

// MARK: - Public Methods
extension GPTNetwork {

public func getGPTResponse(subject: String) async throws -> String {
guard var request else { return "" }
request.httpBody = try JSONSerialization.data(withJSONObject: setupRequestData(with: subject))

do {
let (data, _) = try await URLSession.shared.data(for: request)
let responseString = String(data: data, encoding: .utf8)
return responseString ?? ""
} catch {
throw error
}
}
}

This is where you finally ask to ChatGPT what you need and get your JSON response back.

It could end in here, because each person/project will have different needs and usage for the response, but I'll add what I did on my project just in case you want to try it

Step 5 (optional): Creating response models

struct ChatResponse: Decodable {
let choices: [Choice]
}

struct Choice: Decodable {
let message: Message
}

struct Message: Decodable {
let content: String
}

Those are simple response models to read what GPT brings for us. You can print the response body as it comes and check if you need to use more information than I did. For this project I'm only using the content.

Step 6 (optional): Converting the Json response into a string list

extension String {

func cleanString(_ string: String) -> String {
var newString = string
newString = newString.replacingOccurrences(of: "\\", with: "")
newString = newString.replacingOccurrences(of: "\n", with: "")
newString = newString.replacingOccurrences(of: "[", with: "")
newString = newString.replacingOccurrences(of: "]", with: "")
return newString
}

func convertStringToList(_ string: String) -> [String] {
string.components(separatedBy: "\", \"").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
}

func convertGPTResponseToList() -> [String] {
do {
let jsonData = self.data(using: .utf8)!
let chatResponse = try JSONDecoder().decode(ChatResponse.self, from: jsonData)

var content = chatResponse.choices.first?.message.content ?? ""
return convertStringToList(cleanString(content))
} catch {
print("Erro ao decodificar a resposta: \(error)")
return []
}
}
}

You’ll probably need to make some cleanings in the response as well in order to make it work good for you. Since my app is a text-based game app, this is what I needed to do.

Step 7 (optional): Calling it

@MainActor
class ChatGPTViewModel: ObservableObject {

// MARK: - Wrapped Properties
@Published var subject = ""
@Published var wordsList: [String] = []

// MARK: - Public Methods
func getWords() {
Task {
let result = try await GPTNetwork().getGPTWords(subject: subject)
wordsList = result
}
}
}

This is my viewModel's example of how to make the call.

That's it folks! I hope it helped you somehow today :)

--

--