Written by Marcin Mucha
iOS Developer @ CNP App Kraków
Published May 5, 2020

Robust news feeds using child view controllers in Swift

You open your favourite news app and start reading. What makes you stay there for longer? A front page! If an interesting teaser catches your eyes, it’s more likely you will enter an article to read more.

A front-page is the most important part of news apps and websites, just as it has been with classic newspapers. A group of editors and journalists work constantly to provide the best reading experience. Not only people are involved in this process, but also special algorithms and AI do their job to serve tailored content for users. It’s not only about the front page though, there are also section listings that present teasers from one specific category.

From the presentation layer perspective e.g. iOS app, a section is a similar concept to a front-page: the goal is to present a list of teasers with a given layout and allow navigation to a corresponding article on tapping them.

Sounds easy, but could be challenging sometimes…

In the Core News Product App team, we are developing mobile apps for several news publishers from Norway and Sweden. In the moment of writing, we have 7 apps in App Store:

Norway: Aftenposten, Stavanger Aftenblad, Fædrelandsvennen, Bergens Tidende, Verdens Gang

Sweden: Aftonbladet, Sportbladet

Front page and economy section in Aftonbladet iOS app.

Front page, VG+, and Sport sections in VG iOS app.

The main principle for our team is to share as much code as possible. Instead of writing 7 very similar apps, most of the code is put in the core module, which serves as a base for every app we have. Having a shared codebase and dealing with requirements that often are different among publishers is the most tricky part of our job. Imagine you have seven front pages and more than a hundred section listings counting all the apps. Here are the challenges we face every day:

Backend aspect, presentational API:

In Schibsted, we have a main backend service shared among publishers that apps communicate directly with. However, this is not the case for every feed. There are some feeds that differ in a data format. Some of them are consumed from custom or legacy endpoints. That means the architecture needs to be flexible enough to present basically any data that can be resolved to article teasers.

Native vs. web content

Most of the feeds in our apps are natively rendered but there are some exceptions. If a publisher needs to render one of the feeds in a webview, we need to handle that. What is more, some feeds require to be dynamically switched between native and web versions based on current newsroom decisions. That means even more complex behaviour.

Feeds are not a simple list

We can’t forget about the rest of components a typical news feed contains:

  • advertisements
  • carousel or list widget (list of teasers embedded into a widget)
  • location aware widget (weather forecast, local news)
  • web rendered teasers

Common states

Every feed has the same possible states:

  • loading, an activity indicator is shown
  • presenting content
  • error, showing error message to user and giving the chance to retry by tapping a button

Different brands, different styling

Having 7 different brands onboard requires seven different stylings. Besides each app having its own look, teasers have different layouts, usually resembling classic newspapers. In general, styling is a too large topic to cover in this article. What you will see is a simplification that focuses only on architectural aspects.

Feed loading abstraction

The goal of the blogpost is to present an architectural concept of a reusable container view controller that serves all feeds and deals with mentioned challenges.

The concept consist of two main parts:

  • an abstraction of the loading process that allows to deal with different data formats and helps to determine what type of content is needed to load
  • a container view controller and its child view controllers presenting a current state of the feed

I will focus on the architectural part. Therefore, I’ll simplify data models, UI part, and networking logic to focus on the core of the issue here.

Model

Let’s start by defining a model for a single element in the native feed. A list might contain elements of different types such as various article teaser types, several ad types, and widgets that can include teasers or location-based news. To reflect that, let’s use an enum with three simple cases. Of course, in the real app this model would be substantially more complex.

Let’s create a new enum and call it FeedItem.

enum FeedItem {
    case articleTeaser(title: String)
    case ad(id: String)
    case widget(type: String)
}

Now, we need a type that describes a feed page. As you remember, feed can be rendered natively or web-rendered. Sounds like another enum. There could be some additional pieces of information that are shared and don’t depend on a specific way of rendering. It could be a title of the feed, tracking and styling properties, or basically any metadata connected to the feed. Let’s use a title as an example of such data.

Let’s create a struct Feed that includes those requirements.

struct Feed {
    let title: String?
    let content: Content
    
    enum Content {
        case native([FeedItem])
        case web(URL)
    }
}

Again, we leverage the usage of enum. For native feeds, we need to hold an array of feed items. If a feed is web-rendered, we need the URL of the webpage. In both cases, a feed might have a title if it’s a section listing. The title will be then shown in a navigation bar.

As we’re done with the basic model types, let’s move further.

How to load all the things possible?

As I said, there is no single specific data format for every feed in our apps. Newsroom content APIs and their format are evolving and changing over the years. Remember that our apps serve 7 different Schibsted newspapers. While most feeds are consumed in a unified format, some sections still use legacy endpoints.

Besides that there are also feeds that are always web rendered and feeds that are rendered based on the flag in the response that tells whether to render natively or fallback to web. For those reasons, we need an abstraction that allows us to load feeds in a flexible way and pass information how to render it.

Let’s define a protocol that will describe that.

protocol FeedLoadable {
    func load() -> AnyPublisher<Feed, Error>
}

Protocol consists of a function that returns AnyPublisherfrom Combine framework. For those who are not familiar with it, check out the docs: https://developer.apple.com/documentation/combine/anypublisher

Since loading is an asynchronous process, function needs also return a value in an asynchronous manner. Feed includes information about the content, so we’re good in that aspect. Loading is a process that can fail, so we specify Error type as a failure type.

Now, let’s move on to the specific usage of the created protocol, based on the real challenges we faced during development. Imagine we have three feed pages that we would like to load in the app:

  • Economy section from service X. It represents economic article teasers and it has its own data structure that includes everything that we need including ads and widgets. This feed can be rendered only natively.
  • Lifestyle section from service Y. It contains lifestyle and nutrition related teasers. This time, however, data structure is different from economy feed and it doesn’t include any information about ads or widgets. They need to be injected client-side. This feed is to be rendered natively or as a website depending on renderAsWeb boolean value.
  • Sport section from service Z. It contains sport teasers which can be only web rendered.

Let’s deal with each of them one by one. Our goal at this point is to create loaders that are able to load mentioned sections. They will be key things to use at the later point.

Economy section

First of all, let’s examine the data structure of the economy section. As mentioned, it includes teasers, ads, and widgets. Here’s a sample idea what a response might look like:

{
    "title": "Economy",
    "items": [
        {
            "type": "teaser",
            "title": "Dow sees largest three-day percentage gain since 1931"
        },
        {
            "type": "teaser",
            "title": "Who qualifies to receive money from stimulus package?"
        },
        {
            "type": "ad",
            "id": "123"
        },
        {
            "type": "teaser",
            "title": "Trump pushes to reopen US economy by Easter"
        },
        {
            "type": "teaser",
            "title": "What is the STOCK Act?"
        },
        {
            "type": "widget",
            "subtype": "carousel"
        },
        {
            "type": "ad",
            "id": "276"
        },
        {
            "type": "teaser",
            "title": "Investors uneasy despite promise of economic stimulus"
        },
        {
            "type": "teaser",
            "title": "Wall Street sell-off continues as investors wait for stimulus package from Washington"
        },
        {
            "type": "teaser",
            "title": "5-hour prison hostage standoff in Ireland ends after candy bar exchange"
        },
        {
            "type": "ad",
            "id": "276"
        },
        {
            "type": "teaser",
            "title": "Richard Rossow on President Trump’s trip to India"
        }
    ]
}

We need to define a decodable struct that will be used to parse the response:

struct EconomyPage: Decodable {
    let title: String
    let items: [Item]
}

extension EconomyPage {
    enum CodingKeys: CodingKey {
        case title
        case items
    }
    
    enum Item: Decodable {
        case teaser(title: String)
        case ad(id: String)
        case widget(subtype: String)
    }
}

One tricky thing here is that items array is a heterogeneous collection. As you can see in the JSON file, it can be a teaser, an ad, or a widget. That means, it will be the best to define an item as an enum.

Unfortunately, Swift cannot automatically synthesize the implementation of Decodable for such enum. It means that it needs to be implemented manually. I will not go into details here, you can check out all of the source code by entering the link posted at the end of this article.

Now, it’s time to conform to FeedLoadable protocol. Let’s create a struct called EconomyLoader.

struct EconomyLoader: FeedLoadable {
    private let jsonName: String
    
    init(jsonName: String) {
        self.jsonName = jsonName
    }
    
    func load() -> AnyPublisher<Feed, Error> {
        let future: Future<EconomyPage, Error> = WebService.load(jsonName: jsonName)
        return future.map { page in
            let feedItems = page.items.map(\.asFeedItem)
            return Feed(title: page.title, content: .native(feedItems))
        }
        .eraseToAnyPublisher()
    }
}

 

As you can see, we transform an economy feed page to Feed. As requirement says, this feed is always natively rendered. To make things simple, response is loaded from a locally stored JSON file instead of requesting a real service.

Now, we need to follow similar steps for two more sections keeping their requirements in mind.

Lifestyle section

This time the section has a different data structure that contains no ads or widgets. However, we still want to show them at some predefined spots in the feed. For that reason, we’ll need to inject additional items client-side. Even more interestingly, the section needs to be natively or web rendered according to the renderAsWeb flag in the response. Based on its value, a proper Feed object will be created.

As before, let’s start with the page model. Checkout the JSON file of the sample lifestyle section response:

{
    "title": "Lifestyle & Nutrition",
    "renderAsWeb": true,
    "renderAsWebUrl": "https://www.aftonbladet.se/matdryck",
    "items": [
        {
            "title": "Top 10 healthy storecupboard recipes"
        },
        {
            "title": "Stress relief: How diet and lifestyle can help"
        },
        {
            "title": "Healthy breakfast recipes"
        },
        {
            "title": "Healthy freezable recipes"
        },
        {
            "title": "How to work out at home"
        },
        {
            "title": "276"
        },
        {
            "title": "Creamy pesto & kale pasta"
        },
        {
            "title": "Super smoky bacon & tomato spaghetti"
        },
        {
            "title": "Kidney bean curry"
        },
        {
            "title": "Masala frittata with avocado salsa"
        },
        {
            "title": "Jerk-style chicken pilaf"
        }
    ]
}

As no manual decoding is needed in this case, the model struct is simple:

struct LifestylePage: Decodable {
    let title: String
    let items: [Item]
    
    let renderAsWeb: Bool
    let renderAsWebUrl: URL?
    
    struct Item: Decodable {
        let title: String
    }
}

The only new things are two properties renderAsWeb and renderAsWebUrl. We will make use of these during the loading process:

struct LifestyleLoader: FeedLoadable {
    let jsonName: String
    
    func load() -> AnyPublisher<Feed, Error> {
        let future: Future<LifestylePage, Error> = WebService.load(jsonName: jsonName)
        return future.map { [transform] page in
            if page.renderAsWeb, let renderAsWebUrl = page.renderAsWebUrl {
                return Feed(title: page.title, content: .web(renderAsWebUrl))
            } else {
                let feedItems = page.items.map(\.asFeedItem)
                return Feed(title: page.title, content: .native(feedItems))
            }
        }
        .eraseToAnyPublisher()
    }
}

As you now see, the enum Feed shows its power. This time we don’t hardcode the specific case. We can dynamically choose whether our feed will be rendered natively or web.

However, we still have one more requirement to meet. We want to inject ads and widgets. Loader’s responsibility is to deliver a final model to the view model. It will be the best approach to make any additional changes here, in loader.

We also need to remember that we live in a reality of several newspapers where the code is shared among apps. Thus, we need to be flexible here and cannot simply inject ads and widget at hardcoded indices. Each newspaper wants to have different types of ads in different places in the feed and some of them may or may not want to have widgets there. For that reason, we need to pass information about additional elements from outside by adding a new closure:

struct LifestyleLoader: FeedLoadable {
    let jsonName: String
    let transform: ([FeedItem]) -> [FeedItem]
    
    func load() -> AnyPublisher<Feed, Error> {
        let future: Future<LifestylePage, Error> = WebService.load(jsonName: jsonName)
        return future.map { [transform] page in
            if page.renderAsWeb, let renderAsWebUrl = page.renderAsWebUrl {
                return Feed(title: page.title, content: .web(renderAsWebUrl))
            } else {
                let feedItems = page.items.map(\.asFeedItem)
                let transformedFeedItems = transform(feedItems)
                return Feed(title: page.title, content: .native(transformedFeedItems))
            }
        }
        .eraseToAnyPublisher()
    }
}

Closure transform allows us to transform FeedItem array into a new one. This way, we can do any operation on collection we want for any publisher. Of course, this applies only to a natively rendered feed. If it’s to be web rendered, we don’t do any transformations.

Let’s move on to the last and easiest section – sport.

Sport section

In this case, the section is always web-rendered and we know it upfront. It means we don’t need to deal with any parsing or transformations. We just need a simple loader that can return Feed with web content for a given url. Because of that, let’s give it a generic name WebLoader.

struct WebLoader: FeedLoadable {
    let url: URL
    let title: String?
    
    func load() -> AnyPublisher<Feed, Error> {
        return Result.Publisher(.success(Feed(title: title, content: .web(url))))
            .eraseToAnyPublisher()
    }
}

In this case, what load method does s simply returning immediately with a success value of Feed with a web content. As you can see, the WebLoader is not coupled with any particular section, it can be used for any web feed.

Now, we’re done with all three loaders and it’s time to move on to the second part of our feed architecture.

Custom Container View Controller

Loaders alone have no purpose. We need a way to plug them into a view controller. The goal is to implement a custom container view controller, but let me draw a bigger picture here.

As Apple documentation states:
Container view controllers are a way to combine the content from multiple view controllers into a single user interface.

What are multiple view controllers in our case? Let’s think of states a feed can have:

  • loading
  • presenting native content
  • presenting web content
  • error

By translate these into Swift by creating an enum State:

enum State {
    case loading
    case error(Error)
    case feed(Feed)
}

 

What’s interesting here is by using an associated value of type Feed we can nicely hold information about a type of the feed that is currently presented in the feed.

View model

According to MVVM principles, we’d like to scope down any business logic in the view controllers. Let’s create a FeedViewModel and make it responsible for computing the current state of the feed. How to achieve that? By using loaders from the recent chapter!

final class FeedViewModel {
    enum State {
        case loading
        case error(Error)
        case feed(Feed)
    }
    
    let state: CurrentValueSubject<State, Never> = .init(.loading)
    
    private let loader: FeedLoadable
    
    init(loader: FeedLoadable) {
        self.loader = loader
    }
}

State is defined as CurrentValueSubject<State, Never>. It’s a Combine type that allows us to store a single value and communicate any changes to subscribers. You can read about it more here: https://developer.apple.com/documentation/combine/currentvaluesubject.

We used State and Never as Output and Failure types because we want to store State value and we never want it to error out as we handle errors in one of enum cases.

As you see, we inject loaders in init. By using a protocol FeedLoadable we can inject any loader we want and our FeedViewModel still remains the same! Let’s use the loader to implement the load method.

 func load() {
        state.value = .loading
        loader.load()
            .sink(receiveCompletion: { [weak self] completion in
                switch completion {
                case .failure(let error):
                    self?.state.send(.error(error))
                case .finished: break
                }
                }, receiveValue: { [weak self] feed in
                    self?.state.send(.feed(feed))
            })
            .store(in: &subscriptions)
    }

What we do here, we simply ask a loader to load a feed and we translate a result to the corresponding state. We don’t really care what type of feed it is at this point, because the view model’s responsibility is only to expose a current state to the view controller.

Let’s see the full view model code:

protocol FeedViewModelType {
    func load()
    var state: CurrentValueSubject<FeedViewModel.State, Never> { get }
}

final class FeedViewModel: FeedViewModelType {
    enum State {
        case loading
        case error(Error)
        case feed(Feed)
    }
    
    let state: CurrentValueSubject<State, Never> = .init(.loading)
    
    private let loader: FeedLoadable
    private var subscriptions: Set<AnyCancellable> = []
    
    init(loader: FeedLoadable) {
        self.loader = loader
    }
    
    func load() {
        state.value = .loading
        loader.load()
            .sink(receiveCompletion: { [weak self] completion in
                switch completion {
                case .failure(let error):
                    self?.state.send(.error(error))
                case .finished: break
                }
                }, receiveValue: { [weak self] feed in
                    self?.state.send(.feed(feed))
            })
            .store(in: &subscriptions)
    }
}

 

Additionally, by conforming to the FeedViewModelType protocol, we make the code testable.

Child view controllers

Now, let’s prepare the remaining parts for our container view controller.

As mentioned before, a current state will be represented by using different view controllers. Here are suggested names:

  • loading -> LoadingViewController
  • presenting native feed -> NativeFeedViewController
  • presenting web feed -> WebFeedViewController
  • error -> ErrorViewController

The idea is to add a view controller corresponding to the current state as a child view controller of our container view controller.

Let’s start building our container view controller and call it FeedViewController. First, we need a property that holds current child view controller:

final class FeedViewController: UIViewController {
    ...
    private var currentViewController: UIViewController? {
        didSet {
            updateChildren(add: currentViewController, remove: oldValue)
        }
    ...
}

By using didSet observer, we can swap child view controllers in a convenient way. How to implement updateChildren? We need to remember about a few steps when adding and removing a child view controller.

Adding child view controller

Let’s create an extension to UIViewController to show what needs to be done.

extension UIViewController {
    func add(_ child: UIViewController) {
        addChild(child) // 1
        view.addSubview(child.view) // 2
        child.didMove(toParent: self) // 3
    }
}
  1. Method addChild creates a parent-child relationship between two view controllers which is necessary for a custom container view controller. This method tells UIKit that your container view controller is now managing the view of the child view controller.
  2. We add the child’s view as a subview of the container’s view.
  3. didMove(toParent:) method is required to inform the child that it has a parent view controller now.

Removing child view controller

By analogy, let’s examine the process of removing.

extension UIViewController {
    func remove() {
        if parent != nil {
            willMove(toParent: nil) // 1
            removeFromParent() // 2
        }
        view.removeFromSuperview() // 3
    }
}

1. We tell UIKit that the view controller loses its parent.
2. We remove the child from its parent.
3. Child’s view is unlinked from the superview’s view.

With these extensions, updateChildren method becomes really simple:

private func updateChildren(add viewController: UIViewController?, remove oldViewController: UIViewController?) {
        guard viewController != oldViewController else { return }

        oldViewController?.remove()

        guard let viewController = viewController else { return }

        add(viewController)
        viewController.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            viewController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            viewController.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            viewController.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            viewController.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
    }

After checking if the new child is not the old child, we simply remove the old one and add the new one if it’s not nil. By creating helpers add(_ child:) and remove(), UIKit internal logic was hidden and code became readable and easy. Last thing we need to do is to position the new child’s view. In this case, we pin it to super view using auto-layout.

As we already know how to swap child view controllers, let’s move to the last part: observing FeedViewModel’s state by FeedViewController.

Reacting to state changes

Now, we need to ensure that every state change is reflected by adding a proper child view controller and removing old one. Since we use CurrentValueSubject this job is easy. Let’s subscribe to changes in viewDidLoad:

 override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        
        viewModel.state
            .receive(on: DispatchQueue.main)
            .sink { [weak self] state in
                self?.configure(with: state)
            }
            .store(in: &subscriptions)
        
        viewModel.load()
    }

As we deal with UIKit code when adding child view controllers, we need to be sure that it’s done on the main thread. We can do it by calling .receive(on: DispatchQueue.main) just before .sink. Also, we don’t need to be worried about subscription erroring out and terminating because Failure type of state subject was set to Never.

Finally, let’s configure current child view controller based on feed:

private func configure(with state: FeedViewModel.State) {
        switch state {
        case .loading:
            currentViewController = LoadingViewController()
        case .error(let error):
            currentViewController = ErrorViewController(errorMessage: error.localizedDescription)
        case .feed(let feed):
            switch feed.content {
            case .native(let items):
                currentViewController = NativeFeedViewController(title: feed.title, feedItems: items)
            case .web(let url):
                currentViewController = WebFeedViewController(url: url, title: feed.title)
            }
        }
    }

By using an enum for FeedViewModel.State, we have a nice, readable and predictable code. Every time state changes, the change is reflected in FeedViewController.

It’s likely that you feel now that this code might be shared among different newspapers and feeds. If a new publisher comes in, we just can just use one of existing loaders or create a new custom one, without a need to recreate the whole view controller and view model machinery.

In case of lifestyle section, in order to simulate renderAsWeb changing across responses, I used Bool.random() to compute its value. This way we can see that the feed nicely respects it and renders natively or web depending on the flag.

Lifestyle section

Sport section

If you’re interested in full implementation I invite you to check out the whole sample project: https://github.com/marcin-mucha/RobustFeeds

Conclusion

First of all, thanks for the long journey!

You’ve learned some of the specifics of the news apps. And you’ve found out that a list of teasers you see in your favourite app might be a little bit tricky under the hood.

By using a real project use case, I wanted to show you a bigger picture of challenges we face in our everyday work. You saw how to create an abstraction over loading process with unspecified data structure and how to present such data to the user by using a custom container view controller. I’m sure that at least some parts of the sample project can be useful also in your apps.

P.S. Credits to all CNP App iOS team members for collaboration to this solution in our apps!

Written by Marcin Mucha
iOS Developer @ CNP App Kraków
Published May 5, 2020