internal/skills/content/uikit/SKILL.md
UIKit framework guardrails, patterns, and best practices for AI-assisted development. Use when working with UIKit projects, or when the user mentions UIKit. Provides view controller patterns, Auto Layout, table/collection views, and navigation guidelines.
npx skillsauth add ar4mirez/samuel uikitInstall 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.
Applies to: UIKit (iOS 12+), Swift 5.0+, Imperative UI, iOS/tvOS/Mac Catalyst
weak[weak self] in closures, cancel tasks in deinit, break retain cyclesviewDidLoad once; call setupUI(), setupConstraints(), setupBindings()super in lifecycle methodsfinal class for all view controllers unless explicitly designed for subclassingrequired init?(coder:) with fatalError for programmatic-only controllers@objc actions as privatetranslatesAutoresizingMaskIntoConstraints = false for programmatic viewsNSLayoutConstraint.activate([]) to batch-activate constraintssafeAreaLayoutGuide for top/bottom content edges>= and <= constraints with priorities for flexible layoutsUIStackView) to reduce constraint countcompressionResistance and hugging priorities explicitly when needed[weak self] in all escaping closures and Combine sinksweak vardeinit[unowned self] only when the closure cannot outlive self (rare)prepareForReuse() in cells to clear state and cancel image loads{Feature}ViewController (e.g., UserListViewController){Feature}ViewModel (e.g., UserListViewModel){Item}Cell (e.g., UserCell){Feature}Coordinator (e.g., UserCoordinator)PrimaryButton, EmptyStateView){Name}Protocol or {Name}Delegate (e.g., UserServiceProtocol)MyApp/
├── MyApp.xcodeproj
├── MyApp/
│ ├── Application/
│ │ ├── AppDelegate.swift
│ │ ├── SceneDelegate.swift
│ │ └── AppCoordinator.swift
│ ├── Features/
│ │ ├── Authentication/
│ │ │ ├── Controllers/
│ │ │ ├── ViewModels/
│ │ │ ├── Views/
│ │ │ └── Coordinator/
│ │ ├── Home/
│ │ │ ├── Controllers/
│ │ │ ├── ViewModels/
│ │ │ ├── Views/
│ │ │ └── Cells/
│ │ └── Profile/
│ ├── Core/
│ │ ├── Network/
│ │ ├── Storage/
│ │ ├── Extensions/
│ │ └── Utilities/
│ ├── Shared/
│ │ ├── Views/ # Reusable UI components
│ │ ├── Cells/ # Reusable cells
│ │ └── Protocols/ # Shared protocols
│ └── Resources/
│ ├── Assets.xcassets
│ └── Localizable.strings
├── MyAppTests/
└── MyAppUITests/
Core/ for infrastructure (networking, storage, utilities)Shared/ for reusable UI components used across featuresclass BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupConstraints()
setupBindings()
}
func setupUI() { view.backgroundColor = .systemBackground }
func setupConstraints() { /* Override in subclasses */ }
func setupBindings() { /* Override in subclasses */ }
func showError(_ error: Error) {
let alert = UIAlertController(
title: "Error", message: error.localizedDescription, preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
}
import Combine
final class UserListViewController: BaseViewController {
private let viewModel: UserListViewModel
private var cancellables = Set<AnyCancellable>()
weak var coordinator: UserCoordinator?
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .plain)
table.translatesAutoresizingMaskIntoConstraints = false
table.register(UserCell.self, forCellReuseIdentifier: UserCell.identifier)
table.delegate = self
table.dataSource = self
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = 80
return table
}()
init(viewModel: UserListViewModel = UserListViewModel()) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func setupUI() {
super.setupUI()
title = "Users"
view.addSubview(tableView)
}
override func setupConstraints() {
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
override func setupBindings() {
viewModel.$users
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in self?.tableView.reloadData() }
.store(in: &cancellables)
viewModel.$error
.compactMap { $0 }
.receive(on: DispatchQueue.main)
.sink { [weak self] error in self?.showError(error) }
.store(in: &cancellables)
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel.loadUsers()
}
}
import Foundation
import Combine
final class UserListViewModel {
@Published private(set) var users: [User] = []
@Published private(set) var isLoading = false
@Published private(set) var error: Error?
private let userService: UserServiceProtocol
init(userService: UserServiceProtocol = UserService()) {
self.userService = userService
}
func loadUsers() {
isLoading = true
error = nil
Task { @MainActor in
do { users = try await userService.fetchUsers() }
catch { self.error = error }
isLoading = false
}
}
func deleteUser(at index: Int) {
let user = users[index]
users.remove(at: index)
Task {
do { try await userService.deleteUser(id: user.id) }
catch { await MainActor.run { users.insert(user, at: index); self.error = error } }
}
}
}
extension UserListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel.users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(
withIdentifier: UserCell.identifier, for: indexPath
) as? UserCell else { return UITableViewCell() }
cell.configure(with: viewModel.users[indexPath.row])
return cell
}
}
extension UserListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
coordinator?.showUserDetail(viewModel.users[indexPath.row])
}
}
enum Section { case main }
typealias DataSource = UICollectionViewDiffableDataSource<Section, Photo>
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Photo>
private func setupDataSource() {
let registration = UICollectionView.CellRegistration<PhotoCell, Photo> { cell, _, photo in
cell.configure(with: photo)
}
dataSource = DataSource(collectionView: collectionView) { cv, indexPath, photo in
cv.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: photo)
}
}
private func applySnapshot(photos: [Photo], animating: Bool = true) {
var snapshot = Snapshot()
snapshot.appendSections([.main])
snapshot.appendItems(photos)
dataSource.apply(snapshot, animatingDifferences: animating)
}
private func createGridLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1/3),
heightDimension: .fractionalWidth(1/3)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(1/3)
)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
return UICollectionViewCompositionalLayout(section: NSCollectionLayoutSection(group: group))
}
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get set }
func start()
}
extension Coordinator {
func addChild(_ coordinator: Coordinator) {
childCoordinators.append(coordinator)
}
func removeChild(_ coordinator: Coordinator) {
childCoordinators.removeAll { $0 === coordinator }
}
}
final class AppCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
var navigationController: UINavigationController
private let authManager: AuthManager
init(navigationController: UINavigationController, authManager: AuthManager = .shared) {
self.navigationController = navigationController
self.authManager = authManager
}
func start() {
authManager.isAuthenticated ? showMainFlow() : showAuthFlow()
}
private func showAuthFlow() {
let coordinator = AuthCoordinator(navigationController: navigationController)
coordinator.delegate = self
addChild(coordinator)
coordinator.start()
}
private func showMainFlow() {
let coordinator = MainTabCoordinator(navigationController: navigationController)
addChild(coordinator)
coordinator.start()
}
}
protocol AddUserViewControllerDelegate: AnyObject {
func addUserDidSave(_ controller: AddUserViewController, user: User)
func addUserDidCancel(_ controller: AddUserViewController)
}
final class AddUserViewController: BaseViewController {
weak var delegate: AddUserViewControllerDelegate?
@objc private func saveTapped() {
guard let user = buildUser() else { return }
delegate?.addUserDidSave(self, user: user)
}
@objc private func cancelTapped() {
delegate?.addUserDidCancel(self)
}
}
final class UserCell: UITableViewCell {
static let identifier = "UserCell"
private let nameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .headline)
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
private func setupUI() {
contentView.addSubview(nameLabel)
NSLayoutConstraint.activate([
nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
nameLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
])
}
func configure(with user: User) { nameLabel.text = user.name }
override func prepareForReuse() {
super.prepareForReuse()
nameLabel.text = nil
}
}
import XCTest
@testable import MyApp
final class UserListViewControllerTests: XCTestCase {
var sut: UserListViewController!
var mockViewModel: MockUserListViewModel!
override func setUp() {
super.setUp()
mockViewModel = MockUserListViewModel()
sut = UserListViewController(viewModel: mockViewModel)
sut.loadViewIfNeeded()
}
override func tearDown() {
sut = nil; mockViewModel = nil
super.tearDown()
}
func test_viewDidLoad_loadsUsers() {
XCTAssertTrue(mockViewModel.loadUsersCalled)
}
func test_tableView_rowCount_matchesUsers() {
mockViewModel.users = [User.stub(), User.stub()]
let count = sut.tableView(sut.tableView, numberOfRowsInSection: 0)
XCTAssertEqual(count, 2)
}
}
loadViewIfNeeded() to trigger viewDidLoad without displayingxcodebuild -scheme MyApp -sdk iphonesimulator build # Build
xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 15'
swift format . # Format
swiftlint # Lint
prepareForReuse(); async image loading; diffable data sources; profile with InstrumentspreferredFont); VoiceOver labels; semantic colors (.label, .systemBackground)UINavigationBarAppearance and UITabBarAppearance in AppDelegateFor detailed patterns and examples, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.