ios-dependency-injection/SKILL.md
Implement dependency injection patterns for iOS apps, including containers, factories, and protocol-based injection. Use when designing testable code, creating DI containers, implementing factory patterns, or making code more modular and testable. Triggers on dependency injection, DI, container, factory, inversion of control, IoC, testable, mock, inject, dependencies.
npx skillsauth add abanoub-ashraf/manus-skills-import ios-dependency-injectionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in dependency injection for iOS. When this skill activates, help implement clean, testable architectures using DI patterns.
| Benefit | Description | |---------|-------------| | Testability | Swap real implementations with mocks | | Flexibility | Change implementations without modifying consumers | | Modularity | Clear boundaries between components | | Maintainability | Easier to understand and modify |
// Dependencies passed at initialization
class HomeViewModel {
private let apiClient: APIClient
private let analytics: AnalyticsClient
init(apiClient: APIClient, analytics: AnalyticsClient) {
self.apiClient = apiClient
self.analytics = analytics
}
}
// Usage
let viewModel = HomeViewModel(
apiClient: RealAPIClient(),
analytics: FirebaseAnalytics()
)
// Testing
let testViewModel = HomeViewModel(
apiClient: MockAPIClient(),
analytics: MockAnalytics()
)
// Define protocols for dependencies
protocol APIClientProtocol {
func fetch<T: Decodable>(_ endpoint: Endpoint) async throws -> T
}
protocol AnalyticsClientProtocol {
func track(_ event: AnalyticsEvent)
}
// Real implementations
class RealAPIClient: APIClientProtocol {
func fetch<T: Decodable>(_ endpoint: Endpoint) async throws -> T {
// Real implementation
}
}
// Mock implementations
class MockAPIClient: APIClientProtocol {
var stubbedResult: Any?
var capturedEndpoints: [Endpoint] = []
func fetch<T: Decodable>(_ endpoint: Endpoint) async throws -> T {
capturedEndpoints.append(endpoint)
return stubbedResult as! T
}
}
// ViewModel depends on protocols
class ProfileViewModel {
private let apiClient: APIClientProtocol
init(apiClient: APIClientProtocol) {
self.apiClient = apiClient
}
}
// Define environment key
private struct APIClientKey: EnvironmentKey {
static let defaultValue: APIClientProtocol = RealAPIClient()
}
extension EnvironmentValues {
var apiClient: APIClientProtocol {
get { self[APIClientKey.self] }
set { self[APIClientKey.self] = newValue }
}
}
// iOS 17+ simplified
extension EnvironmentValues {
@Entry var analytics: AnalyticsClientProtocol = RealAnalytics()
}
// Usage in views
struct ProfileView: View {
@Environment(\.apiClient) private var apiClient
var body: some View {
// Use apiClient
}
}
// Injection at root
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.apiClient, RealAPIClient())
.environment(\.analytics, FirebaseAnalytics())
}
}
}
// Testing previews
#Preview {
ProfileView()
.environment(\.apiClient, MockAPIClient())
}
// Container holds all dependencies
@MainActor
class DependencyContainer {
// Singletons
lazy var apiClient: APIClientProtocol = RealAPIClient()
lazy var analytics: AnalyticsClientProtocol = FirebaseAnalytics()
lazy var userSession: UserSession = UserSession()
// Factories (new instance each time)
func makeHomeViewModel() -> HomeViewModel {
HomeViewModel(
apiClient: apiClient,
analytics: analytics
)
}
func makeProfileViewModel(userID: String) -> ProfileViewModel {
ProfileViewModel(
userID: userID,
apiClient: apiClient,
userSession: userSession
)
}
}
// Usage
let container = DependencyContainer()
let homeVM = container.makeHomeViewModel()
// Each module defines its dependencies
protocol HomeFeatureDependencies {
var apiClient: APIClientProtocol { get }
var analytics: AnalyticsClientProtocol { get }
}
protocol ProfileFeatureDependencies {
var apiClient: APIClientProtocol { get }
var userSession: UserSession { get }
var imageLoader: ImageLoaderProtocol { get }
}
// Container conforms to all
@MainActor
class AppDependencies: HomeFeatureDependencies, ProfileFeatureDependencies {
let apiClient: APIClientProtocol
let analytics: AnalyticsClientProtocol
let userSession: UserSession
let imageLoader: ImageLoaderProtocol
init() {
self.apiClient = RealAPIClient()
self.analytics = FirebaseAnalytics()
self.userSession = UserSession()
self.imageLoader = AsyncImageLoader()
}
}
// Features only see what they need
class HomeFeature {
init(dependencies: HomeFeatureDependencies) {
// Only has access to apiClient and analytics
}
}
// Package.swift
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0")
]
import Dependencies
// Define dependency key
struct APIClientKey: DependencyKey {
static let liveValue: APIClientProtocol = RealAPIClient()
static let testValue: APIClientProtocol = MockAPIClient()
static let previewValue: APIClientProtocol = PreviewAPIClient()
}
extension DependencyValues {
var apiClient: APIClientProtocol {
get { self[APIClientKey.self] }
set { self[APIClientKey.self] = newValue }
}
}
import Dependencies
@Observable
class HomeViewModel {
@Dependency(\.apiClient) var apiClient
@Dependency(\.analytics) var analytics
var items: [Item] = []
func loadItems() async {
do {
items = try await apiClient.fetch(.items)
analytics.track(.itemsLoaded(count: items.count))
} catch {
// Handle error
}
}
}
import XCTest
import Dependencies
@testable import MyApp
final class HomeViewModelTests: XCTestCase {
func test_loadItems_success() async {
await withDependencies {
$0.apiClient = MockAPIClient(items: [.mock])
} operation: {
let sut = HomeViewModel()
await sut.loadItems()
XCTAssertEqual(sut.items.count, 1)
}
}
func test_loadItems_tracksAnalytics() async {
let mockAnalytics = MockAnalytics()
await withDependencies {
$0.apiClient = MockAPIClient(items: [.mock, .mock])
$0.analytics = mockAnalytics
} operation: {
let sut = HomeViewModel()
await sut.loadItems()
XCTAssertEqual(mockAnalytics.trackedEvents.count, 1)
}
}
}
protocol ViewModelFactory {
associatedtype ViewModel
func make() -> ViewModel
}
struct HomeViewModelFactory: ViewModelFactory {
let apiClient: APIClientProtocol
let analytics: AnalyticsClientProtocol
func make() -> HomeViewModel {
HomeViewModel(apiClient: apiClient, analytics: analytics)
}
}
// Generic factory
struct Factory<T> {
private let builder: () -> T
init(_ builder: @escaping () -> T) {
self.builder = builder
}
func make() -> T {
builder()
}
}
// Usage
let homeVMFactory = Factory {
HomeViewModel(apiClient: apiClient, analytics: analytics)
}
let viewModel = homeVMFactory.make()
// Each feature has a builder
public struct HomeFeatureBuilder {
public init() {}
public func build(
apiClient: APIClientProtocol,
analytics: AnalyticsClientProtocol,
router: RouterProtocol
) -> HomeView {
let viewModel = HomeViewModel(
apiClient: apiClient,
analytics: analytics
)
return HomeView(viewModel: viewModel, router: router)
}
}
// App uses builders
struct AppCoordinator {
let dependencies: AppDependencies
func makeHomeView() -> some View {
HomeFeatureBuilder().build(
apiClient: dependencies.apiClient,
analytics: dependencies.analytics,
router: dependencies.router
)
}
}
class RequestScope {
let requestID: UUID
let startTime: Date
init() {
self.requestID = UUID()
self.startTime = Date()
}
}
class ScopedAPIClient: APIClientProtocol {
private let baseClient: APIClientProtocol
private let scope: RequestScope
init(baseClient: APIClientProtocol, scope: RequestScope) {
self.baseClient = baseClient
self.scope = scope
}
func fetch<T: Decodable>(_ endpoint: Endpoint) async throws -> T {
// Add request ID to headers
var modifiedEndpoint = endpoint
modifiedEndpoint.headers["X-Request-ID"] = scope.requestID.uuidString
return try await baseClient.fetch(modifiedEndpoint)
}
}
class UserScopedDependencies {
let userID: String
let preferences: UserPreferences
let apiClient: AuthenticatedAPIClient
init(userID: String, token: String, baseClient: APIClientProtocol) {
self.userID = userID
self.preferences = UserPreferences(userID: userID)
self.apiClient = AuthenticatedAPIClient(base: baseClient, token: token)
}
}
class MockAPIClientBuilder {
private var responses: [String: Any] = [:]
private var errors: [String: Error] = [:]
func returning<T>(_ value: T, for endpoint: Endpoint) -> Self {
responses[endpoint.path] = value
return self
}
func throwing(_ error: Error, for endpoint: Endpoint) -> Self {
errors[endpoint.path] = error
return self
}
func build() -> MockAPIClient {
MockAPIClient(responses: responses, errors: errors)
}
}
// Usage in tests
let mockClient = MockAPIClientBuilder()
.returning([User.mock], for: .users)
.throwing(NetworkError.offline, for: .profile)
.build()
class SpyAnalytics: AnalyticsClientProtocol {
private(set) var trackedEvents: [AnalyticsEvent] = []
private(set) var identifiedUsers: [String] = []
func track(_ event: AnalyticsEvent) {
trackedEvents.append(event)
}
func identify(userID: String) {
identifiedUsers.append(userID)
}
// Assertions
func didTrack(_ event: AnalyticsEvent) -> Bool {
trackedEvents.contains { $0.name == event.name }
}
}
// Inject dependencies through initializer
init(apiClient: APIClientProtocol) { }
// Use protocols for dependencies
protocol UserRepositoryProtocol { }
// Keep containers thin - just wiring
class Container {
func makeViewModel() -> ViewModel { }
}
// Don't use singletons directly
let data = APIClient.shared.fetch()
// Don't create dependencies internally
class ViewModel {
let apiClient = RealAPIClient() // Hard to test!
}
// Don't pass container everywhere
init(container: DependencyContainer) // Too broad
development
Design principles for building polished, native-feeling SwiftUI apps and widgets. Use this skill when creating or modifying SwiftUI views, iOS widgets (WidgetKit), or any native Apple UI. Ensures proper spacing, typography, colors, and widget implementations that look and feel like quality apps rather than AI-generated slop.
data-ai
Design and implement SwiftUI views, components, and app architecture. Use when creating new SwiftUI views, implementing MVVM/TCA patterns, managing state with @Observable, @State, @Binding, or @Environment, designing navigation flows, or structuring iOS app architecture. Triggers on SwiftUI, view model, state management, navigation, coordinator pattern.
development
Implement, review, or improve SwiftUI animations and transitions. Use when adding implicit or explicit animations with withAnimation, configuring spring animations (.smooth, .snappy, .bouncy), building phase or keyframe animations with PhaseAnimator/KeyframeAnimator, creating hero transitions with matchedGeometryEffect or matchedTransitionSource, adding SF Symbol effects (bounce, pulse, variableColor, breathe, rotate, wiggle), implementing custom Transition or CustomAnimation types, or ensuring animations respect accessibilityReduceMotion.
testing
Audit SwiftUI views for accessibility (iOS + macOS) with patch-ready fixes