ios-uikit-architecture

iOS - UIKit Architecture

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "ios-uikit-architecture" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-ios-uikit-architecture

iOS - UIKit Architecture

Architectural patterns and best practices for UIKit-based iOS applications.

Key Concepts

MVVM Architecture

The Model-View-ViewModel pattern separates concerns:

  • Model: Data and business logic

  • View: UIViewController and UIView subclasses

  • ViewModel: Presentation logic, transforms model data for display

// Model struct User { let id: String let firstName: String let lastName: String let email: String }

// ViewModel class UserProfileViewModel { private let user: User

var displayName: String {
    "\(user.firstName) \(user.lastName)"
}

var emailDisplay: String {
    user.email.lowercased()
}

init(user: User) {
    self.user = user
}

}

// View class UserProfileViewController: UIViewController { private let viewModel: UserProfileViewModel

init(viewModel: UserProfileViewModel) {
    self.viewModel = viewModel
    super.init(nibName: nil, bundle: nil)
}

override func viewDidLoad() {
    super.viewDidLoad()
    nameLabel.text = viewModel.displayName
    emailLabel.text = viewModel.emailDisplay
}

}

Coordinator Pattern

Coordinators handle navigation flow, removing navigation logic from view controllers:

protocol Coordinator: AnyObject { var childCoordinators: [Coordinator] { get set } var navigationController: UINavigationController { get } func start() }

class AppCoordinator: Coordinator { var childCoordinators: [Coordinator] = [] var navigationController: UINavigationController

init(navigationController: UINavigationController) {
    self.navigationController = navigationController
}

func start() {
    let vc = HomeViewController()
    vc.coordinator = self
    navigationController.pushViewController(vc, animated: false)
}

func showDetail(for item: Item) {
    let detailCoordinator = DetailCoordinator(
        navigationController: navigationController,
        item: item
    )
    childCoordinators.append(detailCoordinator)
    detailCoordinator.start()
}

}

Dependency Injection

Inject dependencies through initializers for testability:

protocol UserServiceProtocol { func fetchUser(id: String) async throws -> User }

class UserViewController: UIViewController { private let userService: UserServiceProtocol private let userId: String

init(userService: UserServiceProtocol, userId: String) {
    self.userService = userService
    self.userId = userId
    super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) not supported")
}

}

Best Practices

Programmatic UI with Auto Layout

class ProfileView: UIView { private let avatarImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true imageView.translatesAutoresizingMaskIntoConstraints = false return imageView }()

private let nameLabel: UILabel = {
    let label = UILabel()
    label.font = .preferredFont(forTextStyle: .headline)
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
}()

override init(frame: CGRect) {
    super.init(frame: frame)
    setupViews()
    setupConstraints()
}

private func setupViews() {
    addSubview(avatarImageView)
    addSubview(nameLabel)
}

private func setupConstraints() {
    NSLayoutConstraint.activate([
        avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
        avatarImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
        avatarImageView.widthAnchor.constraint(equalToConstant: 80),
        avatarImageView.heightAnchor.constraint(equalToConstant: 80),

        nameLabel.topAnchor.constraint(equalTo: avatarImageView.bottomAnchor, constant: 12),
        nameLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
    ])
}

}

Modern Collection Views with Diffable Data Source

class ItemListViewController: UIViewController { enum Section { case main }

private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
private var collectionView: UICollectionView!

override func viewDidLoad() {
    super.viewDidLoad()
    configureCollectionView()
    configureDataSource()
}

private func configureCollectionView() {
    let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
    let layout = UICollectionViewCompositionalLayout.list(using: config)
    collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
    collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    view.addSubview(collectionView)
}

private func configureDataSource() {
    let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Item> { cell, indexPath, item in
        var content = cell.defaultContentConfiguration()
        content.text = item.title
        content.secondaryText = item.subtitle
        cell.contentConfiguration = content
    }

    dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
        collectionView, indexPath, item in
        collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
    }
}

func updateItems(_ items: [Item]) {
    var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
    snapshot.appendSections([.main])
    snapshot.appendItems(items)
    dataSource.apply(snapshot, animatingDifferences: true)
}

}

UIKit and SwiftUI Integration

Hosting SwiftUI in UIKit:

class SettingsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    let swiftUIView = SettingsView()
    let hostingController = UIHostingController(rootView: swiftUIView)

    addChild(hostingController)
    view.addSubview(hostingController.view)
    hostingController.view.frame = view.bounds
    hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    hostingController.didMove(toParent: self)
}

}

Wrapping UIKit in SwiftUI:

struct MapViewRepresentable: UIViewRepresentable { @Binding var region: MKCoordinateRegion

func makeUIView(context: Context) -> MKMapView {
    let mapView = MKMapView()
    mapView.delegate = context.coordinator
    return mapView
}

func updateUIView(_ mapView: MKMapView, context: Context) {
    mapView.setRegion(region, animated: true)
}

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

class Coordinator: NSObject, MKMapViewDelegate {
    var parent: MapViewRepresentable

    init(_ parent: MapViewRepresentable) {
        self.parent = parent
    }
}

}

Common Patterns

View Controller Lifecycle Management

class DataViewController: UIViewController { private var loadTask: Task<Void, Never>?

override func viewDidLoad() {
    super.viewDidLoad()
    setupViews()
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    loadData()
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    loadTask?.cancel()
}

private func loadData() {
    loadTask = Task {
        do {
            let data = try await fetchData()
            guard !Task.isCancelled else { return }
            updateUI(with: data)
        } catch {
            showError(error)
        }
    }
}

}

Memory Management with Closures

class NetworkViewController: UIViewController { private let networkService: NetworkService

func fetchData() {
    // Use [weak self] to prevent retain cycles
    networkService.fetch { [weak self] result in
        guard let self else { return }

        switch result {
        case .success(let data):
            self.handleData(data)
        case .failure(let error):
            self.showError(error)
        }
    }
}

}

Anti-Patterns

Massive View Controllers

Bad: Putting everything in one view controller.

Good: Extract into separate types:

  • ViewModels for presentation logic

  • Coordinators for navigation

  • Custom views for UI components

  • Services for network/data operations

Storyboard Segue Spaghetti

Bad: Complex storyboard with many segues.

Good: Use coordinators with programmatic navigation.

Force Casting Cells

Bad:

let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! CustomCell

Good:

guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? CustomCell else { fatalError("Unable to dequeue CustomCell") }

Related Skills

  • ios-swiftui-patterns: Modern declarative UI

  • ios-swift-concurrency: Async data loading

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

android-jetpack-compose

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

storybook-story-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

atomic-design-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review