# Code Style and Conventions Standards for SwiftUI
This document outlines the code style and conventions standards for SwiftUI development, aiming to ensure consistency, readability, and maintainability across projects. It focuses on modern approaches and patterns specific to SwiftUI and incorporates the latest features.
## 1. General Formatting
Consistent formatting is crucial for code readability. Adhering to specific rules helps prevent visual clutter and ensures that the structure of the code is immediately apparent.
### 1.1. Indentation
* **Do This:**
* Use 4 spaces for indentation. Avoid tabs.
* Ensure that Xcode's indentation settings are configured accordingly (Xcode > Settings > Text Editing > Indentation).
* **Don't Do This:**
* Mix spaces and tabs.
* Use inconsistent indentation levels.
* **Why:** Consistent indentation makes the code structure instantly recognizable, reducing cognitive load.
"""swift
// Do This:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, world!")
.padding()
Button("Tap Me") {
// Action
}
}
}
}
// Don't Do This:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, world!")
.padding()
Button("Tap Me") {
// Action
}
}
}
}
"""
### 1.2. Line Length
* **Do This:**
* Limit lines to a maximum of 120 characters.
* Break long lines at logical points, such as after commas or operators.
* **Don't Do This:**
* Create excessively long lines that require horizontal scrolling.
* **Why:** Shorter lines are easier to read and prevent text from wrapping awkwardly on smaller screens.
"""swift
// Do This:
struct LongContentView: View {
var body: some View {
Text("This is a very long string that needs to be broken into multiple lines to improve readability and maintainability.")
.padding()
}
}
// Don't Do This:
struct LongContentView: View {
var body: some View {
Text("This is a very long string that needs to be broken into multiple lines to improve readability and maintainability.").padding()
}
}
"""
### 1.3. Whitespace
* **Do This:**
* Use whitespace to separate logical blocks of code, enhancing readability.
* Place a single blank line between methods or distinct sections of code within a "View".
* Include spaces around operators ("=", "+", "-", "*", "/").
* **Don't Do This:**
* Omit whitespace entirely, creating dense, hard-to-read code.
* Use excessive whitespace, which can make the code feel disjointed.
* **Why:** Judicious use of whitespace helps to visually group related code, improving comprehension.
"""swift
// Do This:
struct ContentView: View {
var name: String
var body: some View {
VStack {
Text("Hello, \(name)!")
.padding()
Button("Greet Again") {
greet()
}
}
}
func greet() {
print("Greeting \(name)!")
}
}
// Don't Do This:
struct ContentView: View {
var name:String
var body:some View {
VStack {
Text("Hello, \(name)!") .padding()
Button("Greet Again") { greet() }
}
}
func greet() {
print("Greeting \(name)!")
}
}
"""
### 1.4. Braces
* **Do This:**
* Place opening braces on the same line as the statement.
* Ensure closing braces are on a new line, aligned with the statement that opened the block.
* **Don't Do This:**
* Place opening braces on a new line.
* Inconsistently align braces.
* **Why:** Consistent brace placement maintains a uniform code appearance and makes it easy to identify code blocks.
"""swift
// Do This:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, world!")
}
}
}
// Don't Do This:
struct ContentView: View
{
var body: some View
{
VStack
{
Text("Hello, world!")
}
}
}
"""
## 2. Naming Conventions
Clear and descriptive names are vital for code understanding and maintainability. Standardized naming conventions provide immediate context about the purpose and type of a variable, function, or view.
### 2.1. Variables and Constants
* **Do This:**
* Use camelCase for variable and constant names.
* Constants should ideally be "let" declarations where possible.
* Name variables and constants according to their purpose, and be descriptive.
* **Don't Do This:**
* Use single-letter variable names, except in loop counters.
* Use vague or misleading names.
* **Why:** Meaningful names clarify the role of variables and constants, making the code self-documenting.
"""swift
// Do This:
let maximumAttempts = 3
var currentScore = 0
// Don't Do This:
let max = 3
var x = 0
"""
### 2.2. Functions and Methods
* **Do This:**
* Use camelCase for function and method names.
* Start function names with a verb that describes the action it performs (e.g., "calculateTotal", "updateUI").
* For methods that return a value, the name should clearly indicate what is being returned.
* **Don't Do This:**
* Use ambiguous or unclear function names.
* Invent new naming patterns beyond standard camelCase.
* **Why:** Verbs make function actions explicit, while clear return value names enhance understanding of function purpose.
"""swift
// Do This:
func calculateTotalScore(for player: String) -> Int {
// Implementation
return 100
}
// Don't Do This:
func doSomething(player: String) -> Int {
// Implementation
return 100
}
"""
### 2.3. Types (Structs, Classes, Enums)
* **Do This:**
* Use PascalCase (UpperCamelCase) for type names (structs, classes, enums).
* Give type names that describe the data they encapsulate (e.g., "UserProfile", "WeatherData").
* For enums, each case that it represents should be clearly understandable.
* **Don't Do This:**
* Use lowercase or camelCase for type names.
* Use names that are too generic or abstract.
* **Why:** PascalCase clearly identifies types, while meaningful names provide immediate context about the data or functionality they manage.
"""swift
// Do This:
struct UserProfile {
var username: String
var email: String
}
enum ResultState {
case success
case failure
}
// Don't Do This:
struct userProfile {
var username: String
var email: String
}
enum rs {
case ok
case err
}
"""
### 2.4. SwiftUI Views
* **Do This:**
* Name views to reflect their primary UI element or function.
* Use PascalCase and append "View" to the name where appropriate and helpful (e.g., "ProfileView", "SettingsView"). Consider omitting "View" if the purpose is extremely clear from the base name.
* Compose small, reusable views, each responsible for a specific part of the UI.
* **Don't Do This:**
* Create monolithic views that handle multiple unrelated UI components.
* Use very generic names like "UI" or "Screen".
* **Why:** Descriptive view names make it easier to navigate and refactor the UI, and decomposing into smaller views enforces the Single Responsibility Principle.
"""swift
// Do This:
struct ProfileView: View {
var user: UserProfile
var body: some View {
VStack {
Text("Username: \(user.username)")
Text("Email: \(user.email)")
}
}
}
// Don't Do This:
struct MainUI: View {
var user: UserProfile
var body: some View {
VStack {
// Lots of unrelated UI elements...
}
}
}
"""
### 2.5. Protocols
* **Do This:**
* Use PascalCase for protocol names.
* Name protocols according to the capability or contract they provide (e.g., "DataLoadable", "Themeable").
* Consider appending "Delegate" or "DataSource" for protocols that follow these patterns.
* **Don't Do This:**
* Use names that are too generic or abstract without providing context.
* **Why:** Clear protocol names define the role and purpose of the conformant types.
"""swift
// Do This:
protocol DataLoadable {
func loadData()
}
protocol Themeable {
var primaryColor: Color { get }
}
// Don't Do This:
protocol Something {
func doIt()
}
"""
## 3. SwiftUI Specific Conventions
SwiftUI introduces its own specific requirements and stylistic considerations. This section covers practices crucial for writing idiomatic SwiftUI code.
### 3.1. View Composition
* **Do This:**
* Favor composition over inheritance or large view hierarchies.
* Create small, focused views, each responsible for a specific piece of the UI.
* Use "Group" or custom container views to organize and structure the view hierarchy without affecting layout.
* **Don't Do This:**
* Build monolithic views that are difficult to maintain and reuse.
* Create deep view hierarchies that can impact performance.
* **Why:** Composition enhances reusability and manageability, while flat hierarchies improve rendering performance.
"""swift
// Do This:
struct ProfileHeaderView: View {
var user: UserProfile
var body: some View {
HStack {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
Text(user.username)
.font(.title2)
Text(user.email)
.font(.subheadline)
.foregroundColor(.gray)
}
}
}
}
struct ProfileView: View {
var user: UserProfile
var body: some View {
VStack {
ProfileHeaderView(user: user)
// Other profile information...
}
}
}
// Don't Do This:
struct ProfileView: View {
var user: UserProfile
var body: some View {
VStack {
HStack {
Image(systemName: "person.circle.fill")
.resizable()
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
Text(user.username)
.font(.title2)
Text(user.email)
.font(.subheadline)
.foregroundColor(.gray)
}
}
// More code directly in ProfileView...
}
}
}
"""
### 3.2. State Management
* **Do This:**
* Use "@State" for simple, view-specific state.
* Use "@Binding" to pass state from a parent view to a child view.
* Use "@ObservedObject" or "@StateObject" (for owning the object's lifecycle) for more complex data models that require observation.
* Use "@EnvironmentObject" for data that should be accessible throughout the entire app.
* Adopt the unidirectional data flow pattern when possible (State -> View -> Action -> State).
* **Don't Do This:**
* Overuse "@State" for complex data models that should be shared across multiple views.
* Mutate "@Binding" variables directly from the child view; use a callback function instead.
* Access "@EnvironmentObject" without first ensuring it has been injected into the environment by an ancestor view.
* **Why:** Proper state management ensures that data is consistent and predictable, leading to fewer bugs and easier debugging.
"""swift
// Do This:
class Counter: ObservableObject {
@Published var count = 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()
}
SubCounterView(counter: counter)
}
}
}
struct SubCounterView: View {
@ObservedObject var counter: Counter
var body: some View {
Text("Sub Count: \(counter.count)")
}
}
// Don't Do This:
struct BadCounterView: View {
@State var count = 0 // Used for complex logic
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1 //Mutate state directly within the view
}
}
}
}
"""
### 3.3. Modifiers
* **Do This:**
* Apply modifiers in a consistent order; a recommended order is layout, appearance, then behavior (e.g., "frame", "padding", "font", "foregroundColor", "onTapGesture"). This aids readability.
* Group related modifiers together for clarity.
* Use conditional modifiers with caution and consider alternatives like custom views or view builders for complex logic.
* **Don't Do This:**
* Apply modifiers in a random or inconsistent order.
* Chain together excessive modifiers without considering readability.
* Use deeply nested conditional modifiers, as this can reduce readability and maintainability.
* **Why:** consistent modifier order and smart grouping improve code clarity.
"""swift
// Do This:
struct StyledTextView: View {
var text: String
var body: some View {
Text(text)
.font(.title)
.foregroundColor(.blue)
.padding()
.background(Color.gray.opacity(0.2))
.cornerRadius(10)
}
}
// Don't Do This:
struct UnstyledTextView: View {
var text: String
var body: some View {
Text(text)
.padding()
.foregroundColor(.blue)
.font(.title)
.background(Color.gray.opacity(0.2))
.cornerRadius(10)
}
}
"""
### 3.4. Closures
* **Do This:**
* Use trailing closures for cleaner syntax when passing closures as the last argument to a function.
* Use shorthand argument names ("$0", "$1", etc.) for simple closures.
* Explicitly name closure parameters when the logic is complex or the parameter types are unclear.
* Prioritize readability over terseness when using closures.
* **Don't Do This:**
* Overuse shorthand argument names in complex closures, making the code difficult to understand.
* Create excessively long or nested closures that hinder readability.
* **Why:** Proper closure usage enhances code readability and reduces boilerplate.
"""swift
// Do This:
Button("Tap Me") {
print("Button tapped!")
}
List(items) { item in
Text(item.name)
}
// Using explicit parameter names when the context is unclear:
myArray.map { (number: Int) -> String in
return "Number: \(number)"
}
// Don't Do This:
Button { print("Button tapped!") } label: { Text("Tap Me") } // Avoid this verbose syntax
List(items) {
Text($0.name) // Potentially unclear parameter name
}
"""
### 3.5. Asynchronous Operations
* **Do This:**
* Use "async/await" for handling asynchronous operations and avoid using nested completion handlers, also known as "callback hell".
* Use "Task" to perform asynchronous operations within SwiftUI views.
* Handle errors gracefully using "try/catch" within "async" functions.
* **Don't Do This:**
* Block the main thread with long-running synchronous operations. This freezes the UI.
* Ignore potential errors from asynchronous operations.
* Use older completion handler patterns when "async/await" is available.
* **Why:** "async/await" and Task structures make asynchronous code more readable and maintainable, while proper error handling ensures application stability.
"""swift
// Do This:
struct ContentView: View {
@State private var fetchedData: String = "Loading..."
var body: some View {
Text(fetchedData)
.task {
await fetchData()
}
}
func fetchData() async {
do {
let url = URL(string: "https://example.com/data")!
let (data, _) = try await URLSession.shared.data(from: url)
fetchedData = String(data: data, encoding: .utf8) ?? "Invalid Data"
} catch {
fetchedData = "Error: \(error.localizedDescription)"
}
}
}
// Don't Do This:
struct ContentView: View {
@State private var fetchedData: String = "Loading..."
var body: some View {
Text(fetchedData)
.onAppear {
fetchData() // Using onAppear for async tasks
}
}
func fetchData() {
let url = URL(string: "https://example.com/data")!
URLSession.shared.dataTask(with: url) { data, response, error in //Avoid Callback Hell
if let data = data {
fetchedData = String(data: data, encoding: .utf8) ?? "Invalid Data"
} else if let error = error {
fetchedData = "Error: \(error.localizedDescription)"
}
}.resume()
}
}
"""
### 3.6 Live Previews
* **Do This:**
* Make use of Xcode's live preview functionality to rapidly iterate on the UI.
* Provide multiple preview configurations using the "PreviewProvider" to test different device sizes, orientations, and dynamic type settings.
* Ensure previews build and represent a valid state of the view.
* **Don't Do This:**
* Neglect live previews, relying only on running the app on a device or simulator.
* Create overly complex preview configurations that slow down the build process.
* Leave broken or incomplete previews in the codebase.
* **Why:** Live previews are a powerful tool for rapid UI development and testing various design scenarios.
"""swift
// Do This:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.previewDevice(PreviewDevice(rawValue: "iPhone 14 Pro"))
.previewDisplayName("iPhone 14 Pro")
ContentView()
.previewDevice(PreviewDevice(rawValue: "iPad Air (5th generation)"))
.previewDisplayName("iPad Air")
.environment(\.sizeCategory, .extraLarge)
}
}
}
// Don't Do This:
struct ContentView_Previews: PreviewProvider { // Missing Preview
static var previews: some View {
Text("Missing View")
}
}
"""
## 4. Error Handling
Robust error handling is essential for creating stable and reliable applications. Handling errors gracefully can prevent crashes and provide users with meaningful feedback.
### 4.1. Explicit Error Handling
* **Do This:**
* Propagate errors using the "Result" type or "throws" keyword in functions.
* Use "try", "try?", or "try!" appropriately based on the context and error handling requirements. Use "try!" with extreme caution, only when it's absolutely certain that no error will occur.
* Provide informative error messages and consider logging errors for debugging purposes.
* **Don't Do This:**
* Ignore errors or rely solely on optional values without handling potential failures.
* Use "try!" without careful consideration of the consequences.
* **Why:** Explicit error handling ensures that failures are properly managed, preventing unexpected application behavior.
"""swift
// Do This:
enum DataError: Error {
case invalidURL
case networkError(Error)
case invalidData
}
func fetchData(from urlString: String) async throws -> Data {
guard let url = URL(string: urlString) else {
throw DataError.invalidURL
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
return data
} catch {
throw DataError.networkError(error)
}
}
struct ContentView: View {
@State private var data: Data?
@State private var errorMessage: String?
var body: some View {
VStack {
if let data = data {
Text("Data Received: \(data.count) bytes")
} else if let errorMessage = errorMessage {
Text("Error: \(errorMessage)")
} else {
Text("Fetching data...")
}
}
.task {
do {
data = try await fetchData(from: "https://example.com/data")
} catch {
errorMessage = error.localizedDescription
}
}
}
}
// Don't Do This:
func fetchData(from urlString: String) async -> Data? {
guard let url = URL(string: urlString) else {
return nil //Not Handling potential error conditions
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
return data
} catch {
return nil //Not Handling potential error conditions
}
}
"""
## 5. Documentation
Comprehensive documentation is crucial for maintaining codebases and enabling team collaboration. Documentation should be clear, concise, and up-to-date.
### 5.1. Inline Documentation
* **Do This:**
* Use inline comments ("//" or "/* */") to explain complex or non-obvious code sections.
* Use Markdown-style comments for documenting functions, methods, and types.
* Include parameters, return values, and potential error scenarios in documentation.
* **Don't Do This:**
* Write comments that simply restate the code. Documentation should explain the *why*, not the *what*.
* Neglect to update documentation when code changes.
* **Why:** Meaningful comments clarify the intent and purpose of the code, reducing the need for reverse-engineering.
"""swift
// Do This:
/// Calculates the total score for a player based on their performance.
/// - Parameter player: The name of the player.
/// - Returns: The total score as an integer.
/// - Throws: "ScoreError.invalidData" if the player's data is invalid.
func calculateTotalScore(for player: String) throws -> Int {
// Implementation
return 100
}
// Don't Do This:
func calculateTotalScore(for player: String) throws -> Int {
// Calculate the total score //Redundant
return 100
}
"""
### 5.2. README Files
* **Do This:**
* Include a "README.md" file in each project with a clear description of the project's purpose, setup instructions, and usage examples.
* Explain any dependencies or special configuration requirements.
* Provide contact information or a guide for contributing to the project.
* **Don't Do This:**
* Omit a "README" file, leaving new developers without guidance.
* Write a "README" file that is too vague or incomplete.
* **Why:** A well-maintained README file provides essential context and instructions, making it easier for others to understand and contribute to the project.
## 6. Security Considerations
Security should be integrated into the development process from the beginning. Following secure coding practices can help prevent vulnerabilities and protect user data.
### 6.1. Data Validation
* **Do This:**
* Validate all user inputs to prevent injection attacks and data corruption.
* Use secure coding practices to handle sensitive data such as passwords and API keys.
* **Don't Do This:**
* Trust user input without validation.
* Store sensitive data in plaintext, especially in easily accessible locations (like UserDefaults).
* **Why:** Data validation prevents malicious users from exploiting vulnerabilities in the application.
### 6.2. Network Security
* **Do This:**
* Use HTTPS for all network communication to encrypt data in transit.
* Implement certificate pinning for additional security when communicating with trusted servers.
* **Don't Do This:**
* Use HTTP for transmitting sensitive data.
* Ignore potential man-in-the-middle attacks.
* **Why:** Secure network communication protects data from eavesdropping and tampering.
## 7. Performance Optimization
Writing performant code is crucial for delivering a smooth and responsive user experience. Performance optimization should be an ongoing effort throughout the development lifecycle.
### 7.1. View Rendering
* **Do This:**
* Minimize unnecessary view updates by carefully managing state and using "Equatable" conformance.
* Use "LazyVStack" and "LazyHStack" for large lists to improve initial load times.
* Avoid complex calculations or expensive operations within the "body" of a "View".
* **Don't Do This:**
* Force unnecessary view updates.
* Perform heavy computations on the main thread.
* **Why:** Efficient view rendering reduces CPU usage and improves UI responsiveness.
### 7.2. Memory Management
* **Do This:**
* Avoid strong reference cycles by using "weak" or "unowned" references when necessary.
* Release resources when they are no longer needed.
* **Don't Do This:**
* Create memory leaks by neglecting to break strong reference cycles.
* Hold onto large objects longer than necessary.
* **Why:** Proper memory management prevents crashes and improves application stability.
## 8. SwiftUI Updates
* **Do This:**
* Keep code updated with the newest SwiftUI features and best practices as the framework evolves.
* Refactor legacy code to utilize modern SwiftUI constructs.
* Stay informed about SwiftUI updates through official documentation, WWDC sessions, and community resources.
* **Don't Do This:**
* Stick to outdated patterns and practices when better alternatives exist.
* Ignore new features that can simplify code or improve performance.
* **Why:** Keeping code current ensures that it remains maintainable, efficient, and compatible with the latest platform features.
By adhering to these coding standards, development teams can create high-quality, maintainable, and performant SwiftUI applications. This guide serves as a definitive reference for SwiftUI development best practices.
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.
# 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<Content: View>: 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<Content: View>: 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.