View Controller Lifecycle

Understanding the view controller lifecycle is crucial for iOS development. Each method serves a specific purpose in the view's lifetime.


class ExampleViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Called once when view loads into memory
        // Set up UI elements, create constraints
        // Views have bounds but may not have correct frame
        setupUI()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // Called every time before view appears
        // Refresh data, start animations
        refreshData()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // Called after view appears
        // Start location services, timers
        startLocationServices()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // Called before view disappears
        // Save user input, pause video
        saveUserData()
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        // Called after view disappears
        // Stop timers, pause expensive operations
        stopLocationServices()
    }
}





AutoLayout Fundamentals

class AutoLayoutExample: UIViewController {
    private let titleLabel = UILabel()
    private let descriptionLabel = UILabel()
    private let actionButton = UIButton(type: .system)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        setupConstraints()
    }
    
    private func setupViews() {
        // Configure views
        titleLabel.text = "Welcome"
        titleLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold)
        titleLabel.textAlignment = .center
        
        descriptionLabel.text = "This is a description"
        descriptionLabel.numberOfLines = 0
        descriptionLabel.textAlignment = .center
        
        actionButton.setTitle("Get Started", for: .normal)
        actionButton.backgroundColor = .systemBlue
        actionButton.setTitleColor(.white, for: .normal)
        actionButton.layer.cornerRadius = 8
        
        // Add to view hierarchy
        view.addSubview(titleLabel)
        view.addSubview(descriptionLabel)
        view.addSubview(actionButton)
        
        // Disable autoresizing masks
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
        actionButton.translatesAutoresizingMaskIntoConstraints = false
    }
    
    private func setupConstraints() {
        NSLayoutConstraint.activate([
            // Title label constraints
            titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 50),
            titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            titleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            // Description label constraints
            descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
            descriptionLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            descriptionLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            // Action button constraints
            actionButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 30),
            actionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            actionButton.widthAnchor.constraint(equalToConstant: 200),
            actionButton.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
}





Frame vs Bounds

// Frame: Position and size relative to superview
// Bounds: Internal coordinate system of the view

class FrameBoundsExample: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let containerView = UIView()
        containerView.backgroundColor = .systemBlue
        containerView.frame = CGRect(x: 50, y: 100, width: 200, height: 200)
        view.addSubview(containerView)
        
        print("Container Frame: \(containerView.frame)")
        // Output: (50.0, 100.0, 200.0, 200.0)
        
        print("Container Bounds: \(containerView.bounds)")
        // Output: (0.0, 0.0, 200.0, 200.0)
        
        // Add subview using bounds
        let subview = UIView()
        subview.backgroundColor = .systemRed
        subview.frame = CGRect(x: 10, y: 10, width: 50, height: 50)
        containerView.addSubview(subview)
        
        // Transform affects frame but not bounds
        containerView.transform = CGAffineTransform(rotationAngle: .pi/4)
        
        print("After rotation - Frame: \(containerView.frame)")
        // Frame changes to encompass rotated view
        
        print("After rotation - Bounds: \(containerView.bounds)")
        // Bounds remain the same: (0.0, 0.0, 200.0, 200.0)
    }
}





Responder Chain

class CustomView: UIView {
    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        print("Touch began in CustomView")
        
        // Handle touch or pass to next responder
        if shouldHandleTouch(touches) {
            handleTouch(touches)
        } else {
            super.touchesBegan(touches, with: event)
        }
    }
    
    private func shouldHandleTouch(_ touches: Set) -> Bool {
        // Custom logic to determine if this view should handle the touch
        return true
    }
    
    private func handleTouch(_ touches: Set) {
        // Handle the touch event
        print("Touch handled by CustomView")
    }
}

class CustomViewController: UIViewController {
    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        print("Touch began in CustomViewController")
        super.touchesBegan(touches, with: event)
    }
    
    // First responder management
    override var canBecomeFirstResponder: Bool {
        return true
    }
    
    override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        if motion == .motionShake {
            print("Device shaken!")
        }
    }
}





Custom Views

class CustomButton: UIView {
    private let titleLabel = UILabel()
    private let iconImageView = UIImageView()
    
    var title: String? {
        didSet {
            titleLabel.text = title
        }
    }
    
    var icon: UIImage? {
        didSet {
            iconImageView.image = icon
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }
    
    private func setupView() {
        backgroundColor = .systemBlue
        layer.cornerRadius = 8
        
        // Setup title label
        titleLabel.textColor = .white
        titleLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium)
        titleLabel.textAlignment = .center
        
        // Setup icon
        iconImageView.contentMode = .scaleAspectFit
        iconImageView.tintColor = .white
        
        // Add subviews
        addSubview(iconImageView)
        addSubview(titleLabel)
        
        // Setup constraints
        iconImageView.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            iconImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            iconImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
            iconImageView.widthAnchor.constraint(equalToConstant: 24),
            iconImageView.heightAnchor.constraint(equalToConstant: 24),
            
            titleLabel.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: 8),
            titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }
    
    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        backgroundColor = .systemBlue.withAlphaComponent(0.7)
    }
    
    override func touchesEnded(_ touches: Set, with event: UIEvent?) {
        backgroundColor = .systemBlue
    }
    
    override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
        backgroundColor = .systemBlue
    }
}





Best Practices

  • Use AutoLayout for responsive design across devices
  • Understand the view lifecycle to optimize performance
  • Leverage the responder chain for event handling
  • Create reusable custom views for consistency
  • Use safe area guides for proper layout on all devices
  • Test on multiple screen sizes and orientations