# Component Design Standards for SwiftUI
This document outlines component design standards for SwiftUI development, focusing on creating reusable, maintainable, and performant UI elements. Adhering to these standards will promote consistency, readability, and scalability across projects.
## 1. Component Architecture and Principles
### 1.1. Single Responsibility Principle (SRP)
**Standard:** Each SwiftUI view (component) should have one, and only one, reason to change.
**Why:** SRP enhances reusability, testability, and maintainability. Views that handle multiple responsibilities become tightly coupled and difficult to modify without introducing unintended side effects.
**Do This:** Break down complex views into smaller, focused components.
**Don't Do This:** Create monolithic views that handle data fetching, business logic, and UI rendering.
**Example:**
"""swift
// Good: Separating concerns
struct UserProfileView: View {
let userID: String
var body: some View {
VStack {
UserHeaderView(userID: userID)
UserDetailView(userID: userID)
}
}
}
struct UserHeaderView: View {
@StateObject var viewModel: UserHeaderViewModel
init(userID: String) {
_viewModel = StateObject(wrappedValue: UserHeaderViewModel(userID: userID))
}
var body: some View {
HStack {
AsyncImage(url: viewModel.user?.profileImageURL) { image in
image
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
} placeholder: {
ProgressView()
.frame(width: 50, height: 50)
}
Text(viewModel.user?.name ?? "Loading...")
}
.onAppear{
viewModel.loadUser()
}
}
}
struct UserDetailView: View {
@StateObject var viewModel: UserDetailViewModel
init(userID: String) {
_viewModel = StateObject(wrappedValue: UserDetailViewModel(userID: userID))
}
var body: some View {
VStack(alignment: .leading) {
Text("Email: \(viewModel.user?.email ?? "Loading...")")
Text("Location: \(viewModel.user?.location ?? "Loading...")")
}
.onAppear{
viewModel.loadUser()
}
}
}
"""
"""swift
//MARK: View Models
import Foundation
class UserHeaderViewModel: ObservableObject {
@Published var user: User?
let userID: String
init(userID: String) {
self.userID = userID
}
func loadUser() {
// Simulate an API call
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.user = User(id: self.userID, name: "John Doe", email: "john.doe@example.com", location: "New York", profileImageURL: URL(string: "https://example.com/johndoe.jpg"))
}
}
}
class UserDetailViewModel: ObservableObject {
@Published var user: User?
let userID: String
init(userID: String) {
self.userID = userID
}
func loadUser() {
// Simulate an API call
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.user = User(id: self.userID, name: "John Doe", email: "john.doe@example.com", location: "New York", profileImageURL: URL(string: "https://example.com/johndoe.jpg"))
}
}
}
//MARK: Data Model
struct User: Identifiable {
let id: String
let name: String
let email: String
let location: String
let profileImageURL: URL?
}
"""
### 1.2. Abstraction
**Standard:** Create abstract components that can be customized through parameters, modifiers, and content closures.
**Why:** Abstraction promotes code reuse and reduces duplication, making it easier to maintain and evolve the UI.
**Do This:** Parameterize views with data and callbacks, using environment objects for global state.
**Don't Do This:** Hardcode specific values or behaviors within a component that should be configurable.
**Example:**
"""swift
// Good: Abstract Button
struct CustomButton: View {
let action: () -> Void
let label: Content
let isDisabled: Bool
init(action: @escaping () -> Void, isDisabled: Bool = false, @ViewBuilder label: () -> Content) {
self.action = action
self.label = label()
self.isDisabled = isDisabled
}
var body: some View {
Button(action: action) {
label
.padding()
.background(isDisabled ? Color.gray : Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.disabled(isDisabled)
}
}
// Usage
struct ContentView: View {
@State private var counter = 0
var body: some View {
VStack {
Text("Counter: \(counter)")
CustomButton(action: { counter += 1 }) {
Text("Increment")
}
CustomButton(action: { counter = 0 }, isDisabled: counter == 0) {
Text("Reset")
}
}
}
}
"""
### 1.3. Composition
**Standard:** Build complex UIs by composing smaller, reusable views.
**Why:** Composition simplifies the structure of the UI, makes components more testable, and allows for greater flexibility in design.
**Do This:** Use "ViewBuilder" to compose flexible layouts within components.
**Don't Do This:** Embed deeply nested logic within a single view.
**Example:**
"""swift
// Good: Compositional layout
struct ProductCard: View {
let product: Product
var body: some View {
VStack(alignment: .leading) {
ProductImage(url: product.imageURL)
ProductDetails(name: product.name, price: product.price)
}
.padding()
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 3)
}
}
struct ProductImage: View {
let url: URL?
var body: some View {
AsyncImage(url: url) { image in
image.resizable().scaledToFit()
} placeholder: {
Color.gray.opacity(0.3)
}
.frame(height: 150)
}
}
struct ProductDetails: View {
let name: String
let price: Double
var body: some View {
VStack(alignment: .leading) {
Text(name)
.font(.headline)
Text("Price: $\(price, specifier: "%.2f")")
.font(.subheadline)
}
}
}
struct Product: Identifiable {
let id = UUID()
let name: String
let price: Double
let imageURL: URL?
}
"""
### 1.4. Separation of Concerns (SoC)
**Standard:** Separate UI presentation from data handling and business logic.
**Why:** SoC significantly improves code organization, testability, and maintainability.
**Do This:** Use ViewModels (ObservableObjects) to manage data and state.
**Don't Do This:** Perform network requests, data transformations, or complex calculations directly within the view's "body".
**Example:**
"""swift
// Good: Using ViewModel
class ArticleViewModel: ObservableObject {
@Published var article: Article?
@Published var isLoading = false
private let articleID: String
init(articleID: String) {
self.articleID = articleID
}
func loadArticle() {
isLoading = true
// Simulate API call
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.article = Article(title: "Example Article", content: "This is example content.", author: "Jane Doe")
self.isLoading = false
}
}
}
struct ArticleView: View {
@StateObject var viewModel: ArticleViewModel
init(articleID: String) {
_viewModel = StateObject(wrappedValue: ArticleViewModel(articleID: articleID))
}
var body: some View {
VStack {
if viewModel.isLoading {
ProgressView()
} else if let article = viewModel.article {
Text(article.title)
.font(.title)
Text(article.content)
.padding()
Text("By: \(article.author)")
.font(.subheadline)
} else {
Text("Failed to load article")
}
}
.onAppear {
viewModel.loadArticle()
}
}
}
struct Article {
let title: String
let content: String
let author: String
}
"""
## 2. SwiftUI Specific Standards
### 2.1. Using "@State", "@Binding", "@ObservedObject", "@StateObject", and "@EnvironmentObject"
**Standard:** Choose the correct state management property wrapper based on the scope and ownership of data.
**Why:** Proper state management is crucial for predictable UI behavior and performance.
* "@State": Use for simple, private state within a single view.
* "@Binding": Use to create a two-way connection to a "@State" or "@Binding" property in a parent view.
* "@ObservedObject": Use to observe changes from an external ObservableObject (e.g., ViewModel). The observed object should be passed down from another View.
* "@StateObject": Use to create and own an ObservableObject within a view's lifecycle. This ensures the ViewModel is only created *once* for each instance of the View.
* "@EnvironmentObject": Use to access shared data across multiple views via the environment.
**Do This:**
* Use "@StateObject" to instantiate the ViewModel in the parent, and "@ObservedObject" to receive it in child views. Inject only the required properties in the children.
* Pass data down using "@Binding" for direct modifications from child views and reading the data from parent views.
* Prefer using "@EnvironmentObject" for app-wide configurations and less for specific data transfer between views.
**Don't Do This:**
* Use "@State" for complex data models or data that needs to be shared across views.
* Create a new instance of the ViewModel every time the view initializes (e.g. in the "body").
**Example:**
"""swift
// Good: Correct State Management
class CounterViewModel: ObservableObject {
@Published var count = 0
func increment() {
count += 1
}
}
struct CounterView: View {
@StateObject var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Counter: \(viewModel.count)")
IncrementButton(viewModel: viewModel)
ResetButton(counter: $viewModel.count) // Pass down count as a Binding
}
.padding()
}
}
struct IncrementButton: View {
@ObservedObject var viewModel: CounterViewModel
var body: some View {
Button("Increment") {
viewModel.increment()
}
}
}
struct ResetButton: View {
@Binding var counter: Int
var body: some View {
Button("Reset") {
counter = 0
}
}
}
// Imagine MyApp is the top-level app
@main
struct MyApp: App {
@StateObject private var appSettings = AppSettings() // App-level config
var body: some Scene {
WindowGroup {
ContentView() // CounterView would exist somewhere down this tree
.environmentObject(appSettings)
}
}
}
class AppSettings: ObservableObject {
@Published var theme = "light" // Example app-wide setting
}
struct ContentView: View {
@EnvironmentObject var appSettings: AppSettings
var body: some View {
VStack {
Text("Theme: \(appSettings.theme)") // Access global theme
CounterView()
}
}
}
"""
### 2.2. View Composition with "ViewBuilder"
**Standard:** Use "ViewBuilder" to create flexible and reusable container views.
**Why:** "ViewBuilder" simplifies the creation of conditional and dynamic view hierarchies.
**Do This:**
* Utilize "@ViewBuilder" to create custom layouts (e.g., stacks, grids) that accept arbitrary content.
* Create separate modifiers for styling common visual attributes.
**Don't Do This:**
* Embed complex conditional logic directly within the "body" when composition can be used instead.
**Example:**
"""swift
// Good: Using ViewBuilder for flexible layout
struct CardView: View {
let title: String
@ViewBuilder let content: () -> Content
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.headline)
.padding(.bottom, 5)
content()
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
}
}
struct ContentView: View {
var body: some View {
CardView(title: "User Profile") {
Text("Name: John Doe")
Text("Email: john.doe@example.com")
}
}
}
"""
### 2.3. Custom Modifiers
**Standard:** Encapsulate reusable view modifications into custom modifiers.
**Why:** Custom modifiers promote code reuse and make UI code more readable.
**Do This:**
* Create extension on "View" to add new view modifiers.
**Don't Do This:**
* Apply the same long chain of modifiers repeatedly throughout codebase.
**Example:**
"""swift
// Good: Custom Modifier
struct ShadowModifier: ViewModifier {
func body(content: Content) -> some View {
content
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 2)
}
}
extension View {
func withShadow() -> some View {
modifier(ShadowModifier())
}
}
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.padding()
.background(Color.white)
.withShadow()
}
}
"""
### 2.4 Accessibility
**Standard:** Ensure all custom components are fully accessible.
**Why:** Ensures that app is usable by everyone, including users with disabilities.
**Do This:**
* Use "accessibilityLabel", "accessibilityHint", and "accessibilityAdjustableAction" appropriately.
* Test accessibility thoroughly using VoiceOver and other accessibility tools.
**Don't Do This:**
* Rely solely on visual cues without providing alternative text descriptions.
**Example:**
"""swift
// Accessibility Example
struct AccessibleButton: View {
let action: () -> Void
let label: String
var body: some View {
Button(action: action) {
Text(label)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.accessibilityLabel(label)
.accessibilityHint("Taps to perform action.") //Describe what the button does
}
}
"""
### 2.5. PreviewProvider
**Standard:** Provide previews for all custom components for easy iteration.
**Why:** Previews streamline UI development by allowing developers to see live changes without building and running the app on a device or simulator.
**Do This:**
* Create simple preview providers for the component
* Include multiple previews to showcase different states or configurations for the component.
**Don't Do This:**
* Skip previews.
**Example:**
"""swift
struct CustomText: View {
var text: String = "Default Text" //Default value for the preview
var body: some View {
Text(text).font(.title)
}
}
struct CustomText_Previews: PreviewProvider {
static var previews: some View {
Group {
CustomText(text: "Custom Text here") //Initial state of the component
CustomText()//Default state of the component
}.previewLayout(.sizeThatFits)
}
}
"""
## 3. Performance Optimization
### 3.1. Avoiding Unnecessary View Updates
**Standard:** Minimize unnecessary view updates to improve performance.
**Why:** Frequent view updates can lead to performance bottlenecks, especially in complex UIs.
**Do This:**
* Use "Equatable" conformance and "onChange" modifiers to control view updates based on specific data changes.
* Use SwiftUI's "isEqual" to ensure views that are logically identical aren't redrawn.
**Don't Do This:**
* Force views to re-render unnecessarily with methods like "objectWillChange.send()".
**Example:**
"""swift
// Good: Preventing unnecessary updates
struct ProductView: View, Equatable {
let product: Product // Ensure Product conforms to Equatable
static func == (lhs: ProductView, rhs: ProductView) -> Bool {
return lhs.product == rhs.product
}
var body: some View {
Text(product.name)
.onChange(of: product) { newProduct in
print("Product changed: \(newProduct.name)")
}
}
}
struct Product: Equatable {
let id = UUID()
let name: String
}
"""
### 3.2. Lazy Loading
**Standard:** Load content only when it's needed or visible.
**Why:** Improves startup time and reduces memory usage, especially in lists and complex layouts.
**Do This:**
* Use "LazyVStack" and "LazyHStack" for views that contain many children or load content based on user-triggered actions.
* Use "AsyncImage" in order to load them in the background. Show a "ProgressView" until the image loads.
**Don't Do This:**
* Load all content upfront, especially if it's not immediately visible.
**Example:**
"""swift
// Good: Lazy Loading
struct ProductListView: View {
let products: [Product] = Array(repeating: Product(name: "Product"), count: 100)
var body: some View {
ScrollView {
LazyVStack {
ForEach(products.indices, id: \.self) { index in
ProductRow(product: products[index]) // Each product gets its own row
}
}
}
}
}
struct ProductRow: View {
let product: Product
var body: some View {
Text("Row \(product.name) \(index)")
.padding()
}
}
"""
### 3.3. Reducing View Hierarchy Depth
**Standard:** Minimize the depth of the view hierarchy.
**Why:** Deep view hierarchies can negatively impact rendering performance.
**Do This:**
* Flatten layouts where possible by using techniques such as absolute positioning or custom layout containers introduced in later SwiftUI versions.
* Make sure there is no extra padding that can be added to the parent for example.
**Don't Do This:**
* Nest unnecessary "VStack"s, "HStack"s, and "ZStack"s.
**Example:**
"""swift
//Good - Flatter Hierarchy
struct FlatterLayout: View {
var body: some View {
ZStack(alignment: .topLeading) {
Rectangle()
.fill(.gray.opacity(0.3))
.frame(width: 200, height: 100)
.zIndex(0)
Text("Overlayed Text 1")
.padding(10)
.background(.white)
.zIndex(1)
.offset(x: 10, y: 10)
Text("Overlayed Text 2")
.padding(10)
.background(.white)
.zIndex(1)
.offset(x: 20, y: 50)
}
}
}
"""
### 3.4. Image Optimization
**Standard:** Optimize images for size and resolution.
**Why:** Large images can consume excessive memory and increase loading times.
**Do This:**
* Use appropriately sized images for their display dimensions.
* Use image compression tools to reduce file sizes.
* Use Asset Catalogs to manage different image resolutions.
**Don't Do This:**
* Use excessively large images, especially for assets displayed at smaller sizes.
## 4. Security Considerations
### 4.1. Secure Data Handling
**Standard:** Handle sensitive data securely.
**Why:** Protecting user data is crucial for security and privacy.
**Do This:**
* Use Keychain Services to store sensitive information like passwords and API keys.
* Encrypt sensitive data both in transit and at rest.
**Don't Do This:**
* Store sensitive data in plain text, UserDefaults, or directly within the app's code.
### 4.2. Input Validation
**Standard:** Validate user input to prevent injection attacks and data corruption.
**Why:** Input validation is essential for preventing security vulnerabilities and ensuring data integrity.
**Do This:**
* Use secure coding practices for handling user inputs and validating the data.
* Sanitize the data before sending it to the backend, so only the correct data is sent over
**Don't Do This:**
* Trust user input without validation.
**Example:**
"""swift
// Input Validation Example
struct ValidatedTextField: View {
@State private var text: String = ""
@State private var isValid: Bool = true
var body: some View {
VStack {
TextField("Enter Email", text: $text)
.onChange(of: text) { newValue in
isValid = isValidEmail(newValue)
}
if !isValid {
Text("Invalid email format").foregroundColor(.red)
}
}
}
func isValidEmail(_ email: String) -> Bool {
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
return NSPredicate(format:"SELF MATCHES %@", emailRegex).evaluate(with: email)
}
}
"""
## 5. Naming Conventions
**Standard:** Follow consistent naming conventions for components, properties, and methods.
**Why:** Consistent naming improves code readability and maintainability.
**Do This:**
* Use descriptive names that clearly indicate the purpose and function of the component.
* Use TitleCase for struct names, camelCase for variable names, and PascalCase for functions/methods.
**Don't Do This:**
* Use cryptic or abbreviated names that are difficult to understand.
## 6. Error Handling
**Standard:** Implement robust error handling to gracefully manage unexpected situations.
**Why:** Proper error handling prevents crashes and enhances the user experience.
**Do This:**
* Use "Result" types and "try/catch" blocks to handle potential errors.
* Display informative error messages to the user.
**Don't Do This:**
* Ignore errors or allow the app to crash silently.
## 7. Testing
**Standard:** Write unit and UI tests to ensure the correctness and reliability of components.
**Why:** Tests help prevent bugs and ensure that components behave as expected.
**Do This:**
* Write unit tests for individual views and ViewModels.
* Use UI tests to verify the behavior of the UI.
**Don't Do This:**
* Skip testing components altogether.
By following these guidelines, teams can deliver high-quality, maintainable, and performant SwiftUI applications ensuring maximum productivity both alone and when collaborating with AI code-generation tools.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# API Integration Standards for SwiftUI This document outlines coding standards for integrating APIs into SwiftUI applications. It focuses on patterns, best practices, and modern approaches for creating robust, maintainable, and performant applications that consume backend services and external APIs. ## 1. Architectural Considerations for API Integration ### 1.1. Model-View-ViewModel (MVVM) with Repository Pattern **Standard:** Employ the MVVM architecture with a Repository pattern to decouple the SwiftUI view layer from the data access layer. * **Do This:** Isolate data fetching and processing logic within dedicated "Repository" classes. * **Don't Do This:** Directly perform API calls within SwiftUI views or view models. **Why:** * **Maintainability:** Decoupling data fetching makes the code easier to test, modify, and understand. * **Testability:** Repositories can be easily mocked for unit testing view models. * **Reusability:** Repositories can be used across multiple view models and even different parts of the application. **Code Example:** """swift // Data Model struct Article: Identifiable, Decodable { let id: Int let title: String let body: String } // Repository Protocol protocol ArticleRepository { func fetchArticles() async throws -> [Article] } // Concrete Repository Implementation class ArticleRepositoryImpl: ArticleRepository { private let baseURL = "https://example.com/api" private let session: URLSession init(session: URLSession = .shared) { self.session = session } func fetchArticles() async throws -> [Article] { guard let url = URL(string: "\(baseURL)/articles") else { throw APIError.invalidURL } let (data, response) = try await session.data(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw APIError.invalidResponse } do { let decoder = JSONDecoder() return try decoder.decode([Article].self, from: data) } catch { throw APIError.decodingError(error) } } // Define custom API errors for better error handling enum APIError: Error { case invalidURL case invalidResponse case decodingError(Error) } } // Example of Unit Testing a Repository (using XCTest) #if DEBUG class MockArticleRepository: ArticleRepository { var articles: [Article] = [ Article(id: 1, title: "Test Article 1", body: "This is a test article."), Article(id: 2, title: "Test Article 2", body: "This is another test article.") ] func fetchArticles() async throws -> [Article] { return articles } } import XCTest class ArticleRepositoryTests: XCTestCase { func testFetchArticlesSuccess() async throws { // Mock URLSession for controlled testing let mockData = "[{\"id\":1,\"title\":\"Test Article\",\"body\":\"Test Body\"}]".data(using: .utf8)! let mockResponse = HTTPURLResponse(url: URL(string: "https://example.com/api/articles")!, statusCode: 200, httpVersion: nil, headerFields: nil)! let mockSession = MockURLSession(data: mockData, response: mockResponse, error: nil) let repository = ArticleRepositoryImpl(session: mockSession) let articles = try await repository.fetchArticles() XCTAssertEqual(articles.count, 1) XCTAssertEqual(articles[0].title, "Test Article") } func testFetchArticlesInvalidURL() async throws { let repository = ArticleRepositoryImpl(session: .shared) do { _ = try await repository.fetchArticles() // This will trigger an exception later on with the real API. XCTFail("Expected an error to be thrown.") } catch { XCTAssertTrue(error is ArticleRepositoryImpl.APIError) } } } // Mock URLSession Class for testing class MockURLSession: URLSession { var data: Data? var response: URLResponse? var error: Error? init(data: Data?, response: URLResponse?, error: Error?) { self.data = data self.response = response self.error = error } override func data(from url: URL) async throws -> (Data, URLResponse) { if let error = error { throw error } return (data!, response!) } } #endif // View Model class ArticleViewModel: ObservableObject { @Published var articles: [Article] = [] @Published var isLoading: Bool = false @Published var errorMessage: String? private let articleRepository: ArticleRepository init(articleRepository: ArticleRepository) { self.articleRepository = articleRepository } func fetchArticles() { isLoading = true errorMessage = nil Task { do { let fetchedArticles = try await articleRepository.fetchArticles() await MainActor.run { self.articles = fetchedArticles self.isLoading = false } } catch { await MainActor.run { self.errorMessage = "Failed to fetch articles: \(error.localizedDescription)" self.isLoading = false } } } } } // SwiftUI View struct ArticleListView: View { @ObservedObject var viewModel: ArticleViewModel var body: some View { VStack { if viewModel.isLoading { ProgressView("Loading Articles...") } else if let errorMessage = viewModel.errorMessage { Text("Error: \(errorMessage)") .foregroundColor(.red) } else { List(viewModel.articles) { article in Text(article.title) } } } .onAppear { viewModel.fetchArticles() } } } """ ### 1.2. Dependency Injection **Standard:** Use dependency injection to provide the "Repository" instance to the "ViewModel". * **Do This:** Initialize the "ViewModel" with a "Repository" instance passed as a parameter. * **Don't Do This:** Create the "Repository" instance directly within the "ViewModel". **Why:** * **Testability:** Allows you to inject mock repositories for testing purposes. * **Flexibility:** Enables swapping different repository implementations (e.g., local vs. remote) without modifying the view model. **Code Example:** """swift // In the SwiftUI View: struct ArticleListView: View { @ObservedObject var viewModel: ArticleViewModel init(viewModel: ArticleViewModel) { self.viewModel = viewModel } var body: some View { // ... (rest of the view implementation) } } // Usage: let repository = ArticleRepositoryImpl() let viewModel = ArticleViewModel(articleRepository: repository) let contentView = ArticleListView(viewModel: viewModel) """ ### 1.3. Error Handling **Standard:** Implement comprehensive error handling that covers the entire API integration process. * **Do This:** Use a custom "APIError" enum to represent specific error scenarios (e.g., invalid URL, network error, decoding error). Handle errors gracefully in the view model and display meaningful error messages to the user. * **Don't Do This:** Ignore errors or display generic error messages. **Why:** * **User Experience:** Provides informative feedback to the user when something goes wrong. * **Debugging:** Simplifies debugging by providing specific error information. **Code Example:** (See the "ArticleRepositoryImpl" class in section 1.1 for an example of a custom "APIError" enum). ## 2. API Request Standards ### 2.1. URLSession Configuration **Standard:** Configure "URLSession" with appropriate timeouts and caching policies. * **Do This:** Use a custom "URLSessionConfiguration" to set "timeoutIntervalForRequest" and "urlCache". * **Don't Do This:** Use the default "URLSession.shared" without any configuration for production environments. **Why:** * **Performance:** Improves application responsiveness by setting reasonable timeouts. * **Efficiency:** Reduces network traffic by leveraging URL caching. **Code Example:** """swift let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 15 // seconds configuration.urlCache = URLCache.shared let session = URLSession(configuration: configuration) let repository = ArticleRepositoryImpl(session: session) """ ### 2.2. Request Methods **Standard:** Use the appropriate HTTP request methods (GET, POST, PUT, DELETE) according to RESTful principles. * **Do This:** Use "GET" for retrieving data, "POST" for creating new resources, "PUT" for updating existing resources, and "DELETE" for deleting resources. * **Don't Do This:** Use "GET" requests to perform actions that modify data. ### 2.3. Request Headers **Standard:** Include necessary request headers such as "Content-Type" and "Authorization". * **Do This:** Set the "Content-Type" header to "application/json" when sending JSON data. Use the "Authorization" header for authentication. * **Don't Do This:** Expose sensitive information in request headers unnecessarily. **Code Example:** """swift func createNewArticle(article: Article) async throws { guard let url = URL(string: "\(baseURL)/articles") else { throw APIError.invalidURL } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") // Example: Adding an authorization header if required request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") let encoder = JSONEncoder() request.httpBody = try encoder.encode(article) let (_, response) = try await session.data(for: request) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 201 else { // 201 Created is the expected response for a successful POST throw APIError.invalidResponse } // Handle successful creation (e.g., parse response if needed) } """ ### 2.4. Asynchronous Operations **Standard:** Always perform API requests asynchronously to avoid blocking the main thread. * **Do This:** Use "async/await" or Combine to handle asynchronous API requests. Update the UI on the main thread using "@MainActor". * **Don't Do This:** Perform synchronous API requests on the main thread. ## 3. Data Handling Standards ### 3.1. JSON Parsing **Standard:** Use "JSONDecoder" and "JSONEncoder" to parse and serialize JSON data. * **Do This:** Define data models using "Codable" conformance. Handle potential decoding errors gracefully. * **Don't Do This:** Manually parse JSON data using "JSONSerialization". **Code Example:** (See the "Article" struct and the JSON decoding logic in "ArticleRepositoryImpl" in section 1.1). ### 3.2. Data Validation **Standard:** Validate API responses before using the data in your application. * **Do This:** Check for required fields and data types. Handle unexpected data formats gracefully. * **Don't Do This:** Assume that the API response always contains the expected data. **Code Example:** """swift struct User: Decodable { let id: Int let name: String? // Make optional as the API might return null let email: String // Custom decoding to handle potential nil values or incorrect types init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(Int.self, forKey: .id) name = try container.decodeIfPresent(String.self, forKey: .name) // Use decodeIfPresent email = try container.decode(String.self, forKey: .email) } enum CodingKeys: String, CodingKey { case id, name, email } } """ ### 3.3. Secure Data Storage **Standard:** Use secure storage mechanisms for sensitive data such as API keys and user credentials. * **Do This:** Use the Keychain to store sensitive data. * **Don't Do This:** Store sensitive data in plain text in code or in "UserDefaults". **Code Example:** (Implementation of the Keychain storage is beyond the scope of this document, but there are numerous libraries available for this purpose, such as "SwiftKeychainWrapper"). ## 4. SwiftUI-Specific Considerations ### 4.1. State Management **Standard:** Leverage SwiftUI's state management features ("@State", "@ObservedObject", "@EnvironmentObject") to manage data flow between the view model and the view. * **Do This:** Use "@ObservedObject" for the view model, "@State" for local view state, and "@EnvironmentObject" for shared data across the application. * **Don't Do This:** Directly modify the view model's state from the view. **Code Example:** (See the "ArticleListView" in section 1.1 for an example of using "@ObservedObject"). ### 4.2. Data Binding **Standard:** Use data binding to keep the UI synchronized with the view model. * **Do This:** Use "@State" variables and "TextField" bindings to update the view model when the user interacts with the UI. * **Don't Do This:** Manually update the UI elements when the view model changes. **Code Example:** """swift struct EditArticleView: View { @ObservedObject var viewModel: ArticleViewModel @State private var articleTitle: String // local state for editing var body: some View { VStack { TextField("Title", text: $articleTitle) // Binding to local state .padding() Button("Update Title") { viewModel.updateArticleTitle(newTitle: articleTitle) // Call VM to update } }.onAppear { // Initialize the local state articleTitle = viewModel.currentArticle.title } } } class ArticleViewModel: ObservableObject { @Published var currentArticle: Article // ... other code init(article: Article) { self.currentArticle = article } func updateArticleTitle(newTitle: String) { // Business logic to update the article title // Potentially make an API call to persist update currentArticle.title = newTitle } } """ ### 4.3. List Performance **Standard:** Optimize the performance of lists that display data fetched from APIs. * **Do This:** Use "Identifiable" protocol conformance to provide stable identities for list items. Use "LazyVStack" and "LazyHStack" for large lists. Employ pagination where appropriate. * **Don't Do This:** Display large lists without any optimization. **Code Example:** """swift // Ensure your data Model conforms to Identifiable struct Article: Identifiable, Decodable { let id: Int let title: String let body: String } // Using LazyVStack for efficient rendering of a large list: struct ArticleListView: View { @ObservedObject var viewModel: ArticleViewModel var body: some View { ScrollView { // Wrap in ScrollView for lazy loading LazyVStack { ForEach(viewModel.articles) { article in ArticleRow(article: article) } } } .onAppear { viewModel.fetchArticles() } } } """ ### 4.4. Handling Loading States **Standard:** Provide clear visual feedback to the user while data is loading from the API. * **Do This:** Display a "ProgressView" while loading data. Disable UI elements that are not available until the data is loaded. Use a "placeholder" view. * **Don't Do This:** Leave the user in the dark while data is loading. **Code Example:** (See the "ArticleListView" in section 1.1 for an example of displaying a "ProgressView"). ## 5. Security Best Practices ### 5.1. HTTPS **Standard:** Always use HTTPS for all API requests. * **Do This:** Ensure that all API endpoints use HTTPS. * **Don't Do This:** Use HTTP for sensitive data or authentication. ### 5.2. Input Validation **Standard:** Validate all user input before sending it to the API. * **Do This:** Check for invalid characters, malicious code, and potential security vulnerabilities. * **Don't Do This:** Trust user input without validation. ### 5.3. Output Encoding **Standard:** Encode all data received from the API before displaying it in the UI. * **Do This:** Use appropriate encoding techniques to prevent cross-site scripting (XSS) and other security vulnerabilities. * **Don't Do This:** Display raw data from the API without encoding. ## 6. Testing ### 6.1. Unit Tests **Standard:** Write unit tests for the "Repository" and "ViewModel" layers. * **Do This:** Mock the "URLSession" or the "Repository" to isolate the code under test. Verify that the code behaves as expected in different scenarios (e.g., successful response, error response, invalid data). * **Don't Do This:** Skip unit tests for the data access and business logic layers. ### 6.2. UI Tests **Standard:** Write UI tests to verify that the API integration works correctly from the user's perspective. * **Do This:** Simulate user interactions and verify that the UI displays the correct data. Mock API responses to test different scenarios. * **Don't Do This:** Rely solely on manual testing to verify the API integration. ## 7. Common Anti-Patterns ### 7.1. God Object View Models **Anti-Pattern:** Creating view models that handle too many responsibilities, including API requests, data parsing, and UI updates. **Solution:** Follow the MVVM pattern strictly. Delegate data fetching to a dedicated "Repository". ### 7.2. Massive View Controllers (in UIKit interop) **Anti-Pattern:** Placing all the logic for API integration directly inside SwiftUI views, or if using UIKit, inside UIViewControllers. **Solution:** Adhere to MVVM. Move API calls and data processing into the view model and repository. ### 7.3. Ignoring Errors **Anti-Pattern:** Not handling errors properly and displaying generic error messages to the user. **Solution:** Implement comprehensive error handling with custom "APIError" types and display informative error messages. ## 8. Performance Optimization ### 8.1. Caching Strategies **Standard:** Implement layers of caching from HTTP Cache, Memory Cache to Disk Cache depends on the data criticality. * **Do This:** Apply "URLCache" for HTTP caching. Store in memory frequently using data with "NSCache" * **Don't Do This:** Neglect using cache or aggressively cache information when the data is highly dynamic. ### 8.2. Image Loading Optimizations **Standard:** Optimize image loading, especially if you're retrieving images from an API. * **Do This**: Utilize "AsyncImage" which automatically handles the loading and caching. * **Don't Do This**: Load images synchrounously directly in the main thread. """swift import SwiftUI struct AsyncImageView: View { let imageURL: String var body: some View { AsyncImage(url: URL(string: imageURL)) { phase in switch phase { case .empty: ProgressView() // Show a progress indicator case .success(let image): image .resizable() .scaledToFit() case .failure: Image(systemName: "photo") // Show a placeholder image for errors .foregroundColor(.red) @unknown default: EmptyView() } } } } """ ## 9. Conclusion By following these coding standards, you can create robust, maintainable, and performant SwiftUI applications that integrate with APIs effectively. This will result in a better user experience, easier debugging, and more efficient development process. Remember to regularly review and update these standards as SwiftUI evolves and new best practices emerge.
# State Management Standards for SwiftUI This document outlines the coding standards for state management in SwiftUI applications. It aims to provide clear, actionable guidelines for managing application state, data flow, and reactivity to ensure maintainable, performant, and secure code. These standards are based on the latest version of SwiftUI and incorporate modern best practices. ## 1. Introduction to State Management in SwiftUI SwiftUI offers several mechanisms for managing state, enabling dynamic UI updates and data flow between views. Selecting the appropriate state management technique depends on the scope and complexity of the data being managed. Poorly managed state can lead to performance issues, unexpected behavior, and difficult-to-debug code. * **Standard:** Choose the simplest state management approach that meets the needs of the specific use case. Avoid over-complicating state management for trivial scenarios. * **Why:** Simplifies code, reduces potential for bugs, and improves performance. ## 2. State and Binding "@State" and "@Binding" are fundamental property wrappers for managing simple, local state within views. ### 2.1. Using "@State" "@State" manages mutable data within a single view. It's ideal for UI controls and temporary data that doesn't need to persist between view reloads or be shared with other views. * **Do This:** Use "@State" for simple, view-local state, such as whether a button is toggled or the text in a TextField. * **Don't Do This:** Don't use "@State" to manage data that needs to persist across view recreations or be shared among different views. **Example:** """swift import SwiftUI struct ToggleExampleView: View { @State private var isToggled: Bool = false var body: some View { VStack { Toggle(isOn: $isToggled) { Text("Toggle Me") } Text(isToggled ? "Toggled On" : "Toggled Off") } .padding() } } """ * **Why:** "@State" is optimized for fast, local updates. Misusing it can create unnecessary data duplication and make debugging difficult. ### 2.2. Using "@Binding" "@Binding" creates a two-way connection between a view and its parent, allowing the child view to modify the parent's state directly. * **Do This:** Use "@Binding" to allow child views to control or modify state owned by a parent view. * **Don't Do This:** Avoid creating deep chains of bindings (passing a "@Binding" down multiple levels). This can make data flow hard to trace and debug. **Example:** """swift import SwiftUI struct ParentView: View { @State private var message: String = "Hello" var body: some View { VStack { Text("Parent View: \(message)") ChildView(message: $message) } .padding() } } struct ChildView: View { @Binding var message: String var body: some View { TextField("Enter message", text: $message) .padding() } } """ * **Why:** "@Binding" enables clear data flow between related views, but excessive use can lead to tangled dependencies. It promotes data accessibility and UI coordination. ### 2.3 Anti-Pattern: Prop Drilling with Bindings * **Don't Do This:** Passing "@Binding" properties down multiple layers of views (prop drilling). This creates tight coupling between unrelated views. It is a sign that "EnvironmentObject" or "ObservedObject" should be considered. * **Why:** Prop drilling makes code harder to maintain and understand, particularly as the application grows in complexity. ## 3. Observed Objects and State Objects "@ObservedObject" and "@StateObject" are essential for managing complex data models within SwiftUI. ### 3.1. Using "@ObservedObject" "@ObservedObject" is used to subscribe to an external object that conforms to the "ObservableObject" protocol. When the object's "@Published" properties change, the view updates automatically. * **Do This:** Use "@ObservedObject" when the lifetime of the observable object is managed *outside* the view, such as when passed in from another view, or when the view needs to respond to shared global state. * **Don't Do This:** Don't create the observable object *inside* the view using "@ObservedObject", or you will recreate the object every time the view redraws. **Example:** """swift import SwiftUI import Combine class UserSettings: ObservableObject { @Published var username: String = "Guest" } struct UserProfileView: View { @ObservedObject var settings: UserSettings var body: some View { Text("Username: \(settings.username)") } } //Usage in another view: struct ContentView: View { @StateObject var userSettings = UserSettings() var body: some View { UserProfileView(settings: userSettings) } } """ * **Why:** "@ObservedObject" is used to represent externally managed mutable state. ### 3.2. Using "@StateObject" "@StateObject" is similar to "@ObservedObject", but it ensures that the observable object is created and managed *by* the view. The object persists across view updates. This is the right method when initializing the observable object within your view. * **Do This:** Use "@StateObject" to create and manage the lifecycle of an observable object within a specific view. Typically best when the object is private to the view. * **Don't Do This:** Don't use "@StateObject" to manage data that should be shared globally. Use "@EnvironmentObject" for that. **Example:** """swift import SwiftUI import Combine class Counter: ObservableObject { @Published var count: Int = 0 func increment() { count += 1 } } struct CounterView: View { @StateObject var counter = Counter() var body: some View { VStack { Text("Count: \(counter.count)") Button("Increment") { counter.increment() } } .padding() } } """ * **Why:** "@StateObject" prevents the observable object from being re-initialized when the view is redrawn, ensuring that the state persists correctly. ### 3.3. Publishing Data "@Published" is a property wrapper that signals changes to the observable object to which it belongs. SwiftUI views observing this object will automatically update when a "@Published" property changes. * **Do This:** Declare properties that SwiftUI views should observe with "@Published". * **Don't Do This:** Don't overuse "@Published" on properties that don't directly affect the view. This can lead to unnecessary redraws and hurt performance. **Example:** """swift import SwiftUI import Combine class DataModel: ObservableObject { @Published var data: String = "" func fetchData() { //Simulate fetching data DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.data = "Data fetched!" } } } struct DataView: View { @StateObject var dataModel = DataModel() var body: some View { Text(dataModel.data) .onAppear { dataModel.fetchData() } } } """ * **Why:** "@Published" ensures that the UI remains synchronized with the underlying data. ### 3.4. Anti-Pattern: Forgetting to Conform to "ObservableObject" * **Don't Do This**: Neglecting to make a class conform to the "ObservableObject" protocol when intending to use it with "@ObservedObject" or "@StateObject". * **Why**: SwiftUI won't be able to track changes within the class, resulting in the UI not updating as expected. ## 4. Environment Objects "@EnvironmentObject" facilitates sharing data across an entire view hierarchy without explicitly passing it down through each view. It works well for application settings, user preferences, and other global state. * **Do This:** Use "@EnvironmentObject" to inject globally accessible data into the view hierarchy. * **Don't Do This:** Don't pass data as an environment object if only a few views need it. Consider "ObservedObject" or "Binding" instead. **Example:** """swift import SwiftUI import Combine class AppSettings: ObservableObject { @Published var theme: String = "Light" } struct ThemeView: View { @EnvironmentObject var settings: AppSettings var body: some View { Text("Current Theme: \(settings.theme)") } } struct ContentView: View { @StateObject var appSettings = AppSettings() var body: some View { ThemeView() .environmentObject(appSettings) } } """ * **Why:** "@EnvironmentObject" avoids prop drilling, creating more modular and maintainable code. But only use it for truly global state. ### 4.1. Supplying the Environment Object It is critical that an "EnvironmentObject" is injected into the view hierarchy *above* where it is used. Failing to supply the "EnvironmentObject" will cause a runtime crash! * **Do This:** Ensure any view requiring an "@EnvironmentObject" has the object provided using the ".environmentObject()" modifier on a parent view. * **Don't Do This:** Forget to inject the environment object. This results in a runtime error. ### 4.2. Anti-Pattern: Overuse of Environment Objects * **Don't Do This**: Using "@EnvironmentObject" for localized state only used by one or two views. * **Why**: Overusing "@EnvironmentObject" makes data flow less explicit and can harm performance due to unnecessary updates across the view hierarchy. It is important to reserve its usage for data truly used throughout the application. ## 5. AppStorage "@AppStorage" provides a simple way to persist data to "UserDefaults", allowing data to be saved between app launches. * **Do This:** Use "@AppStorage" for simple settings or user preferences that need to persist. * **Don't Do This:** Don't store large or complex data structures in "AppStorage" due to its limitations and performance implications. Consider Core Data or other persistence solutions for complex data. **Example:** """swift import SwiftUI struct SettingsView: View { @AppStorage("isDarkMode") private var isDarkMode: Bool = false var body: some View { Toggle(isOn: $isDarkMode) { Text("Dark Mode") } .padding() } } """ * **Why:** "@AppStorage" simplifies persistence for basic data. ### 5.1 Considerations for Data Types * **Do This:** Ensure the data types you use with "@AppStorage" are compatible with "UserDefaults". Common types like "Bool", "Int", "Double", "String", "URL", and "Data" are appropriate. For custom types, consider encoding and decoding them as "Data" using "Codable". * **Don't Do This:** Try to directly store complex data types in "@AppStorage" without proper encoding, or store very large data, particularly when using value types that are copied on write. ## 6. Data Flow Patterns SwiftUI embraces unidirectional data flow. State changes trigger view updates, but views should not directly modify external state except through bindings or predefined APIs. ### 6.1. Unidirectional Data Flow * **Do This:** Ensure that data flows in a single direction: from the source of truth (e.g., observable object) to the views. Views can signal changes through callbacks or bindings, but the source of truth remains in control. * **Don't Do This:** Don't allow views to directly modify the state of unrelated views. Use bindings, observable objects, or environment objects to manage data flow. ### 6.2 State Restoration * **Do This:** When the app returns to the foreground, restore the app to the state when the app was moved to the background. * **Don't Do This:** If the app did not implement state restoration, the user may start back at the beginning. **Example:** """swift import SwiftUI struct ContentView: View { @State private var counter: Int = 0 var body: some View { VStack { Text("Counter: \(counter)") Button("Increment") { counter += 1 } } .onAppear { // Load saved state if let savedCounter = UserDefaults.standard.value(forKey: "counter") as? Int { counter = savedCounter } } .onDisappear { // Save state UserDefaults.standard.set(counter, forKey: "counter") } .padding() } } """ * **Why:** Unidirectional data flow enhances predictability and simplifies debugging since changes originate from a single, well-defined location. ## 7. Combining State Management Techniques Many SwiftUI applications require a mix of state management techniques to handle different scenarios and complexities. It is important to choose each technique wisely. ### 7.1. Example: Using "@State", "@ObservedObject", and "@EnvironmentObject" """swift import SwiftUI import Combine // Global app settings class AppSettings: ObservableObject { @Published var theme: String = "Light" } // Data model for a specific view class ItemViewModel: ObservableObject { @Published var isSelected: Bool = false let itemName: String init(itemName: String) { self.itemName = itemName } } struct ItemView: View { @ObservedObject var item: ItemViewModel @State private var quantity: Int = 1 var body: some View { HStack { Text("\(item.itemName) (Quantity: \(quantity))") Spacer() Button(action: { quantity += 1 }) { Image(systemName: "plus") } Toggle(isOn: $item.isSelected) { Text("Select") } } .padding() } } struct ContentView: View { @EnvironmentObject var appSettings: AppSettings @StateObject var settings = AppSettings() @State private var numberOfItems: Int = 5 let items = [ItemViewModel(itemName: "Item 1"), ItemViewModel(itemName: "Item 2"), ItemViewModel(itemName: "Item 3")] var body: some View { VStack { Text("Theme: \(appSettings.theme)") TextField("Number of Items", value: $numberOfItems, format: .number) .padding() ForEach(items) { item in ItemView(item: item) } } .padding() } } @main struct MyApp: App { @StateObject var appSettings = AppSettings() var body: some Scene { WindowGroup { ContentView() .environmentObject(appSettings) } } } """ In this example: * "AppSettings" is shared globally as an "@EnvironmentObject". * "ItemViewModel" is a view-specific data model observed using "@ObservedObject". * "quantity" within "ItemView" is managed locally using "@State". "ContentView" uses the "@EnvironmentObject" to display theme info. ### 7.2 When to use what? In summary, here’s a quick guide to choosing the right state management tool in SwiftUI: * **Use "@State"** for: Simple, localized state (simple types) in a view. It’s the easiest way to manage basic UI state (e.g., managing whether a button is toggled). * **Use "@Binding"** for: Sharing state and data between parent and child views (especially for two-way data flow). Commonly used for passing data to custom controls or UI components. * **Use "@StateObject"** for: Managing the lifecycle and state of an entire class instance (particularly when you create an instance inside a view). The state persists the lifetime of a view instance. Useful when you want to encapsulate an object graph within a view. *Only initialize the model once.* * **Use "@ObservedObject"** for: Observing state in external objects (i.e., objects you did *not* initialize in the view). It’s used when an external source owns the data, such as another view or a parent object. The state updates when the observed object changes but *does not* control the object's creation and lifecycle. * **Use "@EnvironmentObject"** for: Sharing data between views without prop drilling. It’s useful when a particular object or model should be accessible virtually anywhere in a view. ## 8. Performance Considerations Efficient state management is crucial for SwiftUI performance. Frequent, unnecessary view updates can lead to sluggish UI. * **Standard:** Minimize unnecessary view updates by ensuring that only the relevant parts of the UI are redrawn when state changes. * **Why:** Improves app responsiveness and battery life. ### 8.1. "Equatable" Conformance * **Do This:** Make sure your data models conform to the "Equatable" protocol when appropriate. SwiftUI can use this to avoid redrawing views when the underlying data hasn't actually changed. Also consider using "==" to check if the data is equal. * **Don't Do This:** Avoid relying solely on SwiftUI's identity comparison, as it can sometimes lead to unnecessary view updates. ### 8.3 "onChange" Modifier (SwiftUI 14 and later) * **Do This:** Use the ".onChange" modifier in SwiftUI 14 to respond to changes in specific state variables and perform specific actions. * **Don't Do This:** Redraw the whole view body when changes arise, leading to performance issues. **Example** """swift import SwiftUI struct MyView: View { @State private var textValue: String = "" var body: some View { TextField("Enter text:", text: $textValue) .onChange(of: textValue) { newValue in print("Text changed to: \(newValue)") // Actions here only executed when textValue changes! } } } """ ### 8.4. "withAnimation" * **Do This**: Use "withAnimation" when you want to animate the change of a SwiftUI View. * **Don't Do This**: Leaving out "withAnimation" will make it a difficult user experience. **Example** """swift import SwiftUI struct RectangleExample: View { @State private var changeRectangle: Bool = false var body: some View { RoundedRectangle(cornerRadius: changeRectangle ? 20 : 10) .fill(changeRectangle ? .red : .blue) .frame(width: changeRectangle ? 200 : 100, height: changeRectangle ? 300: 100) .rotationEffect(Angle(degrees: changeRectangle ? 360 : 0)) .animation(.easeInOut, value: changeRectangle) // Older method .onTapGesture { withAnimation(.spring()) { changeRectangle.toggle() } } } } """ * **Why:** The "withAnimation" modifier allows for smooth transitions instead of jarring ones. ## 9. Testing Testing is crucial for verifying the correctness and reliability of SwiftUI applications. * **Standard:** Write unit tests that validate state changes occur as expected and UI updates correctly in response to those changes. Use SwiftUI previews for visual validation. * **Why:** Ensures the application behaves predictably and reduces the likelihood of bugs. Especially important for state management, where improper handling can lead to many hard-to-debug bugs. ### 9.1. Unit Testing Observable Objects * **Do This:** Mock dependencies and external data sources when testing observable objects to ensure that the tests are isolated and deterministic. * **Don't Do This:** Rely on live data or external services in unit tests, as this can lead to flaky tests that are difficult to reproduce. ## 10. Security Considerations While not always directly related to state management, security should be considered when handling user-sensitive data. * **Standard:** Avoid storing sensitive information in "@AppStorage" or other persistent storage mechanisms without proper encryption. Protect user data by following security best practices. * **Why:** Prevents unauthorized access to sensitive information. ## 11. Conclusion Effective state management is essential for building robust and maintainable SwiftUI applications. By following these coding standards, developers can create code that is easier to understand, debug, and scale. SwiftUI continues to evolve, so it's important to stay up-to-date with the latest best practices and techniques.
# Performance Optimization Standards for SwiftUI This document outlines coding standards focused solely on performance optimization in SwiftUI applications. These standards aim to improve application speed, responsiveness, and resource usage by leveraging the latest SwiftUI features and best practices. This document will be used as context for AI coding assistants. ## 1. Data Management and State Updates Efficient data management and state updates are crucial for SwiftUI performance. Unnecessary updates trigger view re-renders, leading to performance bottlenecks. ### 1.1. Use "@State", "@Binding", "@ObservedObject", "@StateObject", and "@EnvironmentObject" Appropriately * **Standard:** Choose the correct state management property wrapper based on the scope and lifetime of the data. * **Why:** Incorrect usage leads to unnecessary view updates and potential memory leaks. * **Do This:** * Use "@State" for simple, single-view state. * Use "@Binding" to create a two-way connection to a state owned higher up in the view hierarchy. * Use "@ObservedObject" for external, mutable reference type objects that are already created elsewhere. * Use "@StateObject" for creating and managing the lifecycle of external, mutable reference type objects tied to the view's lifetime. Avoid re-creation when SwiftUI re-renders a "View". * Use "@EnvironmentObject" inject shared data into the view hierarchy. * **Don't Do This:** * Use "@ObservedObject" for creating and managing the lifecycle of an object – prefer "@StateObject". * Pass large data structures directly as "@State" or "@Binding" unless strictly necessary. Consider "EnvironmentObject" or "ObservedObject" (while managing the object instantiation carefully) for shared data that is expensive to copy. * **Code Example:** """swift import SwiftUI class UserSettings: ObservableObject { @Published var volume: Double = 0.5 } struct ContentView: View { @StateObject var settings = UserSettings() var body: some View { VStack { Slider(value: $settings.volume, in: 0...1) // Binding to settings property DetailView() } .environmentObject(settings) // Inject settings into the environment } } struct DetailView: View { @EnvironmentObject var settings: UserSettings // Consume settings from the environment var body: some View { Text("Volume: \(settings.volume)") } } """ ### 1.2. Minimize View Updates with "Equatable" and "Identifiable" * **Standard:** Make data models "Equatable" and use "Identifiable" for collections in "ForEach" loops. * **Why:** SwiftUI compares data before re-rendering. Implementing these protocols allows SwiftUI to optimize updates by only re-rendering views based on actual changes. * **Do This:** """swift import SwiftUI struct Task: Identifiable, Equatable { let id = UUID() var title: String var isCompleted: Bool static func == (lhs: Task, rhs: Task) -> Bool { return lhs.id == rhs.id && lhs.title == rhs.title && lhs.isCompleted == rhs.isCompleted } } struct TaskListView: View { @State var tasks: [Task] = [ Task(title: "Buy groceries", isCompleted: false), Task(title: "Walk the dog", isCompleted: true) ] var body: some View { List { ForEach(tasks) { task in TaskRow(task: task) .id(task.id) // Explicit ID assignment for ForEach optimization } } } } struct TaskRow: View { let task: Task var body: some View { HStack { Text(task.title) Spacer() Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle") } } } """ * **Don't Do This:** * Omit "Equatable" conformance for data models, forcing SwiftUI to assume all changes trigger re-renders. * Use "ForEach(0..<tasks.count)" without "id: \.self" and no explicit id assignment in the displayed view. This forces list views to refresh all items with any change. ### 1.3. Use "onChange" Correctly * **Standard:** Use the "onChange" modifier to react efficiently to state changes. * **Why:** "onChange" provides a controlled way to execute code only when a specific value changes. * **Do This:** """swift import SwiftUI struct ContentView: View { @State private var text: String = "" @State private var processedText: String = "" var body: some View { VStack { TextField("Enter text", text: $text) .padding() Text("Processed Text: \(processedText)") .padding() } .onChange(of: text) { newValue in // Perform an expensive operation only when 'text' changes processedText = expensiveTextProcessing(text: newValue) } } func expensiveTextProcessing(text: String) -> String { // Simulate an expensive operation Thread.sleep(forTimeInterval: 0.5) return text.uppercased() } } """ * **Don't Do This:** * Perform operations directly within the "body" that should have been done using ".onChange". This will rerun any time the view is re-rendered even if the data wasn't updated. * Abuse "onChange" by performing computationally expensive operations that could be handled on a background thread. ### 1.4. Debouncing State Updates * **Standard:** Use debouncing for state updates from rapidly changing inputs (e.g., search bars). * **Why:** Minimizes the number of state update calls, reducing view re-renders. * **Do This:** """swift import SwiftUI import Combine class SearchViewModel: ObservableObject { @Published var searchText: String = "" @Published var results: [String] = [] private var cancellables: Set<AnyCancellable> = [] init() { $searchText .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main) .removeDuplicates() .sink { [weak self] term in self?.performSearch(term: term) } .store(in: &cancellables) } func performSearch(term: String) { // Simulate network request or data processing DispatchQueue.global().async { Thread.sleep(forTimeInterval: 0.5) let fakeResults = (1...5).map { "\(term)-\($0)" } DispatchQueue.main.async { self.results = fakeResults } } } } struct SearchView: View { @StateObject var viewModel = SearchViewModel() var body: some View { VStack { TextField("Search", text: $viewModel.searchText) .padding() List(viewModel.results, id: \.self) { result in Text(result) } } } } """ * **Don't Do This:** Directly make network calls or complex operations from TextField input changes without debouncing. UI will lag due to rate limiting. ## 2. View Composition and Rendering Optimizing how views are composed and rendered significantly impacts performance. ### 2.1. Use "Group" and "ViewBuilder" to Reduce View Hierarchy Depth * **Standard:** Use "Group" and "@ViewBuilder" to flatten view hierarchies without introducing extra overhead. * **Why:** Deep view hierarchies impact rendering performance. These constructs help structure code while minimizing the impact on the view tree. * **Do This:** """swift import SwiftUI struct ComplexView: View { var body: some View { VStack { HeaderView() ContentView() FooterView() } } } struct HeaderView: View { var body: some View { Text("Header") .font(.largeTitle) .padding() } } struct ContentView: View { var body: some View { VStack { // Wrapping content to satisfy "one view" requirement Text("Content Part 1") Text("Content Part 2") } .padding() } } struct FooterView: View { var body: some View { Text("Footer") .font(.footnote) .padding() } } """ * **Don't Do This:** Over-nest views unnecessarily, creating deeply nested hierarchies that can be avoided with grouping or ViewBuilder. ### 2.2. Avoid Complex Logic in "body" * **Standard:** Move complex logic and computations out of the "body" property into separate functions or computed properties. * **Why:** The "body" should primarily describe the view structure. Complex logic makes the "body" harder to read and can lead to performance issues when the view is re-rendered. * **Do This:** """swift import SwiftUI struct ProductView: View { let product: Product var formattedPrice: String { return String(format: "$%.2f", product.price) // Move formatting logic outside body } var body: some View { VStack { Text(product.name) .font(.title) Text(formattedPrice) .font(.headline) } } } struct Product { let name: String let price: Double } """ * **Don't Do This:** Perform complex calculations directly within the "body". This will severely impact performance as it will run frequently. ### 2.3. Use "LazyVStack" and "LazyHStack" for Large Lists * **Standard:** Employ "LazyVStack" and "LazyHStack" for scrollable lists with many items. * **Why:** These lazy stacks load views only as they come into view, improving initial load time and memory usage for very large datasets. * **Do This:** """swift import SwiftUI struct ItemListView: View { let items = Array(1...1000).map { "Item \($0)" } // Simulate a large dataset var body: some View { ScrollView { LazyVStack { ForEach(items, id: \.self) { item in Text(item) .padding() Divider() } } } } } """ * **Don't Do This:** Use "VStack" or "HStack" for large lists, as they load all views at once, degrading performance. ### 2.4. Utilize "redacted(reason:)" and "shimmering()" for Placeholder UI * **Standard:** Employ the "redacted(reason:)" and custom "shimmering()" effect modifiers to create placeholder UI while content loads. * **Why:** Improves the user experience when loading data by indicating to the user that the data is on its way instead of showing a blank page. * **Do This:** """swift import SwiftUI struct RedactedView: View { @State private var isLoading = true var body: some View { VStack { Text("This is redacted while loading") .redacted(reason: isLoading ? .placeholder : []) Text("And this will Shimmer") .shimmering() Button("Toggle Loading") { isLoading.toggle() } } } } extension View { func shimmering(duration: Double = 1.5) -> some View { modifier(Shimmer(duration: duration)) } } struct Shimmer: ViewModifier { let duration: Double @State private var shimmering = false func body(content: Content) -> some View { content .opacity(shimmering ? 0.5 : 1) .animation(Animation.linear(duration: duration).repeatForever(autoreverses: true), value: shimmering) .onAppear { shimmering = true } } } """ * **Don't Do This:** Show a blank screen without any indication of loading while fetching data. ## 3. Image Handling Efficient image handling is essential for preventing memory issues and improving load times. ### 3.1. Use "AsyncImage" for Remote Images * **Standard:** Use "AsyncImage" for fetching and displaying remote images. * **Why:** "AsyncImage" handles image loading asynchronously and provides built-in caching, reducing the need for manual image loading and caching logic and avoiding blocking the main thread. * **Do This:** """swift import SwiftUI struct RemoteImageView: View { let url: URL var body: some View { AsyncImage(url: url) { phase in switch phase { case .empty: ProgressView() case .success(let image): image .resizable() .scaledToFit() case .failure: Image(systemName: "xmark.circle.fill") .foregroundColor(.red) @unknown default: EmptyView() } } } } """ * **Don't Do This:** Manually load images using "UIImage(data:)" on the main thread, blocking the UI, or implement custom caching solutions when "AsyncImage" provides these features. ### 3.2. Optimize Image Sizes * **Standard:** Resize images to the appropriate dimensions before displaying them. * **Why:** Displaying large images at smaller sizes wastes memory and processing power. Right-sizing your images for display results in better performance and memory profile for free.. * **Do This:** Use asset catalogs to provide different image resolutions for different devices. Alternatively, use code to resize images. * **Example using ".resizable()" and ".scaledToFit()" """swift import SwiftUI struct OptimizedImageView: View { let imageName: String var body: some View { Image(imageName) .resizable() .scaledToFit() .frame(width: 100, height: 100) // Adjust dimensions as needed } } """ ### 3.3. Image Caching Strategies * **Standard:** Implement a caching strategy for frequently accessed images to avoid repeated downloads. * **Why:** Reduces network overhead and improves the user experience by providing faster image loading. "AsyncImage" has default caching. For more control, you can use a custom caching solution. * **Example using URLCache:** """swift import SwiftUI class ImageCache { private let cache = URLCache.shared func cachedImage(url: URL) -> UIImage? { let request = URLRequest(url: url) if let cachedResponse = cache.cachedResponse(for: request) { return UIImage(data: cachedResponse.data) } return nil } func cacheImage(url: URL, image: UIImage) { guard let data = image.jpegData(compressionQuality: 0.8) else { return } let response = URLResponse(url: url, mimeType: "image/jpeg", expectedContentLength: data.count, textEncodingName: nil) let cachedResponse = CachedURLResponse(response: response, data: data) let request = URLRequest(url: url) cache.storeCachedResponse(cachedResponse, for: request) } } """ ## 4. Background Tasks and Concurrency Offloading long-running tasks to background threads is crucial for maintaining UI responsiveness. ### 4.1. Use "DispatchQueue" for Background Tasks * **Standard:** Perform network requests, data processing, and other long-running tasks on background threads using "DispatchQueue". * **Why:** Prevents blocking the main thread, ensuring the UI remains responsive. * **Do This:** """swift import SwiftUI struct ContentView: View { @State private var data: String = "Loading..." var body: some View { Text(data) .onAppear { loadData() } } func loadData() { DispatchQueue.global().async { // Simulate a long-running task Thread.sleep(forTimeInterval: 2) let loadedData = "Data loaded from background" DispatchQueue.main.async { data = loadedData // Update UI on the main thread } } } } """ * **Don't Do This:** Perform time intensive operations on the main thread directly. This will freeze the UI. ### 4.2. Leverage "async" / "await" * **Standard:** Employ Swift's "async" and "await" for asynchronous operations to simplify asynchronous code. * **Why:** Results in more readable and maintainable asynchronous code than traditional completion handlers. * **Do This:** """swift import SwiftUI struct ContentView: View { @State private var data: String = "Loading..." var body: some View { Text(data) .onAppear { Task { await loadData() } } } func loadData() async { // Simulate a long-running task do { let loadedData = try await fetchData() data = loadedData // Update UI on the main thread } catch { data = "Error loading data" } } func fetchData() async throws -> String { try await Task.sleep(nanoseconds: 2_000_000_000) // Simulate async delay return "Data loaded asynchronously" } } """ * **Don't Do This:** Nest completion handlers excessively, leading to "callback hell." Favor "async" / "await". ### 4.3. Be Mindful of Thread Safety * **Standard:** Ensure thread safety when accessing and modifying shared data from multiple threads. * **Why:** Prevents race conditions and data corruption, ensuring data consistency. * **Do This:** Use "DispatchQueue.main.async" to update UI elements. Use actors or locks for data mutation. * **Example using an Actor:** """swift import SwiftUI actor Counter { private var count = 0 func increment() -> Int { count += 1 return count } func getCount() -> Int { return count } } struct ContentView: View { @State private var count: Int = 0 let counter = Counter() var body: some View { Text("Count: \(count)") .onAppear { Task { for _ in 1...1000 { let newCount = await counter.increment() // Await actor function DispatchQueue.main.async { count = newCount } } } } } } """ * **Don't Do This:** Directly modify UI elements or shared data from background threads without proper synchronization. ## 5. Performance Profiling and Debugging Regular profiling and debugging are essential for identifying and resolving performance bottlenecks. ### 5.1. Use Instruments for Performance Analysis * **Standard:** Use Instruments to profile the application and identify performance bottlenecks. * **Why:** Instruments provides detailed insights into CPU usage, memory allocation, and drawing performance. * **Do This:** * Use the Time Profiler to identify CPU-intensive tasks. * Use the Allocations instrument to track memory usage and identify memory leaks. * Use the Core Animation instrument to analyze rendering performance and identify drawing bottlenecks. * **Don't Do This:** Neglect Instruments and rely solely on guesswork for performance optimization. ### 5.2. Use SwiftUI Previews Efficiently * **Standard:** Use SwiftUI previews for rapid iteration but limit the scope of previews to individual components. * **Why:** Overly complex previews slow down the development process. * **Do This:** * Create focused previews for individual views. * Use conditional compilation ("#if DEBUG") to include mock data or simplified logic in previews. * **Don't Do This:** Create all-encompassing previews that attempt to render the entire application. ### 5.3. Implement Logging and Monitoring * **Standard:** Add logging and monitoring to track performance metrics in production. * **Why:** Provides insights into performance issues users are experiencing and helps prioritize optimization efforts. * **Do This:** * Use "os_log" for structured logging. * Integrate with analytics platforms to track performance metrics. * Implement custom performance monitors to track specific areas of the application. * **Don't Do This:** Rely solely on manual testing and neglect production performance monitoring. By adhering to these performance optimization standards, developers can create SwiftUI applications that are not only visually appealing but also performant and responsive, providing a better user experience.
# Security Best Practices Standards for SwiftUI This document outlines the security best practices for SwiftUI development. Adhering to these standards will help protect your application from common vulnerabilities and ensure a more secure user experience. ## 1. Data Handling and Storage ### 1.1 Secure Data Persistance **Standard:** Always use secure methods for storing sensitive data persistently. **Why:** Storing sensitive information like passwords or API keys in plain text or easily accessible formats poses a huge security risk. **Do This:** * Utilize the Keychain for storing sensitive data like passwords, tokens, and API keys. * Employ encryption for Core Data, Realm, or other database solutions which hold sensitive information locally. **Don't Do This:** * Store sensitive data directly in UserDefaults without encryption. * Hardcode API keys or other credentials directly in your SwiftUI code. **Code Examples:** """swift // Using Keychain for storing and retrieving a token import Security import Foundation enum KeychainError: Error { case itemNotFound case unexpectedStatus(OSStatus) case unknown } struct KeychainManager { static func save(key: String, value: String) throws { guard let valueData = value.data(using: .utf8) else { return } let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, kSecValueData as String: valueData, kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock ] let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecSuccess else { throw KeychainError.unexpectedStatus(status) } } static func retrieve(key: String) throws -> String { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne ] var result: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess else { if status == errSecItemNotFound { throw KeychainError.itemNotFound } else { throw KeychainError.unexpectedStatus(status) } } guard let resultData = result as? Data, let stringValue = String(data: resultData, encoding: .utf8) else { throw KeychainError.unknown } return stringValue } static func delete(key: String) throws { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key ] let status = SecItemDelete(query as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { throw KeychainError.unexpectedStatus(status) } } } // Example usage: Storing an API token do { try KeychainManager.save(key: "apiToken", value: "YOUR_API_TOKEN") let retrievedToken = try KeychainManager.retrieve(key: "apiToken") print("Retrieved Token: \(retrievedToken)") } catch { print("Keychain Error: \(error)") } """ **Anti-Pattern:** Incorrectly using UserDefaults: """swift // DON'T DO THIS! Insecure storage of sensitive information UserDefaults.standard.set("YOUR_API_KEY", forKey: "apiKey") // Insecure! """ ### 1.2 Input Validation and Sanitization **Standard:** Validate and sanitize all user inputs to prevent injection attacks and data corruption **Why:** User input is a common entry point for many security vulnerabilities. Proper validation helps ensure the user's data is formatted correctly and free of malicious characters. **Do This:** * Use data validation to verify the data type, length, and format of user inputs. * When dealing with text input, escape special characters that could be interpreted as code (like HTML or SQL). * Implement server-side validation to complement client-side validation. **Don't Do This:** * Trust all data received from the client. * Construct database queries or HTML directly from user input without sanitization. **Code Examples:** """swift // Validating email format import Foundation func isValidEmail(_ email: String) -> Bool { let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegex) return emailPredicate.evaluate(with: email) } struct ContentView: View { @State private var email: String = "" @State private var isValid: Bool = true var body: some View { VStack { TextField("Email Address", text: $email) .onChange(of: email) { newValue in isValid = isValidEmail(newValue) } .padding() if !isValid { Text("Invalid email format") .foregroundColor(.red) } Button("Submit") { // Process email if valid if isValid { print("Valid email: \(email)") } } .disabled(!isValid) } .padding() } } """ **Anti-Patterns:** """swift // DON'T DO THIS! No input validation let userInput = textField.text let query = "SELECT * FROM users WHERE username = '\(userInput!)'" // Vulnerable to SQL injection """ ### 1.3 Secure Communication **Standard:** Ensure network communication is encrypted, especially when transmitting sensitive data. **Why:** Transmitting data in plain text over the network is a severe security risk, as eavesdroppers can easily intercept the data. **Do This:** * Use HTTPS for all network requests to encrypt the communication channel. * Implement SSL pinning to prevent man-in-the-middle attacks. * Avoid sending sensitive information in query parameters (use POST requests instead). **Don't Do This:** * Use HTTP for transmitting sensitive data. * Disable SSL certificate validation in production. **Code Examples:** """swift // Making a secure HTTPS request using URLSession struct NetworkManager { func fetchData(from urlString: String) async throws -> Data { guard let url = URL(string: urlString) else { throw URLError(.badURL) } var request = URLRequest(url: url) request.httpMethod = "GET" //Or POST //Add headers if needed request.addValue("application/json", forHTTPHeaderField: "Content-Type") let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { throw URLError(.badServerResponse) } return data } } // Usage in SwiftUI View struct SecureView: View { @State private var data: String = "Loading..." var body: some View { Text(data) .task { do { let fetchedData = try await NetworkManager().fetchData(from: "https://api.example.com/secure-data") // USE HTTPS! self.data = String(data: fetchedData, encoding: .utf8) ?? "Data Error" } catch { self.data = "Error fetching data: \(error)" } } } } """ **Anti-Patterns:** """swift // DON'T DO THIS! Using HTTP for sensitive data. Vulnerable to man-in-the-middle attacks let url = URL(string: "http://api.example.com/login") //Insecure! """ ## 2. Authentication & Authorization ### 2.1 Secure Authentication Methods **Standard:** Implement strong, secure methods for authenticating users. **Why:** Weak authentication leaves user accounts vulnerable to compromise. **Do This:** * Use multi-factor authentication (MFA) whenever possible. * Store passwords securely using strong hashing algorithms (bcrypt or Argon2). * Consider using biometric authentication (Face ID, Touch ID) for convenience and security. **Don't Do This:** * Store passwords in plain text or with weak hashing algorithms (MD5, SHA1). * Allow users to select weak or easily guessable passwords. **Code Example:** """swift // Using CryptoKit to hash a password import CryptoKit import Foundation struct PasswordManager { static func hashPassword(password: String) -> String? { guard let data = password.data(using: .utf8) else { return nil } let salt = generateSalt() let saltedPassword = salt + data let hashed = SHA256.hash(data: saltedPassword) let hashedString = Data(hashed).base64EncodedString() return hashedString // For production, use secure algorithms like Argon2 or bcrypt via a server. } static func generateSalt() -> Data { var keyData = Data(count: 32) let result = keyData.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!) } if result == errSecSuccess { return keyData } else { return Data() //Handle appropriately in real code } } } // Example usage: Hashing a password if let hashedPassword = PasswordManager.hashPassword(password: "mySecretPassword") { print("Hashed Password: \(hashedPassword)") // Store the hashed password securely, along with salt (important for more robust algorithms)! } else { print("Password hashing failed.") } """ **Anti-Patterns:** """swift // DON'T DO THIS! Storing passwords in plaintext or using weak hashing algorithms. let passwordHash = password.md5() // Insufficient for secure password storage """ ### 2.2 Authorization Controls **Standard:** Implement authorization controls to restrict access to sensitive resources based on user roles or permissions. **Why:** Ensures that users can only access data and functionalities they are permitted to use. Without proper authorization, users could potentially access or modify data they shouldn't. **Do This:** * Define clear roles and permissions for different user types. * Implement role-based access control (RBAC) to restrict access to sensitive data and functionalities. * Regularly review and update access controls as needed. **Don't Do This:** * Grant excessive permissions to users. * Bypass authorization checks on the client-side without server-side validation. **Code Example:** """swift // Role-Based Access Control Example enum UserRole { case admin case editor case viewer } // Dummy function to get user role func getUserRole() -> UserRole { // In real implementation, fetch role from secure storage or server. return .editor } struct AdminView: View { var body: some View { Text("Admin View - Full Access") } } struct EditorView: View { var body: some View { Text("Editor View - Limited Access") } } struct ViewerlView: View { var body: some View { Text("Viewer View - Read-Only Access") } } struct ContentView: View { var body: some View { Group { switch getUserRole() { case .admin: AdminView() case .editor: EditorView() case .viewer: ViewerlView() } } } } """ **Anti-Patterns:** """swift // DON'T DO THIS! Client-side authorization only, easily bypassed. if isLoggedIn { // Show sensitive data (authorization logic should happen on the server!) } """ ## 3. App Transport Security (ATS) and Network Configuration ### 3.1 Enforce ATS Compliance **Standard:** Ensure your app is compliant with App Transport Security (ATS) requirements. **Why:** ATS enforces best practices for secure network connections, ensuring that your app only communicates with servers using HTTPS and modern TLS protocols. **Do This:** * Enable ATS in your app's Info.plist file. * Ensure that all your app's external connections use HTTPS. * Review and address any ATS exceptions to minimize security risks. **Don't Do This:** * Disable ATS altogether without a valid reason. * Ignore warnings about insecure network connections. **Implementation Details:** ATS is enabled by default. If your app needs to connect to servers that do not support HTTPS or modern TLS, you can configure exceptions in your app's Info.plist file. However, aim to avoid exceptions as much as possible and migrate to HTTPS endpoints. ### 3.2 Secure WebView Configuration **Standard:** Securely configure WebViews to mitigate cross-site scripting (XSS) and other vulnerabilities. **Why:** WebViews can introduce security risks if not properly configured, as they can load arbitrary web content and execute JavaScript code. **Do This:** * Disable JavaScript execution in WebViews unless absolutely necessary. * Implement strict content security policies (CSP) to restrict the sources of content loaded in WebViews. * Sanitize and validate all data passed to and from WebViews. **Don't Do This:** * Load untrusted or user-generated content directly in WebViews without proper sanitization. * Enable JavaScript execution in WebViews without a strong security policy. **Code Example:** """swift import SwiftUI import WebKit struct WebView: UIViewRepresentable { let url: URL func makeUIView(context: Context) -> WKWebView { let webView = WKWebView() //disable JavaScript webView.configuration.preferences.javaScriptEnabled = false; return webView } func updateUIView(_ webView: WKWebView, context: Context) { let request = URLRequest(url: url) webView.load(request) } } struct WebViewExampleView: View { var body: some View { WebView(url: URL(string:"https://example.com")!) //Prefer secure URLs } } """ ## 4. Data Privacy ### 4.1 Respect User Privacy **Standard**: Minimize data collection, be transparent about data usage, and respect user privacy preferences. **Why**: Building trust with users is essential and compliance with privacy regulations (e.g., GDPR, CCPA) is mandatory. **Do This**: * Only collect data that is essential for the app's functionality. * Obtain explicit consent from users before collecting or using their personal data. * Provide users with clear and accessible privacy policies. * Allow users to access, modify, or delete their data. **Don't Do This**: * Collect data without user consent. * Share user data with third parties without explicit authorization. * Retain user data for longer than necessary. ### 4.2 Handle Personally Identifiable Information (PII) Carefully **Standard**: Implement extra security measures when handling PII. **Why**: PII (e.g., name, address, phone number) can be misused if compromised, leading to identity theft or harm to users. **Do This**: * Encrypt PII both in transit and at rest. * Implement access controls to restrict access to PII. * Anonymize or pseudonymize PII whenever possible. * Comply with all relevant data privacy regulations. **Don't Do This**: * Store PII in plain text. * Share PII with unauthorized parties. * Use PII for purposes other than those for which it was collected without explicit consent. ## 5. General Security Practices ### 5.1 Regular Security Audits and Penetration Testing **Standard:** Schedule regular security audits and penetration testing to identify vulnerabilities. **Why:** Proactive security assessments can reveal hidden weaknesses in your app's code and infrastructure. **Do This:** * Conduct regular code reviews to identify potential security flaws. * Perform penetration testing to simulate real-world attacks and assess your app's resilience. * Engage external security experts for independent security assessments. **Anti-patterns:** Neglecting security audits and penetration testing. ### 5.2 Keep Dependencies Up-to-Date **Standard:** Regularly update dependencies to patch security vulnerabilities. **Why:** Vulnerabilities are often discovered in third-party libraries and frameworks. Keeping dependencies up-to-date helps mitigate these risks. **Do This:** * Use dependency management tools to track and update dependencies. * Subscribe to security advisories to stay informed about known vulnerabilities in your dependencies. * Test updated dependencies thoroughly to ensure compatibility and stability. ### 5.3 Error Handling and Logging **Standard:** Implement robust error handling and logging mechanisms to detect and respond to security incidents. **Why:** Proper error handling and logging helps you identify and troubleshoot security issues, as well as track suspicious activity. **Do This:** * Log security-related events (e.g., login attempts, access control violations) to a secure location. * Implement error handling to prevent sensitive information from being exposed in error messages. * Monitor logs for suspicious activity and investigate promptly. **Don't Do This:** * Log sensitive information (e.g., passwords, API keys) in plain text. * Expose detailed error messages to users in production. ### 5.4 Code Obfuscation **Standard:** Obfuscate your code to make it harder for attackers to reverse engineer your app. **Why:** While not a foolproof solution, code obfuscation can raise the bar for attackers by making it more difficult to understand and exploit your app's code. **Do This:** * Use code obfuscation tools to rename classes, methods, and variables. * Apply string encryption to protect sensitive strings from being easily discovered. It is important to remember that code obfuscation is not a substitute for other security measures, such as input validation and secure data storage. By adhering to these security best practices, you can significantly reduce the risk of vulnerabilities and ensure a more secure experience for your SwiftUI app users. Remember to continuously review and update your security practices as new threats and technologies emerge.
# Core Architecture Standards for SwiftUI This document outlines the core architectural standards for developing robust, maintainable, and scalable SwiftUI applications. It emphasizes modern SwiftUI practices and aims to provide clear guidance for developers of all levels. ## 1. Architectural Patterns Choosing the right architectural pattern is crucial for organizing your code and managing complexity. SwiftUI inherently encourages a declarative, data-driven approach. ### 1.1 Model-View-ViewModel (MVVM) * **Standard:** Adopt MVVM as the primary architectural pattern for most SwiftUI projects. * **Do This:** Separate data (Model), UI logic (ViewModel), and the UI representation (View). * **Don't Do This:** Place business logic directly within the View. Avoid tight coupling between View and Model. **Why:** MVVM promotes testability, reusability, and separation of concerns, resulting in a more maintainable codebase. It aligns well with SwiftUI's declarative nature. **Example:** """swift // Model struct User { let id: UUID = UUID() let name: String let email: String } // ViewModel import SwiftUI class UserViewModel: ObservableObject { @Published var users: [User] = [] @Published var isLoading = false @Published var errorMessage: String? = nil init() { loadUsers() } func loadUsers() { isLoading = true errorMessage = nil // Reset error message // Simulate network request DispatchQueue.global().async { do { // Simulate network delay Thread.sleep(forTimeInterval: 1) // Simulate fetching data let fetchedUsers = [ User(name: "Alice Smith", email: "alice.smith@example.com"), User(name: "Bob Johnson", email: "bob.johnson@example.com") ] DispatchQueue.main.async { self.users = fetchedUsers self.isLoading = false } } catch { DispatchQueue.main.async { self.errorMessage = "Failed to load users: \(error.localizedDescription)" self.isLoading = false } } } } } // View struct UserListView: View { @ObservedObject var viewModel = UserViewModel() var body: some View { VStack { if viewModel.isLoading { ProgressView("Loading Users...") } else if let errorMessage = viewModel.errorMessage { Text("Error: \(errorMessage)") .foregroundColor(.red) } else { List(viewModel.users, id: \.id) { user in Text(user.name) } } } .padding() } } struct UserListView_Previews: PreviewProvider { static var previews: some View { UserListView() } } """ **Anti-Pattern:** Storing complex data transformation logic directly in the View's "body". ### 1.2 Coordinator Pattern * **Standard:** Use the Coordinator pattern for managing navigation, especially in complex applications with multiple screens and deep-linking requirements. * **Do This:** Create Coordinator objects responsible for instantiating and presenting ViewControllers or other Views. * **Don't Do This:** Handle navigation directly within Views, leading to tight coupling and difficulty in testing navigation flows. **Why:** The Coordinator pattern centralizes navigation logic, making it easier to manage complex navigation flows, support deep linking, and test navigation independently of views. While less common in pure SwiftUI, it's crucial when integrating UIKit or managing complex route logic. **Example (UIKit example):** While Coordinators are traditionally a UIKit pattern, using a "UIViewControllerRepresentable" can bridge the gap with SwiftUI, but this is generally discouraged unless absolutely unavoidable. """swift // Coordinator (For UIKit navigation - use sparingly in SwiftUI) import UIKit import SwiftUI class MainCoordinator: Coordinator { var navigationController: UINavigationController init(navigationController: UINavigationController) { self.navigationController = navigationController } func start() { let viewController = UIHostingController(rootView: HomeView(coordinator: self)) navigationController.pushViewController(viewController, animated: false) } func showDetails(for user: User) { // Assuming User is your model let detailViewController = UIHostingController(rootView: DetailView(user: user)) navigationController.pushViewController(detailViewController, animated: true) } } protocol Coordinator { var navigationController: UINavigationController { get set } func start() } // Example SwiftUI View Utilizing the Coordinator struct HomeView: View { var coordinator: MainCoordinator // Store a reference to the associated Coordinator var body: some View { Button("Show Details") { // Assume the button is interacting with a dummy user let dummyUser = User(name: "John Doe", email: "John.doe@email.com") coordinator.showDetails(for: dummyUser) } .navigationTitle("Home") } } struct DetailView: View { let user: User var body: some View { Text("Details for \(user.name)") .navigationTitle("Details") } } // Wrapping within the UIViewControllerRepresentable. struct CoordinatorView: UIViewControllerRepresentable { let coordinator: MainCoordinator func makeUIViewController(context: Context) -> UINavigationController { return coordinator.navigationController } func updateUIViewController(_ uiViewController: UINavigationController, context: Context) { } } // Entry Point (App) @main struct CoordinatorExampleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { CoordinatorView(coordinator: appDelegate.coordinator) } } } class AppDelegate: NSObject, UIApplicationDelegate { var coordinator: MainCoordinator = { let navController = UINavigationController() let coordinator = MainCoordinator(navigationController: navController) coordinator.start() return coordinator }() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { return true } } """ **Anti-Pattern:** Scattering navigation calls throughout the application, making it difficult to trace the user's journey. Using NavigationLinks excessively without properly managing the NavigationStack. ### 1.3 State Management Strategies (Beyond ObservedObject) * **Standard:** Choose the appropriate state management technique based on the complexity of your application and the scope of the data. Understand the implications of environment objects, app storage, state objects and bindings. * **Do This:** For simple, view-specific state, use "@State". For shared state within a screen or feature, leverage "@ObservedObject" or "@StateObject" connected with shared view models. For application-wide state, use "@EnvironmentObject" or "@AppStorage". * **Don't Do This:** Overuse "@EnvironmentObject" for localized state—this makes it harder to trace data flow. Force-unwrap optional environment objects! Rely on passing bindings down through many layers of views; often a view model or "EnvironmentObject" is a better solution. **Why:** Proper state management ensures predictable data flow, facilitates UI updates, and simplifies testing. Choosing the right tool improves maintainability. **Example:** """swift // App-wide settings (using AppStorage) import SwiftUI struct ContentView: View { @AppStorage("isDarkMode") private var isDarkMode = false //Default dark mode to false var body: some View { VStack { Text("Current Mode: \(isDarkMode ? "Dark" : "Light")") Button("Toggle Dark Mode") { isDarkMode.toggle() } } .frame(width: 300, height: 200) .background(isDarkMode ? Color.black : Color.white) .foregroundColor(isDarkMode ? Color.white : Color.black) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } // Sharing data between views on the same screen (using ObservedObject) class ScreenViewModel: ObservableObject { @Published var sharedMessage: String = "Initial Message" } struct ScreenView: View { @ObservedObject var viewModel = ScreenViewModel() var body: some View { VStack { TextField("Enter Message", text: $viewModel.sharedMessage) .padding() SecondView(viewModel: viewModel) } } } struct SecondView: View { @ObservedObject var viewModel: ScreenViewModel var body: some View { Text("Message from ScreenView: \(viewModel.sharedMessage)") .padding() } } // Sharing data across the *entire* app (EnvironmentObject) class AppSettings: ObservableObject { @Published var accentColor: Color = .blue } struct AppContainerView: View { @StateObject var settings = AppSettings() var body: some View { MainView() .environmentObject(settings) } } struct MainView: View { var body: some View { NavigationView { NavigationLink("Go to Settings") { SettingsView() } .navigationTitle("Main View") } } } struct SettingsView: View { @EnvironmentObject var settings: AppSettings var body: some View { VStack { Text("Accent Color:") ColorPicker("Select Accent Color", selection: $settings.accentColor) .padding() } .navigationTitle("Settings") } } """ **Anti-Pattern:** Using "@State" for complex data models that should reside in a ViewModel. Relying solely on closures for passing data between views – use view models or the environment for more structured communication. ## 2. Project Structure and Organization A well-organized project structure is essential for scalability and collaboration. ### 2.1 Feature-Based Folders * **Standard:** Organize your project into feature-based folders, grouping related Models, Views, ViewModels, and other resources within dedicated directories. * **Do This:** Create a top-level "Features" directory. Inside, create subdirectories for each feature (e.g., "Login", "Profile", "Settings"). * **Don't Do This:** Dumping all project files into a single directory. **Why:** Feature-based folders improve code discoverability, reduce merge conflicts, and make it easier to understand the application's structure. **Example:** """ MyProject/ ├── MyProjectApp.swift ├── Assets.xcassets/ ├── Features/ │ ├── Login/ │ │ ├── LoginView.swift │ │ ├── LoginViewModel.swift │ │ ├── LoginModel.swift │ │ ├── LoginService.swift │ ├── Profile/ │ │ ├── ProfileView.swift │ │ ├── ProfileViewModel.swift │ │ ├── ProfileModel.swift │ │ ├── ProfileService.swift │ ├── Settings/ │ │ ├── SettingsView.swift │ │ ├── SettingsViewModel.swift │ │ ├── SettingsModel.swift │ │ ├── SettingsService.swift ├── Services/ (Shared services) │ ├── APIClient.swift │ ├── PersistenceManager.swift ├── Models/ (Shared models) │ ├── User.swift ├── Resources/ (Images, fonts, etc) │ ├── Assets.xcassets/ ├── Utilities/ (Extensions, helper functions) │ ├── ColorExtensions.swift ├── Supporting Files/ │ ├── Info.plist """ **Anti-Pattern:** Creating folders based on file type (e.g., "Views", "ViewModels") instead of feature. ### 2.2 Modularization * **Standard:** Break down large projects into smaller, independent modules or Swift Packages. * **Do This:** Identify reusable components or logical groups of functionality. Package them into separate modules. Use local Swift Packages for internal modularization or remote Swift Packages for sharing code across projects using the Swift Package Manager (SPM). * **Don't Do This:** Having a single monolithic module for everything – can be difficult to manage and compile. **Why:** Modularization promotes code reuse, improves build times, isolates functionality, and facilitates team collaboration. **Example:** Imagine "APIClient.swift" and "User.swift" from the above structure formed their own Swift Package for reuse across multiple apps. This can be done through Xcode. **Anti-Pattern:** Creating overly granular modules that lead to unnecessary complexity. ### 2.3 Naming Conventions * **Standard:** Adhere to clear and consistent naming conventions for all project elements. * **Do This:** * Use descriptive names that clearly indicate the purpose of each entity (e.g., "LoginViewModel", "UserListView"). * Follow Swift's naming conventions (e.g., camelCase for variables and functions, PascalCase for types). * Suffix Views with "View" and ViewModels with "ViewModel". * Prefix custom SwiftUI modifiers for namespacing (e.g., "myAppCustomStyle()"). * **Don't Do This:** Using ambiguous or cryptic names. Inconsistent naming across the project. **Why:** Consistent naming improves code readability and maintainability, and reduces cognitive load. **Example:** """swift // Good Naming struct UserProfileView: View { ... } class UserProfileViewModel: ObservableObject { ... } func calculateTotal(items: [OrderItem]) -> Double { ... } // Bad Naming struct UserView: View { ... } // Not descriptive enough class ProfileVM: ObservableObject { ... } // Abbreviation is unclear func calc(items: [OrderItem]) -> Double { ... } // Ambiguous """ **Anti-Pattern:** Using single-letter variable names (except in very localized contexts, such as within small loops). ## 3. Coding Practices Writing clean and maintainable code is essential for any successful project. ### 3.1 Readability and Formatting * **Standard:** Prioritize code readability and follow consistent formatting rules. * **Do This:** * Use proper indentation. * Keep lines of code reasonably short (e.g., 80-120 characters). * Use blank lines to separate logical blocks of code. * Add comments to explain complex or non-obvious logic. * **Don't Do This:** Writing excessively long lines of code. Having inconsistent indentation. Omitting comments for complex sections. **Why:** Readability improves code comprehension and reduces the likelihood of errors. **Example:** """swift // Good Formatting struct ContentView: View { @ObservedObject var viewModel: ContentViewModel var body: some View { VStack { Text("Hello, world!") .padding() Button("Tap Me") { viewModel.performAction() } } } // End of body // Comment explaining the purpose of this method, if needed private func helperFunction() { //Implementation details } } // Bad Formatting struct ContentView:View{ @ObservedObject var viewModel:ContentViewModel var body: some View {VStack{Text("Hello, world!").padding();Button("Tap Me"){viewModel.performAction()}}} // Incomprehensible } """ **Anti-Pattern:** Putting multiple statements on a single line. ### 3.2 Reusability * **Standard:** Design reusable components and avoid code duplication. * **Do This:** * Create custom SwiftUI views for common UI elements. * Use SwiftUI modifiers to encapsulate styling and behavior. * Employ generics to create flexible and type-safe components. * **Don't Do This:** Copying and pasting code. Creating tightly coupled components. **Why:** Reusability reduces code size, simplifies maintenance, and improves consistency. **Example:** """swift // Custom Button Style (Modifier) struct CustomButtonStyle: ViewModifier { let backgroundColor: Color let foregroundColor: Color func body(content: Content) -> some View { content .padding() .background(backgroundColor) .foregroundColor(foregroundColor) .clipShape(RoundedRectangle(cornerRadius: 10)) } } extension View { func customButtonStyle(backgroundColor: Color, foregroundColor: Color) -> some View { self.modifier(CustomButtonStyle(backgroundColor: backgroundColor, foregroundColor: foregroundColor)) } } // Usage struct MyView: View { var body: some View { Button("Tap Me") {} .customButtonStyle(backgroundColor: .blue, foregroundColor: .white) } } //Reusable component: Generic List struct GenericListView<T, Content: View>: View { let items: [T] let contentBuilder: (T) -> Content init(items: [T], @ViewBuilder contentBuilder: @escaping (T) -> Content) { self.items = items self.contentBuilder = contentBuilder } var body: some View { List { ForEach(items, id: \.self) { item in // Assuming T is Hashable/Identifiable for this context contentBuilder(item) } } } } //Usage: struct ContentViewExample: View { let names = ["Alice", "Bob", "Charlie"] let numbers = [1, 2, 3, 4, 5] var body: some View { VStack { GenericListView(items: names) { name in Text("Name: \(name)") .padding() } GenericListView(items: numbers) { number in Text("Number: \(number)") .padding() } } } } """ **Anti-Pattern:** Repeating the same UI code in multiple views. ### 3.3 Error Handling * **Standard:** Implement robust error handling to prevent crashes and provide informative feedback to the user. * **Do This:** * Use Swift's "Result" type for handling asynchronous operations. * Catch and handle errors using "do-catch" blocks. * Display user-friendly error messages. * Implement logging for debugging purposes. * **Don't Do This:** Ignoring potential errors. Force-unwrapping optionals without checking for "nil". Crashing the app due to unhandled exceptions. **Why:** Proper error handling ensures a stable and reliable application. **Example:** """swift // Using Result Type enum NetworkError: Error { case invalidURL case requestFailed(Error) case invalidData } func fetchData(from urlString: String, completion: @escaping (Result<Data, NetworkError>) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(.invalidURL)) return } URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { completion(.failure(.requestFailed(error))) return } guard let data = data else { completion(.failure(.invalidData)) return } completion(.success(data)) }.resume() } //Handling networking calls within ViewModel class ViewModel: ObservableObject { @Published var data: String = "" @Published var errorMessage: String? = nil func loadData() { fetchData(from: "https://example.com/data") { result in switch result { case .success(let data): if let stringData = String(data: data, encoding: .utf8) { DispatchQueue.main.async { self.data = stringData } } else { DispatchQueue.main.async { self.errorMessage = "Invalid data format" } } case .failure(let error): DispatchQueue.main.async { self.errorMessage = "Error fetching data: \(error)" } } } } } //Displaying errors to the user. struct ErrorDisplayView: View { @ObservedObject var viewModel = ViewModel() var body: some View { VStack { if let errorMessage = viewModel.errorMessage { Text("Error: \(errorMessage)") .foregroundColor(.red) } else { Text("Data: \(viewModel.data)") } Button("Load Data") { viewModel.loadData() } } } } """ **Anti-Pattern:** Using "try!" or "as!" without proper error handling or type checking (respectively). ### 3.4 Asynchronous Operations * **Standard:** Handle long-running tasks asynchronously to prevent blocking the main thread and ensure a responsive UI. * **Do This:** * Use "async/await" for modern asynchronous code. * Utilize "Task" to execute asynchronous operations from synchronous contexts. * Update UI elements on the main thread using "@MainActor". * **Don't Do This:** Performing network requests or heavy computations on the main thread. Causing UI freezes or unresponsive behavior. **Why:** Asynchronous operations keep the UI responsive and improve the user experience. SwiftUI's concurrency model simplifies these operations. **Example:** """swift // Using async/await class ImageLoader: ObservableObject { @Published var image: Image? = nil @Published var isLoading: Bool = false @Published var errorMessage: String? = nil @MainActor // Ensures UI updates happen on MainThread func loadImage(from urlString: String) async { isLoading = true errorMessage = nil guard let url = URL(string: urlString) else { errorMessage = "Invalid URL" isLoading = false return } do { let (data, _) = try await URLSession.shared.data(from: url) guard let uiImage = UIImage(data: data) else { errorMessage = "Failed to convert data to image" isLoading = false return } self.image = Image(uiImage: uiImage) isLoading = false } catch { errorMessage = "Failed to load image: \(error.localizedDescription)" isLoading = false } } } struct AsyncImageView: View { @ObservedObject var imageLoader = ImageLoader() let urlString: String init(urlString: String) { self.urlString = urlString } var body: some View { VStack { if imageLoader.isLoading { ProgressView("Loading Image...") } else if let image = imageLoader.image { image .resizable() .scaledToFit() .frame(width: 200, height: 200) } else if let errorMessage = imageLoader.errorMessage { Text("Error: \(errorMessage)") .foregroundColor(.red) } }.task { // Perform Async operations from Struct, which isn't an @MainActor itself await imageLoader.loadImage(from: urlString) } } } """ **Anti-Pattern:** Using "DispatchQueue.main.async" excessively without understanding SwiftUI's automatic UI updating mechanisms. ## 4. Testing Writing tests is crucial for ensuring the quality and reliability of your application. ### 4.1 Unit Testing * **Standard:** Write unit tests to verify the behavior of individual components, especially ViewModels and Models. * **Do This:** * Use the XCTest framework to write unit tests. * Mock dependencies to isolate units under test. * Test different scenarios, including success and failure cases. * **Don't Do This:** Neglecting unit tests. Writing tightly coupled tests that are difficult to maintain. **Why:** Unit tests catch bugs early, improve code quality, and facilitate refactoring with confidence. **Example:** """swift import XCTest @testable import MyProject // Replace with your project name final class UserViewModelTests: XCTestCase { // Make the class final so there's no overriding it func testLoadUsersSuccess() async throws { // Given let viewModel = UserViewModel() //When await viewModel.loadUsers() //Then XCTAssertFalse(viewModel.users.isEmpty, "Users should not be empty on successful data loading.") XCTAssertNil(viewModel.errorMessage, "Error message should be nil on successful data loading.") XCTAssertFalse(viewModel.isLoading, "Is Loading should be set back to false after loading") } func testLoadUsersFail() async throws { // Given let viewModel = UserViewModel() //When await viewModel.loadUsers() //Then XCTAssertNotNil(viewModel.errorMessage, "Error message should not be nil on when failed to load data loading.") } } """ **Anti-Pattern:** Writing tests that cover only happy-path scenarios. ### 4.2 UI Testing * **Standard:** Automate UI testing to verify the user interface and navigation flows. * **Do This:** * Use the XCUITest framework to write UI tests. * Test common user interactions, such as button taps and text input. * Verify that UI elements are displayed correctly. * **Don't Do This:** Skipping UI tests. Writing brittle tests that break easily due to UI changes. **Why:** UI tests ensure that the application functions correctly from the user's perspective. **Example:** (Conceptual example – XCUITests often involve launching the app and interacting with elements via their accessibility identifiers). """swift import XCTest @testable import MyProject // Replace with your project name class MyProjectUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false } func testSuccessfulLogin() throws { let app = XCUIApplication() app.launch() // Get handles to UI elements by their accessibility identifiers let usernameTextField = app.textFields["usernameTextField"] let passwordSecureField = app.secureTextFields["passwordSecureField"] let loginButton = app.buttons["loginButton"] // Enter username and password and log in usernameTextField.tap() usernameTextField.typeText("testuser") passwordSecureField.tap() passwordSecureField.typeText("password") loginButton.tap() // Get handle to log out button let logoutButton = app.buttons["logoutButton"] XCTAssertTrue(logoutButton.exists) // If it exists, we are logged in. } func testFailedLogin() throws { let app = XCUIApplication() app.launch() // Get handles to UI elements by their accessibility identifiers let usernameTextField = app.textFields["usernameTextField"] let passwordSecureField = app.secureTextFields["passwordSecureField"] let loginButton = app.buttons["loginButton"] // Enter username and password and log in usernameTextField.tap() usernameTextField.typeText("wronguser") passwordSecureField.tap() passwordSecureField.typeText("wrongpassword") loginButton.tap() // Get handles to log out button let errorLabel = app.staticTexts["errorMessage"] XCTAssertTrue(errorLabel.exists) // If it exists, login failed } } """ **Anti-Pattern:** Hardcoding UI element positions or text values in UI tests. ## 5. Modern SwiftUI Features (Latest Version) Leverage the latest SwiftUI features to write more concise, efficient, and expressive code. ### 5.1 "Grid" Layout * **Standard:** Use the "Grid" view for creating flexible and dynamic grid-based layouts. * **Do This:** Define rows and columns using "GridRow", "GridColumn", "GridCell" and modifiers like "gridCellColumns", "gridCellRows" and "gridColumnAlignment". * **Don't Do This:** Overusing "HStack" and "VStack" for complex layouts that are better suited to a grid. **Why:** The modern "Grid" is more flexible and performant. It replaces older, less efficient layout techniques. **Example:** """swift import SwiftUI struct GridLayoutExample: View { var body: some View { Grid { GridRow { Color.red Color.green Color.blue } GridRow { Color.yellow Color.orange Color.purple } } .padding() } } """ ### 5.2 "@Bindable" (SwiftUI 5.0+) * **Standard:** Employ "@Bindable" for improved data binding within ViewModels. * **Do this:** Adopt "Bindable" for classes that are designed for handling user facing data. * **Don't do this:** Don't overuse "Bindable" objects. Keep them limited to a limited scope and avoid unnecessary memory allocations. ### 5.3 "PhaseAnimator" (SwiftUI 5.0+) * **Standard:** Utilize "PhaseAnimator" for creating complex, multi-stage animations. * **Do This:** Define a "Phase" enum and animate properties with custom animation curves for each phase. * **Don't Do This:** Relying on older animation techniques for complex sequences that are easily managed by "PhaseAnimator". **Example:** """swift struct PhaseAnimatorExample: View { @State private var isAnimating = false var body: some View { PhaseAnimator([0.0, 1.0, 0.0]) { phaseValue in RoundedRectangle(cornerRadius: 20) .fill(.blue) .scaleEffect(1 + phaseValue) .opacity(1 - Double(abs(phaseValue))) } animation: { phase in switch phase { case 0: return .easeInOut(duration: 1.0) case 1: return .easeInOut(duration: 0.5) default: return .easeInOut(duration: 1.0) } } .onTapGesture { isAnimating.toggle() } } } """ ### 5.4 "@Observable" (SwiftUI 5.0+) * **Standard:** Prefer using "@Observable" for simpler state management when applicable * **Do This:** Conform class to the "Observable" macro. Let SwiftUI handle the state management * **Don't Do This:** If more control is needed for the object and its state, "ObservableObject" is the preferred method! This document provides a strong foundation for building well-architected SwiftUI applications. Adhering to these standards will promote maintainability, scalability, and overall code quality. Be sure to stay up-to-date with the latest SwiftUI releases and adapt these standards accordingly.