Mastering SwiftUI: HTTP POST request

Diego Perez Salas
6 min readApr 1, 2024

--

This tutorial will guide you through creating a simple form, capturing user input, and using URLSession to perform a network request to a mock server endpoint.

Step 1: Define the Data Model

For this example, let’s assume we’re submitting a simple user feedback form with two fields: name and feedback.

struct Feedback: Codable {
var name: String
var feedback: String
}

Step 2: Create the Form View

Next, we’ll build a SwiftUI view that contains a form where users can enter their name and feedback.

import SwiftUI

struct FeedbackFormView: View {
@State private var name = ""
@State private var feedback = ""
@State private var showAlert = false
@State private var alertMessage = ""

var body: some View {
NavigationView {
Form {
Section(header: Text("Your Details")) {
TextField("Name", text: $name)
TextField("Feedback", text: $feedback)
}

Button("Submit") {
submitFeedback()
}
}
.navigationTitle("Feedback")
.alert(isPresented: $showAlert) {
Alert(title: Text("Feedback"), message: Text(alertMessage), dismissButton: .default(Text("OK")))
}
}
}

func submitFeedback() {
guard !name.isEmpty, !feedback.isEmpty else {
alertMessage = "Please fill in all fields"
showAlert = true
return
}

let feedbackData = Feedback(name: name, feedback: feedback)
postFeedback(feedback: feedbackData)
}
}

Step 3: Implement the Networking Logic

Now, let’s add the function to perform the HTTP POST request using URLSession. For demonstration purposes, we'll use a mock server URL like https://jsonplaceholder.typicode.com/posts, which is a free service that simulates data operations.

extension FeedbackFormView {
func postFeedback(feedback: Feedback) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")

do {
let jsonData = try JSONEncoder().encode(feedback)
request.httpBody = jsonData

URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
DispatchQueue.main.async {
self.alertMessage = "Failed to send feedback: \(error.localizedDescription)"
self.showAlert = true
}
return
}

guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 201 else {
DispatchQueue.main.async {
self.alertMessage = "Failed with status code: \((response as? HTTPURLResponse)?.statusCode ?? -1)"
self.showAlert = true
}
return
}

DispatchQueue.main.async {
self.alertMessage = "Feedback sent successfully!"
self.showAlert = true
}
}.resume()
} catch {
DispatchQueue.main.async {
self.alertMessage = "Failed to encode feedback"
self.showAlert = true
}
}
}
}

Step 4: The App Entry Point

Finally, ensure your FeedbackFormView is set as the main view in your SwiftUI app's entry point.

@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
FeedbackFormView()
}
}
}

Running Your App

When you run this app, you’ll have a simple form for submitting feedback. Upon tapping “Submit,” the app collects the input data, encodes it into JSON, and sends it to the server using an HTTP POST request. Errors and success are handled gracefully, displaying alerts to the user.

This example covers essential aspects of working with forms in SwiftUI, including capturing user input, performing validation, and networking. To extend this further, you might consider implementing more complex validation logic, handling more detailed server responses, or enhancing the user interface.

Organizing files in an Xcode project is crucial for maintaining readability, manageability, and scalability, especially as the project grows. For the examples we’ve discussed, including forms and fetching data from a network, a logical and structured approach to file organization can significantly help. Here’s a suggested structure that you might find useful:

Suggested File Structure for SwiftUI Projects

Model

Contains the data models of your application. For network requests and forms, this includes any structs or classes that represent the data you are working with or fetching.

Example: Feedback.swift for the feedback form data model.

View

Holds all the SwiftUI view files. If you have a complex UI with many components, you might further organize this folder into subfolders like Components, Screens, or Views.

Examples:

  • ContentView.swift for the main view of your app.
  • FeedbackFormView.swift for the feedback form view.
  • PostsView.swift for displaying fetched posts.

ViewModel

Contains the view model files that manage the state and logic for your views. This is where you’d include classes or structs that fetch data from the network, handle form submissions, and prepare data for presentation.

Examples:

  • PostsViewModel.swift for fetching and managing posts data.
  • FeedbackViewModel.swift for managing the feedback submission logic.

Network

Dedicated to networking tasks, including API calls, network request configurations, and any utilities related to networking.

Examples:

  • NetworkingManager.swift for a reusable network manager.
  • APIEndpoints.swift could define your app's API endpoints in a structured way.

Utility

For general-purpose utilities and helpers that are used across the app. This might include extensions, formatters, and any other utility functions or constants.

Examples:

  • Extensions.swift for common Swift or SwiftUI extensions.
  • Constants.swift for defining app-wide constants, like API base URLs.

Resources

Contains assets, such as images and data files, and other resources like storyboards (if used alongside SwiftUI), localization strings files, and configuration plist files.

AppDelegate and SceneDelegate

If you’re using them for app lifecycle management, notifications, and other app-wide tasks. With SwiftUI, you might not need to touch these much, but they’re still part of the project structure.

Tips for Maintaining the Structure

  • Use Groups: Xcode uses groups (virtual folders) to organize files in the project navigator. Ensure that your file system structure mirrors your group structure for consistency.
  • Naming Conventions: Adopt consistent naming conventions for your files and groups, making it easier to locate and understand their purpose.
  • Refactoring: As your project evolves, don’t hesitate to refactor your file organization. This might include splitting large files, reorganizing groups, or renaming files for clarity.

This structure is a starting point and should be adapted based on your project’s specific needs and complexity. The key is maintaining a logical organization that aligns with the architecture of your app, making it easy for any developer to navigate and understand your codebase.

Creating a full example with navigation, login, registration, feedback form, animations, and subscription management involves a lot of components and is quite comprehensive for a single response. However, I can outline a project structure for you and provide examples for each key part, which you can then expand into a full application.

Project Structure Overview

Here’s a proposed structure for your Xcode project:

YourApp/
├── Model/
│ ├── UserModel.swift
│ └── SubscriptionModel.swift
├── View/
│ ├── MainView.swift
│ ├── LoginView.swift
│ ├── RegistrationView.swift
│ ├── FeedbackFormView.swift
│ └── SubscriptionView.swift
├── ViewModel/
│ ├── AuthViewModel.swift
│ └── SubscriptionViewModel.swift
├── Network/
│ ├── NetworkManager.swift
│ └── APIEndpoints.swift
├── Utility/
│ ├── Extensions.swift
│ └── Constants.swift
└── Resources/
├── Assets.xcassets
└── Info.plist

Model

  • UserModel.swift: Defines the user structure.
  • SubscriptionModel.swift: Defines subscription details.

View

  • MainView.swift: The root view that manages navigation and displays the main interface.
  • LoginView.swift: Presents login fields and connects to AuthViewModel for authentication.
  • RegistrationView.swift: Collects new user information and registers them via AuthViewModel.
  • FeedbackFormView.swift: Allows users to submit feedback, showing how to use forms.
  • SubscriptionView.swift: Displays available subscriptions and handles purchases.

ViewModel

  • AuthViewModel.swift: Manages authentication tasks (login and registration).
  • SubscriptionViewModel.swift: Handles fetching subscription options and managing purchases.

Network

  • NetworkManager.swift: Centralized network requests management.
  • APIEndpoints.swift: Defines API endpoints used throughout the app.

Utility

  • Extensions.swift: Swift and SwiftUI extensions for reusability.
  • Constants.swift: Stores constants, like API base URLs.

Example Components

Let’s define some components from the structure:

NetworkManager.swift

import Foundation

class NetworkManager {
static let shared = NetworkManager()

func login(with credentials: Credentials, completion: @escaping (Bool) -> Void) {
// Perform login network request
}

func fetchSubscriptions(completion: @escaping ([SubscriptionModel]) -> Void) {
// Fetch subscription options
}
}

LoginView.swift

import SwiftUI

struct LoginView: View {
@State private var username: String = ""
@State private var password: String = ""

var body: some View {
Form {
TextField("Username", text: $username)
SecureField("Password", text: $password)
Button("Log In") {
// Call ViewModel to handle login
}
}
}
}

MainView.swift

import SwiftUI

struct MainView: View {
var body: some View {
TabView {
LoginView()
.tabItem {
Label("Login", systemImage: "person.fill")
}
RegistrationView()
.tabItem {
Label("Register", systemImage: "person.badge.plus")
}
FeedbackFormView()
.tabItem {
Label("Feedback", systemImage: "bubble.left.fill")
}
SubscriptionView()
.tabItem {
Label("Subscribe", systemImage: "star.fill")
}
}
}
}

Implementing Animations

Animations in SwiftUI can be easily added with modifiers. For example, in your FeedbackFormView, you might want to animate the submission button to confirm the user's action:

Button("Submit") {
// Trigger the animation
}
.scaleEffect(isAnimating ? 1.2 : 1)
.animation(.easeInOut, value: isAnimating)

Managing Subscriptions

Handling subscriptions and tokens involves securely communicating with your backend and possibly integrating with StoreKit for in-app purchases. The SubscriptionViewModel would handle fetching available subscriptions and processing purchases, coordinating with NetworkManager for server-side verification.

This outline and examples give you a foundation to build a comprehensive SwiftUI app featuring authentication, feedback submission, animations, and subscription management. Remember, the key to managing complex projects is keeping your codebase organized and modular. Each component should have a clear responsibility, and you should aim for reusable and maintainable code patterns.

--

--