20 Essential Swift Extensions Every iOS Developer Must Know

20 Essential Swift Extensions Every iOS Developer Must Know

If you frequently work with UIKit and Swift, you definitely understand why extensions are incredibly useful in iOS development! Extensions allow us to add functionality to existing data types without having to modify the original code.

In this article, we'll explore 20 powerful Swift extensions that can help simplify the development process, reduce repetitive code, and make your iOS applications look better and easier to maintain.

Part 1: UIView Extensions - Making UI Development More Efficient

1. Adding Multiple Subviews at Once

How many times have you written multiple addSubview calls one after another? With this simple extension, you can add multiple subviews in just one function call!

extension UIView {
    func addSubviews(_ views: UIView...) {
        views.forEach { addSubview($0) }
    }
}
✦ How to Use:
view.addSubviews(titleLabel, imageView, descriptionLabel, actionButton)
Benefits:
  • Shorter and more concise code
  • Reduces repetitive .addSubview calls
  • Easier to read and maintain

2. Pinning a View to its Superview

Setting up constraints to pin a view to its superview's edges is something we do often. This extension makes it easier with customizable insets.

extension UIView {
    func pinToSuperview(top: CGFloat = 0, left: CGFloat = 0,
                       bottom: CGFloat = 0, right: CGFloat = 0) {
        guard let superview = superview else {
            print("Error: superview does not exist")
            return
        }

        translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            topAnchor.constraint(equalTo: superview.topAnchor, constant: top),
            leftAnchor.constraint(equalTo: superview.leftAnchor, constant: left),
            bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: -bottom),
            rightAnchor.constraint(equalTo: superview.rightAnchor, constant: -right)
        ])
    }
}
✦ How to Use:
view.pinToSuperview(top: 16, left: 16, bottom: 16, right: 16)
Note: This is especially helpful for those who don't use libraries like SnapKit. This extension can also be modified to work with SnapKit.

3. Animation UIView

Extension to create UIView animations easily, including fade in and fade out effects.

extension UIView {
    /// Chain multiple animations with completion handler
    func animate(
        duration: TimeInterval,
        delay: TimeInterval = 0,
        options: UIView.AnimationOptions = [],
        animations: @escaping (UIView) -> Void,
        completion: ((Bool) -> Void)? = nil
    ) {
        UIView.animate(withDuration: duration, delay: delay,
                      options: options, animations: {
            animations(self)
        }, completion: completion)
    }

    /// Fade in view with customizable parameters
    func fadeIn(duration: TimeInterval = 0.3, delay: TimeInterval = 0,
               completion: ((Bool) -> Void)? = nil) {
        alpha = 0
        isHidden = false
        UIView.animate(withDuration: duration, delay: delay,
                      options: .curveEaseInOut, animations: {
            self.alpha = 1
        }, completion: completion)
    }

    /// Fade out view with customizable parameters
    func fadeOut(duration: TimeInterval = 0.3, delay: TimeInterval = 0,
                completion: ((Bool) -> Void)? = nil) {
        UIView.animate(withDuration: duration, delay: delay,
                      options: .curveEaseInOut, animations: {
            self.alpha = 0
        }, completion: { [weak self] finished in
            self?.isHidden = true
            completion?(finished)
        })
    }
}
✦ How to Use:
// Multiple animations
view.animate(duration: 1.0) { view in
    view.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}

// Fade out animation
view.fadeOut()

// Fade in animation
view.fadeIn()

4. Shake Animation for Error Feedback

A shake animation is a great way to provide visual feedback when validation fails.

extension UIView {
    func shake(duration: TimeInterval = 0.5, repeatCount: Float = 2) {
        let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
        animation.timingFunction = CAMediaTimingFunction(name: .linear)
        animation.duration = duration
        animation.values = [-10, 10, -8, 8, -5, 5, -3, 3, 0]
        animation.repeatCount = repeatCount
        layer.add(animation, forKey: "shake")
    }
}
✦ How to Use:
if passwordTextField.text?.isEmpty ?? true {
    passwordTextField.shake()
    showError("Password cannot be empty")
}

5. Pulse Animation for Highlighting Elements

Need to highlight a specific UI element? This pulse animation is exactly what you need.

extension UIView {
    func pulse(duration: TimeInterval = 0.5, scale: CGFloat = 1.1) {
        UIView.animate(withDuration: duration / 2, animations: {
            self.transform = CGAffineTransform(scaleX: scale, y: scale)
        }, completion: { _ in
            UIView.animate(withDuration: duration / 2) {
                self.transform = CGAffineTransform.identity
            }
        })
    }
}
✦ How to Use:
submitButton.pulse()

6. Rounded Corners for Specific Edges

Sometimes, you might want to round only specific corners of a view.

extension UIView {
    func roundCorners(corners: UIRectCorner, radius: CGFloat) {
        let path = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius)
        )

        let mask = CAShapeLayer()
        mask.path = path.cgPath
        layer.mask = mask
    }
}
✦ How to Use:
cardView.roundCorners(corners: [.topLeft, .topRight], radius: 12)

7. Adding Custom Shadows

Shadows can add depth and dimension to your UI, but setting them up often requires a lot of code.

extension UIView {
    func addShadow(
        color: UIColor = .black,
        opacity: Float = 0.3,
        offset: CGSize = CGSize(width: 0, height: 2),
        radius: CGFloat = 4
    ) {
        layer.shadowColor = color.cgColor
        layer.shadowOpacity = opacity
        layer.shadowOffset = offset
        layer.shadowRadius = radius
        layer.masksToBounds = false
    }
}
✦ How to Use:
profileCard.addShadow(color: .darkGray, opacity: 0.2, radius: 8)

8. Easy Gradient Backgrounds

A gradient background can make your app look more visually appealing, and this extension makes it easy to implement.

extension UIView {
    func addGradient(
        colors: [UIColor],
        startPoint: CGPoint = CGPoint(x: 0.5, y: 0),
        endPoint: CGPoint = CGPoint(x: 0.5, y: 1)
    ) -> CAGradientLayer {
        let gradientLayer = CAGradientLayer()
        gradientLayer.frame = bounds
        gradientLayer.colors = colors.map { $0.cgColor }
        gradientLayer.startPoint = startPoint
        gradientLayer.endPoint = endPoint
        layer.insertSublayer(gradientLayer, at: 0)
        return gradientLayer
    }
}
✦ How to Use:
let gradient = headerView.addGradient(
    colors: [.systemBlue, .systemIndigo],
    startPoint: CGPoint(x: 0, y: 0),
    endPoint: CGPoint(x: 1, y: 1)
)

9. Converting Views to Images

Need to capture a view as an image for sharing or further processing? This extension makes it simple.

extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}
✦ How to Use:
let chartImage = chartView.asImage()
// Now you can share this image via UIActivityViewController

10. Making Any View Touchable

This extension allows any view to respond to touch events using a simple closure-based approach.

extension UIView {
    func makeTouchable(action: @escaping () -> Void) {
        self.isUserInteractionEnabled = true
        let tap = TapGestureRecognizer(action: action)
        tap.numberOfTapsRequired = 1
        self.addGestureRecognizer(tap)
    }

    private class TapGestureRecognizer: UITapGestureRecognizer {
        private var action: () -> Void

        init(action: @escaping () -> Void) {
            self.action = action
            super.init(target: nil, action: nil)
            self.addTarget(self, action: #selector(self.execute))
        }

        @objc private func execute() {
            self.action()
        }
    }
}
✦ How to Use:
infoIcon.makeTouchable {
    self.showInfoAlert()
}

Part 2: Swift Extensions - Productive Development Tools

11. Extension UICollectionView Setup

When working with UICollectionView, managing cells can become repetitive. This extension simplifies cell registration and dequeuing.

import UIKit

extension UICollectionView {
    func registerCellType(_ cellClass: T.Type) {
        let identifier = "\(cellClass)"
        let nib = UINib(nibName: identifier, bundle: Bundle(for: cellClass))
        register(nib, forCellWithReuseIdentifier: identifier)
    }

    func dequeueReusableCell(_ cellClass: T.Type,
                            for indexPath: IndexPath) -> T {
        let identifier = "\(cellClass)"
        guard let cell = dequeueReusableCell(withReuseIdentifier: identifier,
                                             for: indexPath) as? T else {
            fatalError("Error dequeueing cell")
        }
        return cell
    }
}
✦ How to Use:
// Setup
private func setupView() {
    collectionView.dataSource = self
    collectionView.delegate = self
    collectionView.registerCellType(ScheduleTimeCollectionViewCell.self)
}

// DataSource
extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView,
                       cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(
            ScheduleTimeCollectionViewCell.self,
            for: indexPath
        )
        return cell
    }
}

12. Extension UITableView Setup

Similar to UICollectionView, this extension simplifies UITableView setup.

import UIKit

extension UITableView {
    func registerNib(_ cell: T.Type) {
        let identifier = "\(cell)"
        register(UINib(nibName: String(describing: cell), bundle: nil),
                forCellReuseIdentifier: identifier)
    }

    func dequeueReusableCell(_ cell: T.Type,
                            for indexPath: IndexPath) -> T {
        let identifier = "\(cell)"
        guard let cell = dequeueReusableCell(withIdentifier: identifier,
                                             for: indexPath) as? T else {
            fatalError("Error dequeueing cell")
        }
        return cell
    }
}
✦ How to Use:
private func setupTableView() {
    tableView.dataSource = self
    tableView.delegate = self
    tableView.registerNib(StudentTopDetailCell.self)
    tableView.registerNib(StudentMidDetailCell.self)
    tableView.registerNib(StudentBottomDetailCell.self)
    tableView.separatorStyle = .none
}

13. Extension String Validation

Ensuring user input is valid is crucial. This extension adds validation capabilities directly to the String type.

extension String {
    var isValidEmail: Bool {
        let regularExpression = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let predicate = NSPredicate(format: "SELF MATCHES %@", regularExpression)
        return predicate.evaluate(with: self)
    }

    var isValidPhone: Bool {
        let regularExpression = "0[0-9]*"
        let predicate = NSPredicate(format: "SELF MATCHES %@", regularExpression)
        return predicate.evaluate(with: self)
    }

    var isValidPassword: Bool {
        let regularExpression = "(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,}"
        let predicate = NSPredicate(format: "SELF MATCHES %@", regularExpression)
        return predicate.evaluate(with: self)
    }
}
✦ How to Use:
func textFieldDidEndEditing(_ textField: UITextField) {
    switch type {
    case .email:
        if let text = textField.text, text.isEmpty || text.isValidEmail {
            resetError()
        } else {
            setError("Email is not valid")
        }
    case .password:
        if let text = textField.text, text.isEmpty || text.isValidPassword {
            resetError()
        } else {
            setError("Password must contain uppercase, lowercase, number and special character")
        }
    }
}

14. Extension Default String

Managing common string values like empty strings, spaces, and symbols becomes more consistent.

extension String {
    static var empty: String {
        return SC.empty
    }

    static var space: String {
        return SC.space
    }

    static var dash: String {
        return SC.dash
    }
}

typealias SC = StringConstants
enum StringConstants {
    static let empty = ""
    static let space = " "
    static let dash = "-"
}
✦ How to Use:
// Empty variable
private(set) var id: String = .empty

// Optional handling
func set(student: ScheduleStudents) {
    self.id = student.id ?? .empty
}

// Spacing
totalStudentsLabel.text = total + SC.space + studentText

// Placeholder
field?.placeholder = .dash

15. Extension UIApplication Top Controller

Extension to find the topmost view controller that is currently active.

extension UIApplication {
    class func topViewController(_ viewController: UIViewController? =
                                 UIApplication.shared.currentActiveWindow?.rootViewController)
                                 -> UIViewController? {
        if let nav = viewController as? UINavigationController {
            return topViewController(nav.visibleViewController)
        }

        if let tab = viewController as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(selected)
            }
        }

        if let presented = viewController?.presentedViewController {
            return topViewController(presented)
        }
        return viewController
    }

    var currentActiveWindow: UIWindow? {
        return UIApplication.shared.connectedScenes.flatMap {
            ($0 as? UIWindowScene)?.windows ?? []
        }.first { $0.isKeyWindow }
    }
}
✦ How to Use:
private func presentPopup(message: String) {
    let popupViewController = PopoverToastViewController(message: message)
    popupViewController.modalPresentationStyle = .overCurrentContext
    UIApplication.topViewController()?.present(popupViewController, animated: true)
}

16. Extension Date Manipulation

Extension for easy Date manipulation.

extension Date {
    func dayOfWeek() -> Int {
        let calendar = Calendar.current
        let components = calendar.dateComponents([.weekday], from: self)
        return components.weekday!
    }

    func addMonth(n: Int) -> Date {
        let cal = NSCalendar.current
        return cal.date(byAdding: .month, value: n, to: self) ?? Date()
    }

    func addDay(n: Int) -> Date {
        let cal = NSCalendar.current
        return cal.date(byAdding: .day, value: n, to: self) ?? Date()
    }

    func addSec(n: Int) -> Date {
        let cal = NSCalendar.current
        return cal.date(byAdding: .second, value: n, to: self) ?? Date()
    }
}
✦ How to Use:
// Day of week
let day = Date()
print(day.dayOfWeek()) // Thursday = 5

// Add month
@IBAction func nextButtonDidClick(_: UIButton) {
    baseDate = baseDate.addMonth(n: 1)
    monthLabel.text = dateFormatter.string(from: baseDate)
}

// Add seconds
let now = Date()
let futureDate = now.addSec(n: 120) // Add 2 minutes

17. Extension UIImage Base64

Convert images to Base64 strings for upload or API integrations.

extension UIImage {
    enum Format: String {
        case png
        case jpeg
    }

    func toBase64(type: Format = .jpeg, quality: CGFloat = 1,
                 addMimePrefix: Bool = false) -> String? {
        let imageData: Data?
        switch type {
        case .jpeg: imageData = jpegData(compressionQuality: quality)
        case .png: imageData = pngData()
        }
        guard let data = imageData else { return nil }

        let base64 = data.base64EncodedString()
        var result = base64.replacingOccurrences(of: "\"", with: "",
                                                 options: NSString.CompareOptions.literal,
                                                 range: nil)
        if addMimePrefix {
            let prefix = "data:image/\(type.rawValue);base64,"
            result = prefix + base64
        }
        return result
    }
}
✦ How to Use:
func imageUploadButtonDidClick(image: UIImage?) {
    guard let image = image,
          let imageString = image.toBase64(addMimePrefix: true) else { return }
    // Upload imageString to server
}

18. Extension UITextField Add Done Button

Add a "Done" button to the keyboard for easy dismissal.

extension UITextField {
    func addDoneButton() {
        let doneToolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 320, height: 50))
        doneToolbar.barStyle = UIBarStyle.default

        let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
                                        target: nil, action: nil)
        let done = UIBarButtonItem(title: "Done", style: .done,
                                   target: self,
                                   action: #selector(self.doneButtonAction))

        var items = [UIBarButtonItem]()
        items.append(flexSpace)
        items.append(done)

        doneToolbar.items = items
        doneToolbar.sizeToFit()

        self.inputAccessoryView = doneToolbar
    }

    @objc func doneButtonAction() {
        self.endEditing(true)
    }
}
✦ How to Use:
lazy var textField: UITextField = {
    let textField = UITextField()
    textField.addDoneButton() // Add done button
    textField.backgroundColor = .blue
    return textField
}()

19. Extension Converting Type

Simplify type conversions between Int, Double, and String.

extension Int {
    func toString() -> String {
        return String(describing: self)
    }
}

extension Double {
    func toString() -> String {
        return String(describing: self)
    }
}

extension String {
    func toInt() -> Int {
        return Int(self) ?? .zero
    }
}
✦ How to Use:
// Int to String
let number = 42
let numberAsString = number.toString()

// Double to String
let pi = 3.14159
let piAsString = pi.toString()

// String to Int
let validNumber = "123"
let convertedNumber = validNumber.toInt()  // 123

20. Extension UIScrollView (To Top and Bottom)

Scroll to top or bottom programmatically with ease.

public extension UIScrollView {
    func scrollToBottom(animated: Bool, additionalBottomPadding: CGFloat = 0) {
        let bottomOffset = CGPoint(x: 0,
                                  y: contentSize.height - bounds.size.height +
                                     contentInset.bottom + additionalBottomPadding)
        setContentOffset(bottomOffset, animated: animated)
    }

    func scrollToTop() {
        let desiredOffset = CGPoint(x: 0, y: -contentInset.top)
        setContentOffset(desiredOffset, animated: true)
    }
}
✦ How to Use:
// Scroll to top
var currentPage: Int = 1 {
    didSet {
        guard currentPage == 1 else { return }
        collectionView.scrollToTop()
    }
}

// Scroll to bottom when keyboard shows
@objc func keyboardWillShow(notification: NSNotification) {
    let keyboardHeight = tableView.getKeyboardHeight(notification: notification)
    tableView.scrollToBottom(animated: false,
                            additionalBottomPadding: keyboardHeight)
}

Conclusion

Swift extensions are incredibly powerful tools for making your code cleaner, more reusable, and maintainable. By using the 20 extensions we've discussed above, you can:

  • Reduce repetitive code - Write once, use multiple times
  • Improve readability - Code is easier to read and understand
  • Speed up development - Focus on business logic, not boilerplate code
  • Reduce bugs - Centralized logic reduces the chance of errors

These extensions have proven helpful in various iOS projects, from small apps to enterprise-level applications. Start integrating these extensions into your projects and experience the difference!

Happy Coding! 🚀

Pro Tip: Store these extensions in separate files based on category (e.g., UIView+Extensions.swift, String+Extensions.swift) for better organization.

No comments: