A two-screen iOS application built with SwiftUI that demonstrates authentication and server list functionality according to the provided design specifications.
- User authentication with credential storage in Keychain
- Server list display with sorting capabilities
- Persistent data storage with cache management
- Automatic token validation and error handling
- Modern SwiftUI architecture with MVVM pattern
- Protocol-based router for navigation
The application follows a clean MVVM (Model-View-ViewModel) architecture with clear separation of concerns and protocol-oriented design for better testability and maintainability.
- AuthModels.swift: Contains data structures for authentication and server data
LoginRequest: Request payload for authenticationAuthResponse: Authentication response containing tokenServer: Server data model with name and distance properties (all with Sendable conformance)
- NetworkModels.swift: Contains networking layer abstractions
Request: Protocol defining network request structure with associated response typesLoginNetworkRequest: Concrete implementation for authentication requestsServersNetworkRequest: Concrete implementation for server list requests
- ContentView.swift: Root view with protocol-based router for navigation
- AuthView.swift: Login screen implementation
- ServerListView.swift: Server list display with clean switch-based UI state management
- AuthViewModel.swift: Manages authentication state and user credentials
- Handles login/logout operations
- Manages authentication state transitions
- Integrates with AuthService for authentication operations
- ServerListViewModel.swift: Manages server data and sorting
- Handles server data fetching and caching
- Implements sorting by distance and alphabetically
- Manages loading states and error handling
- Integrates with ServerService for server operations
- Protocol:
NetworkServiceProtocol - Implementation:
NetworkService - Responsibilities:
- Generic
execute<R: Request>(_ request: R) async throws -> R.ResponseTypemethod - Eliminates code duplication through protocol-based requests
- HTTP error handling with custom
NetworkErrorenum - Automatic error mapping from Alamofire errors
- Works with any request type conforming to
Requestprotocol
- Generic
- Protocol:
AuthServiceProtocol - Implementation:
AuthService - Responsibilities:
- Authentication-specific business logic
- Uses NetworkService internally for API calls
- Single responsibility: only authentication operations
- Protocol:
ServerServiceProtocol - Implementation:
ServerService - Responsibilities:
- Server-related business logic
- Uses NetworkService internally for API calls
- Single responsibility: only server operations
- Protocol:
KeychainServiceProtocol - Implementation:
KeychainService - Responsibilities:
- Secure token storage in iOS Keychain
- Token retrieval and deletion
- Keychain error handling
- Protocol:
PersistentServiceProtocol - Implementation:
UserDefaultsPersistentService - Responsibilities:
- Server data caching with configurable keys
- Cache expiration management (5-minute TTL)
- Data persistence using UserDefaults
- Configurable for testing with different storage keys
- Protocol:
AppRouter - Implementation: Mini router with enum-based navigation
- Responsibilities:
- Centralized navigation logic
- Route management through
AppRouteenum - Clean separation of navigation concerns
- Protocol-Oriented Design: All services use protocols, enabling easy mocking for testing and future implementation changes
- Single Responsibility Principle: Each service has one clear responsibility
- Generic Network Layer: One universal
executemethod handles all request types - Separation of Concerns: Clear boundaries between networking, persistence, security, and UI logic
- Reactive UI: SwiftUI with ObservableObject ensures automatic UI updates
- Enhanced Error Handling: Custom error types with automatic mapping
The networking layer follows a generic, protocol-based approach:
protocol Request: Sendable {
associatedtype ResponseType: Codable & Sendable
var path: String { get }
var method: HTTPMethod { get }
var parameters: [String: Any]? { get }
var headers: HTTPHeaders? { get }
}
// Usage
let loginRequest = LoginNetworkRequest(username: "user", password: "pass")
let response: AuthResponse = try await networkService.execute(loginRequest)Benefits:
- Type Safety: Response type is determined by request type
- Code Reuse: One method handles all network requests
- Easy Extension: Add new requests by conforming to
Requestprotocol - No Code Duplication: Eliminates repetitive networking code
- App Launch: ContentView with AppRouter checks for stored credentials via AuthViewModel
- Authentication: AuthView collects credentials and triggers login through AuthService
- Token Storage: Successful authentication stores token in Keychain
- Navigation: AppRouter manages transition to server list
- Server Loading: ServerListView loads servers via ServerService
- Caching: Servers are cached locally with timestamp for offline access
- UI State Management: Clean switch-based UI states (loading, loaded, error, empty)
The project includes unit tests for critical components with improved isolation:
PersistentServiceTests: Tests for data persistence functionality
- Server saving and loading
- Cache expiration logic
- Data clearing operations
- Isolated Testing: Uses configurable keys to avoid conflicts with production data
- Tests use XCTest framework
- Configurable services eliminate need for separate test implementations
- Mock services can be easily created using protocols
- Each service component is independently testable
- Routing Architecture: Introduce a dedicated
NavigationRouterlayer to separate navigation logic from views. - Dependency Injection: Adopt a DI container (e.g., Resolver or Swift Concurrency–based injection) to enable easy service substitution and unit-testing.
- Reusable Design System: Extract typography, color tokens, and shared UI components into a standalone design-system module for consistent styling.
- Centralized Constants: Move hard-coded numbers, strings, and keys into structured enums such as
UIConstants,LocalizationKeys, andAPIEndpoints. - Core Data Migration: Replace
UserDefaultswith Core Data for higher-performance persistence and scalable querying. - Modularization: Split into separate Swift Packages
- CI/CD: Automated testing and deployment pipeline
- Code Coverage: Increase test coverage to 90%+
- Static Analysis: Integrate SwiftLint and other code quality tools
- Pagination: Add server list pagination for large datasets
- Background Refresh: Implement background app refresh for server data
- Memory Management: Optimize memory usage for large server lists
- Request Deduplication: Prevent duplicate simultaneous requests
- Pull-to-Refresh: Add pull-to-refresh gesture for server list
- Search Functionality: Implement server search and filtering
- Favorites: Allow users to mark favorite servers
- Connection Status: Show server connection status and ping times
- Biometric Authentication: Add Touch ID/Face ID support
- Dark Mode: Enhanced dark mode support with custom themes
- Cache Storage: Uses UserDefaults which has size limitations and security concerns
- Error Recovery: Limited retry mechanisms for network failures
- iOS 18.0+
- Xcode 16.3+
- Swift 6+
- Alamofire: provides sugar syntax for network requests which simplifies API calls specially on the small and medium size projects.
You can find the screen recording here