Building an iOS app around the Unsplash API

Unsplash API

I enjoy discovering new APIs to experiment with.  In this case I came across Unsplash which is a source of high resolution images which are free to use.  The developer API is well documented and it is a simple process to register an app to obtain an API key.

To skip ahead and see what I built using this API, see my Unsplash demo app on GitHub.

An opportunity to build an iOS app

An Image API service provides a nice basis for building an iOS app.   It lends itself to working with table views, collection views, storage and many other iOS features.  The following screenshots show what I was able to achieve.

I used a table to show a list of all the recent photos with thumbnail image and description (the API doesn’t provide a separate title field).

The gallery view (collection) shows the same set of images without the description. Images are presented in circles with borders.

Tapping on an image in either the table or gallery view brings up a full sized version of the image. A button in the navigation bar allows the user to bookmark an image.

A bookmarks screen shows a table view of all the bookmarked images (slightly larger images than the recent photos screen). Support was provided for multiple selection and deletion.

Implementing the Table View

Apart from a standard table view implementation, I decided to implement a couple other features:

  • Refresh with pop-up view presented to the user to indicate that result were loading.
  • A means for endless scrolling – fetching more data once a user reaches a certain point in the current scroll view.  This also presents a pop-up fetching view.

Loading view

I decided to implement a pop-up view in a couple different fashions.

For the initial load and for refresh, I decided to present a simple UIView that was centered over the table. For a presentation such as this, I used the Scene Dock to attach a UIView to the table view controller.

To place the loading view I added constraints to center the view over the table view.   For a commented version of this code, see the GitHub project referenced at the top and bottom of this article.

    func updatePosition(for loadingView: UIView, containedWithin view: UIView) {

        view.constraints.forEach { constraint in
            if constraint.identifier == "loadingViewCenterX" ||  constraint.identifier == "loadingViewCenterY" {
                constraint.isActive = false
            }
        }

        loadingView.translatesAutoresizingMaskIntoConstraints = false

        let centerX = loadingView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0)
        centerX.identifier = "loadingViewCenterX"

        let centerYOffset = -view.safeAreaInsets.bottom/2.0 - view.safeAreaInsets.top/2.0

        let centerY = loadingView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: centerYOffset)
        centerY.identifier = "loadingViewCenterY"

        NSLayoutConstraint.activate([centerX, centerY])
    }

The constraints need to be updated however whenever a rotation between landscape and portrait occurs, since the safe area insets will change.  The following method was overridden to handle this.

    override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()

        imageListViewModel.updatePosition(for: loadingView, containedWithin: view)
    }

Fetching View

Using constraints to place a pop-up view does not work well when the user is scrolling away from the top rows of the table.   To solve this problem, I decided to use a new UIWindow for the purpose.

In the code below,  FetchingView is a UIView that is loaded from a XIB file.  It also conforms to a protocol called NibLoadable  which makes loading from the XIB much easier.   Here is a post where I discovered this technique.

 func showWindowFetchingView(for view: UIView, withText text: String = "Loading ...") {
        currentWindow = view.window

        let fetchingView = FetchingView(frame: CGRect(x: 0, y: 0, width: 200, height: 120), withText: text)
        let fetchingViewController = UIViewController()
        fetchingViewController.view.addSubview(fetchingView)

        guard let windowScene = currentWindow?.windowScene else {
            return
        }

        fetchingWindow = UIWindow(windowScene: windowScene)
        fetchingWindow?.backgroundColor = .clear  // Let root view controller control background color
        fetchingWindow?.rootViewController = fetchingViewController
        fetchingWindow?.makeKeyAndVisible()

        updateFetchingView()
    }

 

Other App Features

As described on the Github page, there are several other features that I incorporated into the app.

Dark Mode support

There were a couple places in the app where some attention to dark mode was needed:

  • Placeholder image tint.
  • Spinner color on loading and fetching view.

This was handled easily by defining a color property such as:

        static let spinnerColor = UIColor {(traitCollection: UITraitCollection) -> UIColor in
            if traitCollection.userInterfaceStyle == .dark {
                return spinnerColorDark
            } else {
                return spinnerColorLight
            }
        }

Miscellaneous Features

  • Core Data and NSFetchedResultsController used with bookmark storage.
  • Combine made it easier to react to changes to the bookmarks.
  • SwiftLint was used to enforce a clean coding style.
  • Alamofire and AlamofireImage (loaded as a Swift Package) simplified networking and image fetching respectively.

GitHub project

The Unsplash demo project has been posted to GitHub.

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s