Rebuilding Mint’s UI From the Ground Up Using CardParts (iOS)
It’s impossible! Too ambitious! Overhauling the user interface of Mint — a multi-feature iOS application that has not changed significantly since it’s launch in 2008 — seems like a daunting endeavor. How was our development team able to accomplish this redesign? Easy. We used a UI framework we built called CardParts.
What is CardParts?
CardParts is a reactive, card-based UI framework built on top of Apple’s UIKit framework. CardParts brings the MVVM (Model-View-ViewModel) design paradigm to life and allows developers to focus on building a functional and interactive UI rather than reinventing the wheel every time they need to build a new UI.
“…great use of POP to separate out display traits, and 💯 for view controller containment.” — Paul Hudson, Hacking with Swift
The primary intent of CardParts is to provide a UI architecture that is easy to build with and even easier to test. Developers can apply protocol-driven traits to their cards and leverage the MVVM design paradigm to shift complex logic away from view controllers and into view models. Creating this view controller containment allows for the view model logic to be tested easily and separately from view controllers.
What Did We Do?
Now that we had a UI framework that could allow our engineering team to move fast we set out to revamp the Mint user experience. We settled on a design that focused on surfacing cards to users that will help them gain more insight into their personal finances. Of course we call these insight cards MintSights.
To achieve this goal of revamping the customer experience we leveraged everything that CardParts has to offer. We employed a MVVM architecture, and utilized custom traits to fully customize the user experience and provide a wide variety of features in MintSights. Let’s walk through our process for the MintSight card.
The MintSight card is a colorful and uniform card that grabs the user’s attention. Each card contains similar design elements — an icon, title, and description. We were able to create a generic view controller and view model to support this functionality and create a reactive UI. The following are examples of the view controller and view model code:
class OverviewMintsightCardPartController: CardPartsViewController, MintsightTypeProtocol, NoTopBottomMarginsCardTrait {
override func viewDidLoad() {
super.viewDidLoad()
var cardParts: [CardPartView] = []
let title = CardPartTextView(type: .title)
viewModel.title.asObservable().bind(to: title.rx.text).disposed(by: bag) // This is where we are binding to the viewModel
title.textColor = brightFontColor
title.font = UIFont.mintGenericFontBlack(.large22)
title.textAlignment = .center
cardParts.append(title)
let description = CardPartTextView(type: .detail)
viewModel.description.asObservable().bind(to: description.rx.text).disposed(by: bag) // This is where we are binding to the viewModel
description.textColor = brightFontColor
description.font = UIFont.mintGenericFontSemibold(.normal)
description.textAlignment = .center
cardParts.append(description)
// ... and so on
setupCardParts(cardParts, forState: .hasData)
}
}
class OverviewMintsightViewModel {
var type = Variable<MintsightType>(.unknown)
var thumbnailImage: Variable<UIImage?> = Variable(nil)
var title = Variable<String>("")
var description = Variable<String>("")
var state:Variable<CardState> = Variable<CardState>(.hasData)
init() { ... }
}
CardParts utilizes the power of RxSwift to create reactive UIs — binding data from our view models to view controllers. This allows for any changes in the view model data to be reflected immediately in the UI! All that is left is to setup a listener that enabled changes in the internal Realm persistence database to be reflected in the UI. Here is an example of how we accomplished that:
realmNotification = MMProvidersRealm.allProviders().addNotificationBlock({[weak self] (results, change, error) in
guard let this = self, let providers = results as? [MMProvidersRealm], error == nil else { return }
// Apply some logic on providers
this.title.value = computedTitle
this.description.value = computedDescription
this.state.value = .hasData
})
And it was as easy as that! Leveraging CardParts enabled us to create a minimum viable product in just a few days. We were able to spend more time adding complex feature that would provide users with insight into their personal finances.
What we learned
With CardParts we are easily and quickly able to stub out our card-based designs and keep our UI layer up-to-date with every change that came back from the server. The architectural flow went roughly as follows:
- Network Response
- Persist in Realm
- viewModel notified via Realm notification block
- Apply logic on cached data returned from notification block
- Change reactive variables in viewModel
- UI updates due to be being bound to aforementioned reactive variables
Our ViewController practically consists only of UI setup such as fonts, colors, margins, etc. while our viewModels do all the heavy lifting of data manipulation — thus we can easily test these viewModels and ensure they are acting as desired.
Final Thoughts
CardParts has enabled us to move faster than ever before. A feature that might have taken weeks in the past now only takes a few days. We can focus on building algorithms and complex logic to provide the best user experience possible rather than building boilerplate code for every custom UI in the application.
We just recently open-sourced CardParts to allow everyone to leverage its benefits. Visit our GitHub page and help us make CardParts one of the most powerful card-based UI frameworks out there! We hope that CardParts can also help you rebuild your application’s UI in just three weeks!