A Repository Full of Our Best Practices in iOS Development

  —  
 read

Here's a scenario fellow iOS developers will find familiar: iOS development projects you work on come in all shapes and sizes, but as varied as they get, the same pesky problems in them appear repeatedly. And just when you've solved a problem in one project, it reappears in the next one, like in that bunny hammer game.

We've had this happen time and time again at Infinum. Project after project, we would go through best practices and fixes we've implemented in the past (and which Apple still hasn't implemented), copying the solution that worked in the last project into the new one, making any neccessary modifications.

Over time, this has morphed into a knowledge base that became our reference point for upcoming development projects. Now, we're sharing it with the world so that other developers can stop copy/pasting from project to project, and find the solution in a centralized repository instead.

Say hello to Nuts & Bolts!

All about that feature life

If you look at the repository, you'll be able to find all the features inside the Sources folder. Each feature has its sample inside the catalog list which can be run and/or tests written for it, where you can find more info on the specific feature usage. 

Those features are currently split into 5 main domains, and we'll go through the contents of each one. 

Most of these are written in Swift since that's what we mostly use nowadays, but if you're still using Obj-C don't fret. There are some useful things in there for you to use as well. Also, if you have an Obj-C implementation which you think could be useful, please open a PR–we're always open for suggestions.

Before we continue, please note that we are using some third party integrations through CocoaPods to make our lives easier–we don't always need to reinvent the wheel.

Networking made simple

As we all know, pretty much every app we build or use depends on some kind of networking in order to provide its full functionality to the user. From complex apps like banking ones all the way to simple workout tracking, networking is pretty much a must. In order to make it a breeze, we've added several useful properties to this feature folder:

  • Router–Built on top of Alamofire's URLRequestConvertible, Routers allow for easier separation of concerns when it comes to different API calls your application needs to make.
  • Encoding–Used in conjuction with Routers, encoding features are used for creating request params with associated encoding.
  • Adapters– For the times you need to athenticate your users. Our two adapters allow you to do just that, with support for basic authentication, as well as token-based ones.
  • Service–Base protocol for API networking communication uses the previously desribed Router to make API calls.
  • RxService–Reactive implementation of the Service mentioned above, where API calls are wrapped in Singles and Completables, whichever better fits your reactive needs.
  • JSONAPI–Everything necessary to make your life easier when working with the JSON:API standard.

RxSwift

We're big fans of reactive programming here, and we use it in pretty much every project we're currently supporting.

We know that Combine is knocking on the door, but the majority of the projects we're actively working on will have a minimum device support lower than iOS 13 for some time, which is why RxSwift is here to stay for some time. Since it's such a central tool in our toolbox, we decided to make it even better with some improvements.

Observables are their main building blocks, and with these they become even better:

public extension ObservableType {

    /// Maps each sequence elements to given value.
    ///
    /// - Parameter value: Value to map
    /// - Returns: Sequence where all elements are given value.
    func mapTo<T>(_ value: T) -> Observable<T> {
        return map { _ in value }
    }

    /// Behaves like `take(n)`, but with predicate rather then a fixed number of next events.
    /// It will complete once predicate returns `false`.
    ///
    /// - Parameter predicate: Predicate function that will decide if we should continue or complete
    /// - Returns: Observable of type `E`.
    func takeUntil(predicate: @escaping (Element) -> Bool) -> Observable<Element> {
        return Observable.create { observer in
            return self.subscribe { event in
                switch event {
                case .next(let value):
                    observer.on(.next(value))
                    if !predicate(value) {
                        observer.on(.completed)
                    }
                case .error(let error):
                    observer.on(.error(error))
                case .completed:
                    observer.on(.completed)
                }
            }
        }
    }

}

We haven't forgotten about Singles, so be sure to give them some much needed love when you take a look at what's in there.

RxCocoa

When talking about reactive programming for iOS, we can't forget RxCocoa. It provides extensions to all things Cocoa, allowing them to take advantage of the reactive world.

If you use Drivers in your everyday life, these could be just what you've been looking for to drive your projects further than ever before. As you've probably noticed, we love our puns.

/// Safely unwraps optional value from chain
func unwrap<T>() -> Driver<T> where Element == Optional<T> {
    return self
        .filter { $0 != nil }
        .map { $0! }
}

/// Creates new subscription and sends elements to `PublishRelay`.
///
/// - Parameter relay: PublishRelay instance
/// - Returns: Disposable for current subscription
func drive(_ relay: PublishRelay<Element>) -> Disposable {
    return drive(onNext: { e in relay.accept(e) })
}

/// Maps each sequence elements to given value.
///
/// - Parameter value: Value to map
/// - Returns: Sequence where all elements are given value.
func mapTo<T>(_ value: T) -> Driver<T> {
    return map { _ in value }
}

Foundation

This feature folder contains a wide range of useful extensions and computed properties covering many commonly used Foundation features: Arrays, Strings, Bools, Date, Optional and many others.

public extension Array {

    /// Returns object at provided index, if an index is in bounds of array indices,
    /// otherwise, returns nil.
    ///
    /// - Parameter index: Index of the object that you want to get.
    subscript (safe index: Int) -> Element? {
        return indices ~= index ? self[index] : nil
    }
}

public extension Optional {

    /// Executes `function` if optional is .some, otherwise nothing happens.
    ///
    /// - Parameter function: Function to be executed.
    func forValue(do function: (Wrapped) -> ()) {
        if let value = self {
            function(value)
        }
    }

}

public extension Bool {

    /// If the bool is `true` the provided object is returned, otherwise `nil` is returned.
    ///
    /// - Parameter object: Object to be returned if the bool is `true`.
    /// - Returns: `object` if `true`, `nil` otherwise.
    func mapTrue<T>(to object: T) -> T? {
        return self ? object : nil
    }

}

public extension String {

    /// Checks if string is empty or contains only whitespaces and newlines
    ///
    /// Returns `true` if string is empty or contains only whitespaces and newlines
    var isBlank: Bool {
        return trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    }

}

These are just a taste of what we have available there, so be sure to check them out. We're willing to bet that you will find something of use there.

UI

This one is centered around UIKit–mproving it, as well as fixing some issues which we think should come out-of-the-box. 

Ever wanted to initialize colors more easily, or generate their hex values on the fly? Look no further.

public extension UIColor {

    /// Initializes a UIColor object with red, green and blue values,
    /// without the need for dividing the value (red, green or blue) with 255.0,
    /// because this function does that for you.
    ///
    /// - Parameters:
    ///   - r: Red value.
    ///   - g: Green value.
    ///   - b: Blue value.
    ///   - alpha: Alpha value of the color.
    convenience init(r: CGFloat, g: CGFloat, b: CGFloat, alpha: CGFloat = 1) {
        self.init(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: alpha)
    }

        /// Generates a Hex string, from a current color value.
    ///
    /// - Parameter includeAlpha: Boolean value indicating whether the alpha channel should be included in a hex string. Default is false.
    /// - Returns: Hex color string, generated from a current color value.
    func getHex(withAlpha includeAlpha: Bool = false) -> String? {
        guard let components = cgColor.components, components.count >= 3 else {
            return nil
        }

        let r = Float(components[0])
        let g = Float(components[1])
        let b = Float(components[2])
        let a = Float(components.count >= 4 ? components[3] : 1)

        if includeAlpha {
            return String(format: "#%02lX%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255), lroundf(a * 255))
        } else {
            return String(format: "#%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255))
        }
    }
}

Need to remove insets from your UITextView? We got you covered.

public extension UITextView {

    /// Removes all insets from a textView, so it can act more like a label.
    func removeAllInsets() {
        textContainer.lineFragmentPadding = 0
        textContainerInset = .zero
        contentInset = .zero
    }
}

This is the biggest feature folder–for a reason. We love working on UI and strive to make user experience the best it can be, so focusing on improving the tools at our disposal is the least we can do.

Not our first rodeo

If things metioned in this article have sparked your interest, you'll maybe also want to take a peek inside our other open-source iOS stuff:

  • VIPER Xcode templates–Our template generator used to quickly generate all files and structures needed for implementing VIPER app arhitecture.
  • FBAnnotationClustering–A library for clustering map annotations in an easy and performant way, which also has an accompanying blog post.
  • Japx–A lightweight JSON:API parser that flattens complex JSON:API structure and turns it into simple JSON and vice versa.
  • Prince of Versions–A library used for easier versioning of your applications, allowing you to prompt your users to update the app to the newest version.
  • Loggie–Used for capturing all HTTP/S requests the app makes and showing them in a simple table view, enabling easier API debugging.

Your contributions and support is what keeps them evolving as iOS evolves, and we hope that it will continue to be the case.

Go nuts with it

We hope all of these are going to be helpful to you. If you have any suggestions of your own, feel free to open a PR by following steps detailed here. We're always looking forward to learning new things, and can't wait to see wait to see what other tips & tricks our community can come up with.

Until next time, happy coding!

If the cover illustration made you want to grab a cup of tea and get lost in a great book, you can thank Marijana Šimag.

Greetings from our lovely team!
1/4
Achievement unlocked
Resize Master