[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7129":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":15,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":16,"rankGlobal":9,"rankLanguage":9,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":28,"readmeContent":29,"aiSummary":30,"trendingCount":15,"starSnapshotCount":15,"syncStatus":31,"lastSyncTime":32,"discoverSource":33},7129,"CardParts","intuit\u002FCardParts","intuit","A reactive, card-based UI framework built on UIKit for iOS developers.",null,"Swift",2522,221,46,21,0,59.04,"Other",false,"master",true,[22,23,24,25,26,27],"cardparts","cards","ios","swift","ui","uikit","2026-06-12 04:00:32","\u003Cp align=\"center\">\n    \u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FcardPartsLogo.png\" width=\"300\" alt=\"Mint Logo\"\u002F>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\"> \u003C\u002Fp>\n\n[![Build Status](https:\u002F\u002Ftravis-ci.org\u002Fintuit\u002FCardParts.svg?branch=master)](https:\u002F\u002Ftravis-ci.org\u002Fintuit\u002FCardParts)\n[![Version](https:\u002F\u002Fimg.shields.io\u002Fcocoapods\u002Fv\u002FCardParts.svg?style=flat)](http:\u002F\u002Fcocoapods.org\u002Fpods\u002FCardParts)\n[![License](https:\u002F\u002Fimg.shields.io\u002Fcocoapods\u002Fl\u002FCardParts.svg?style=flat)](http:\u002F\u002Fcocoapods.org\u002Fpods\u002FCardParts)\n[![Platform](https:\u002F\u002Fimg.shields.io\u002Fcocoapods\u002Fp\u002FCardParts.svg?style=flat)](http:\u002F\u002Fcocoapods.org\u002Fpods\u002FCardParts)\n\n\u003Cp align=\"center\">\n    \u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002Fmintsights.gif\" width=\"290\" alt=\"MintSights by CardParts\"\u002F>\n    \u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FmintCardParts.gif\" width=\"290\" alt=\"CardParts in Mint\"\u002F>\n    \u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FturboCardParts.gif\" width=\"290\" alt=\"CardParts in Turbo\"\u002F>\n\u003C\u002Fp>\n\nCardParts - made with ❤️ by Intuit:\n\n- [Example](#example)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Communication & Contribution](#communication-and-contribution)\n- [Overview](#overview)\n- [Quick Start](#quick-start)\n- [Architecture](#architecture)\n  - [CardsViewController](#cardsviewcontroller)\n    - [Load Specific Cards](#load-specific-cards)\n    - [Custom Card Margins](#custom-card-margins)\n  - [Card Traits](#card-traits)\n    - [NoTopBottomMarginsCardTrait](#notopbottommarginscardtrait)\n    - [TransparentCardTrait](#transparentcardtrait)\n    - [EditableCardTrait](#editablecardtrait)\n    - [HiddenCardTrait](#hiddencardtrait)\n    - [ShadowCardTrait](#shadowcardtrait)\n    - [RoundedCardTrait](#roundedcardtrait)\n    - [GradientCardTrait](#gradientcardtrait)\n    - [BorderCardTrait](#bordercardtrait)\n    - [CustomMarginCardTrait](#custommargincardtrait)\n  - [CardPartsViewController](#cardpartsviewcontroller)\n  - [CardPartsFullScreenViewController](#cardpartsfullscreenviewcontroller)\n  - [CardParts](#card-parts)\n    - [CardPartTextView](#cardparttextview)\n    - [CardPartAttributedTextView](#cardpartattributedtextview)\n    - [CardPartImageView](#cardpartimageview)\n    - [CardPartButtonView](#cardpartbuttonview)\n    - [CardPartTitleView](#cardparttitleview)\n    - [CardPartTitleDescriptionView](#cardparttitleview)\n    - [CardPartPillLabel](#cardpartpilllabel)\n    - [CardPartIconLabel](#cardparticonlabel)\n    - [CardPartSeparatorView](#cardpartseparatorview)\n    - [CardPartVerticalSeparatorView](#cardpartverticalseparatorview)\n    - [CardPartTableView](#cardparttableview)\n    - [CardPartTableViewCell](#cardparttableviewcell)\n    - [CardPartTableViewCardPartsCell](#cardparttableviewcardpartscell)\n    - [CardPartCollectionView](#cardpartcollectionview)\n    - [CardPartCollectionViewCardPartsCell](#cardpartcollectionviewcardpartscell)\n    - [CardPartBarView](#cardpartbarview)\n    - [CardPartPagedView](#cardpartpagedview)\n    - [CardPartSliderView](#cardpartsliderview)\n    - [CardPartMultiSliderView](#cardpartmultisliderview)\n    - [CardPartSpacerView](#cardpartspacerview)\n    - [CardPartTextField](#cardparttextfield)\n    - [CardPartCenteredView](#cardpartcenteredview)\n    - [CardPartOrientedView](#cardpartorientedview)\n    - [CardPartConfettiView](#cardpartconfettiview)\n    - [CardPartProgressBarView](#cardpartprogressbarview)\n    - [CardPartMapView](#cardpartmapview)\n    - [CardPartRadioButton](#cardpartradiobutton)\n    - [CardPartSwitchView](#cardpartswitchview)\n    - [CardPartHistogramView](#cardparthistogramview)\n    - [CardPartsBottomSheetViewController](#cardpartsbottomsheetviewcontroller)\n    - [CardPartVideoView](#cardpartvideoview)\n  - [Card States](#card-states)\n  - [Data Binding](#data-binding)\n  - [Themes](#themes)\n  - [Clickable Cards](#clickable-cards)\n  - [Listeners](#listeners)\n- [Apps That Love CardParts](#apps-that-love-cardparts)\n- [Publications](#publications)\n- [License](#license)\n\n# Example\n\nTo run the example project, clone the repo, and run `pod install` from the Example directory first.\n\nIn `ViewController.swift` you will be able to change the cards displayed and\u002For their order by commenting out one of the `loadCards(cards: )` functions.\nIf you want to change the content of any of these cards, you can look into each of the `CardPartsViewController` you pass into the function such as: `TestCardController`, `Thing1CardController`, `Thing2CardController`, etc.\n\n# Requirements\n\n- iOS 10.0+\n- Xcode 10.2+\n- Swift 5.0+\n- CocoaPods 1.6.1+\n\n# Installation\n\nCardParts is available through [CocoaPods](http:\u002F\u002Fcocoapods.org). You can install it with the following command:\n\n```bash\n$ gem install cocoapods\n```\n\nTo add CardParts to your project, simply add the following line to your Podfile:\n\n```ruby\nsource 'https:\u002F\u002Fgithub.com\u002FCocoaPods\u002FSpecs.git'\nplatform :ios, '10.0'\nuse_frameworks!\n\ntarget '\u003CYour Target Name>' do\n    pod 'CardParts'\nend\n```\n\nThen, run the following command:\n\n```bash\n$ pod install\n```\n\n# Communication and Contribution\n\n- If you **need help**, open an issue and tag as `help wanted`.\n- If you **found a bug**, open an issue and tag as `bug`.\n- If you **have a feature request**, open an issue and tag as `feature`.\n- If you **want to contribute**, submit a pull request.\n  - In order to submit a pull request, please fork this repo and submit a PR from your forked repo.\n  - Have a detailed message as to what your PR fixes\u002Fenhances\u002Fadds.\n  - Each PR must get two approvals from our team before we will merge.\n\n# Overview\n\nCardParts is the second generation Card UI framework for the iOS Mint application. This version includes many updates to the original card part framework, including improved MVVM, data binding (via [RxSwift](https:\u002F\u002Fgithub.com\u002FReactiveX\u002FRxSwift)), use of stack views and self sizing collection views instead sizing cells, 100% swift and much more. The result is a much simpler, easier to use, more powerful, and easier to maintain framework. This framework is currently used by the iOS Mint application and the iOS Turbo application.\n\n\u003Cp align=\"center\">\n    \u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FcardPartExample.png\" alt=\"CardPart Example in Mint\"\u002F>\n\u003C\u002Fp>\n\n# Quick Start\n\nSee how quickly you can get a card displayed on the screen while adhering to the MVVM design pattern:\n\n```swift\nimport RxCocoa\n\nclass MyCardsViewController: CardsViewController {\n\n    let cards: [CardController] = [TestCardController()]\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        loadCards(cards: cards)\n    }\n}\n\nclass TestCardController: CardPartsViewController  {\n\n    var viewModel = TestViewModel()\n    var titlePart = CardPartTitleView(type: .titleOnly)\n    var textPart = CardPartTextView(type: .normal)\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        viewModel.title.asObservable().bind(to: titlePart.rx.title).disposed(by: bag)\n        viewModel.text.asObservable().bind(to: textPart.rx.text).disposed(by: bag)\n\n        setupCardParts([titlePart, textPart])\n    }\n}\n\nclass TestViewModel {\n\n    var title = BehaviorRelay(value: \"\")\n    var text = BehaviorRelay(value: \"\")\n\n    init() {\n\n        \u002F\u002F When these values change, the UI in the TestCardController\n        \u002F\u002F will automatically update\n        title.accept(\"Hello, world!\")\n        text.accept(\"CardParts is awesome!\")\n    }\n}\n```\n\n_Note:_ `RxCocoa` is required for `BehaviorRelay`, thus you must import it wherever you may find yourself using it.\n\n# Architecture\n\nThere are two major parts to the card parts framework. The first is the `CardsViewController` which will display the cards. It is responsible for displaying cards in the proper order and managing the lifetime of the cards. The second major component is the cards themselves which are typically instances of `CardPartsViewController`. Each instance of `CardPartsViewController` displays the content of a single card, using one or more card parts (more details later).\n\n## `CardsViewController`\n\nThe `CardsViewController` uses a collection view where each cell is a single card. The cells will render the frames for the cards, but are designed to have a child ViewController that will display the contents of the card. Thus `CardsViewController` is essentially a list of child view controllers that are rendered in special cells that draw the card frames.\n\nTo use a `CardsViewController`, you first need to subclass it. Then in the `viewDidLoad` method call the super class `loadCards` method passing an array of `CardController`s. Each instance of a `CardController` will be rendered as a single card. The `loadCards` method does this by getting the view controller for each `CardController` and adding them as child view controllers to the card cells. Here is an example:\n\n```swift\nclass TestCardsViewController: CardsViewController {\n\n    let cards: [CardController] = [TestCardController(), AnotherTestCardController()]\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        loadCards(cards: cards)\n    }\n}\n```\n\nEach card must implement the CardController protocol (note that `CardPartsViewController` discussed later implements this protocol already). The\nCardController protocol has a single method:\n\n```swift\nprotocol CardController : NSObjectProtocol {\n\n    func viewController() -> UIViewController\n\n}\n```\n\nThe viewController() method must return the viewController that will be added as a child controller to the card cell. If the CardController is a UIViewController it can simply return self for this method.\n\n### Load specific cards\n\nWhile normally you may call `loadCards(cards:)` to load an array of CardControllers, you may want the ability to load reload a specific set of cards. We offer the ability via the `loadSpecificCards(cards: [CardController] , indexPaths: [IndexPath])` API. Simply pass in the full array of new cards as well as the indexPaths that you would like reloaded.\n\n### Custom Card Margins\n\nBy default, the margins of your `CardsViewController` will match the theme's `cardCellMargins` property. You can change the margins for all `CardsViewController`s in your application by applying a new [theme](#themes) or setting `CardParts.theme.cardCellMargins = UIEdgeInsets(...)`. Alternatively, if you want to change the margins for just one `CardsViewController`, you can set the `cardCellMargins` property of that `CardsViewController`. To change the margin for an individual card see [`CustomMarginCardTrait`](#custommargincardtrait). This property will default to use the theme's margins if you do not specify a new value for it. Changing this value should be done in the `init` of your custom `CardsViewController`, but must occur after `super.init` because it is changing a property of the super class. For example:\n\n```swift\nclass MyCardsViewController: CardsViewController {\n\n\tinit() {\n\t\t\u002F\u002F set up properties\n\t\tsuper.init(nibName: nil, bundle: nil)\n\t\tself.cardCellMargins = UIEdgeInsets(\u002F* custom card margins *\u002F)\n\t}\n\n\t...\n}\n```\n\nIf you use storyboards with `CardsViewController` subclasses in your storyboard, the `cardCellMargins` property will take the value of the `CardParts.theme.cardCellMargins` when the `required init(coder:)` initializer is called. If you are trying to change the theme for your whole application, you will need to do so in this initializer of the first view controller in your storyboard to be initialized, and changes will take effect in all other view controllers. For example:\n\n```swift\nrequired init?(coder: NSCoder) {\n\tYourCardPartTheme().apply()\n\tsuper.init(coder: coder)\n}\n```\n\n## Card Traits\n\nThe Card Parts framework defines a set of traits that can be used to modify the appearance and behavior of cards. These traits are implemented as protocols and protocol extensions. To add a trait to a card simply add the trait protocol to the CardController definition. For example:\n\n```swift\nclass MyCard: UIViewController, CardController, TransparentCardTrait {\n\n}\n```\n\nMyCard will now render itself with a transparent card background and frame. No extra code is needed, just adding the TransparentCardTrait as a protocol is all that is necessary.\n\nMost traits require no extra code. The default protocol extensions implemented by the framework implement all the code required for the trait to modify the card. A few traits do require implementing a function or property. See the documentation for each trait below for more information.\n\n#### `NoTopBottomMarginsCardTrait`\n\nBy default each card has margin at the top and bottom of the card frame. Adding the NoTopBottomMarginsCardTrait trait will remove that margin allowing the card to render to use the entire space inside the card frame.\n\n#### `TransparentCardTrait`\n\nEach card is rendered with a frame that draws the border around the card. Adding TransparentCardTrait will not display that border allowing the card to render without a frame.\n\n#### `EditableCardTrait`\n\nIf the EditableCardTrait trait is added, the card will be rendered with an edit button in upper right of the card. When user taps in the edit button, the framework will call the cards _onEditButtonTap()_ method. The EditableCardTrait protocol requires the CardController to implement the _onEditButtonTap()_ method.\n\n#### `HiddenCardTrait`\n\nThe HiddenCardTrait trait requires the CardController to implement an isHidden variable:\n\n```swift\n    var isHidden: BehaviorRelay\u003CBool> { get }\n```\n\nThe framework will then observe the isHidden variable so that whenever its value is changed the card will be hidden or shown based upon the new value. This allows the CardController to control its visibility by simply modifying the value of its isHidden variable.\n\n#### `ShadowCardTrait`\n\nThe ShadowCardTrait protocol requires CardController to implement `shadowColor()`, `shadowRadius()`, `shadowOpacity()` and `shadowOffset()` methods.\n\n```swift\n    func shadowColor() -> CGColor {\n        return UIColor.lightGray.cgColor\n    }\n\n    func shadowRadius() -> CGFloat {\n        return 10.0\n    }\n\n    \u002F\u002F The value can be from 0.0 to 1.0.\n    \u002F\u002F 0.0 => lighter shadow\n    \u002F\u002F 1.0 => darker shadow\n    func shadowOpacity() -> Float {\n        return 1.0\n    }\n\n    func shadowOffset() -> CGSize {\n    \treturn CGSize(width: 0, height: 5)\n    }\n\n```\n\n\u003Cp align=\"center\">\nshadowColor: lightGray, shadowRadius: 5.0, shadowOpacity: 0.5 \u003Cbr\u002F>\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002Fshadow_5.png\" width=\"300\" alt=\"Shadow radius 5.0\"\u002F>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\nshadowColor: lightGray, shadowRadius: 10.0, shadowOpacity: 0.5 \u003Cbr\u002F>\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002Fshadow_10.png\" width=\"300\" alt=\"Shadow radius 10.0\"\u002F>\n\u003C\u002Fp>\n\n#### `RoundedCardTrait`\n\nUse this protocol to define the roundness for the card by implementing the method `cornerRadius()`.\n\n```swift\n    func cornerRadius() -> CGFloat {\n        return 10.0\n    }\n```\n\n\u003Cp align=\"center\">\ncornerRadius: 10.0 \u003Cbr\u002F>\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002Fshadow_5.png\" width=\"300\" alt=\"Shadow radius 5.0\"\u002F>\n\u003C\u002Fp>\n\n#### `GradientCardTrait`\n\nUse this protocol to add a gradient background for the card. The gradients will be added vertically from top to bottom. Optionally you can apply an angle to the gradient. Angles are defined in degrees, any negative or positive degree value is valid.\n\n```swift\n    func gradientColors() -> [UIColor] {\n        return [UIColor.lavender, UIColor.aqua]\n    }\n\n    func gradientAngle() -> Float {\n        return 45.0\n    }\n\n\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002Fgradient.png\" width=\"300\" alt=\"Shadow radius 10.0\"\u002F>\n\u003C\u002Fp>\n\n#### `BorderCardTrait`\n\nUse this protocol to add border color and border width for the card, implement `borderWidth()`, and `borderColor()` methods.\n\n```swift\n    func borderWidth() -> CGFloat {\n        return 2.0\n    }\n\n    func borderColor() -> CGColor {\n        return UIColor.darkGray.cgColor\n    }\n\n\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002Fborder.png\" width=\"300\" alt=\"border\"\u002F>\n\u003C\u002Fp>\n\n#### `CustomMarginCardTrait`\n\nUse this protocol to specifiy a custom margin for the card, implement `customMargin()` method. Value returned will be used for left and right margins thus centering the card in the superview.\n\n```swift\n    func customMargin() -> CGFloat {\n        return 42.0\n    }\n\n\n```\n\n## `CardPartsViewController`\n\nCardPartsViewController implements the CardController protocol and builds its card UI by displaying one or more card part views using an MVVM pattern that includes automatic data binding. Each CardPartsViewController displays a list of CardPartView as its subviews. Each CardPartView renders as a row in the card. The CardParts framework implements several different types of CardPartView that display basic views, such as title, text, image, button, separator, etc. All CardPartView implemented by the framework are already styled to correctly match the applied\ns UI guidelines.\n\nIn addition to the card parts, a CardPartsViewController also uses a view model to expose data properties that are bound to the card parts. The view model should contain all the business logic for the card, thus keeping the role of the CardPartsViewController to just creating its view parts and setting up bindings from the view model to the card parts. A simple implementation of a CardPartsViewController based card might look like the following:\n\n```swift\nclass TestCardController: CardPartsViewController  {\n\n    var viewModel = TestViewModel()\n    var titlePart = CardPartTitleView(type: .titleOnly)\n    var textPart = CardPartTextView(type: .normal)\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        viewModel.title.asObservable().bind(to: titlePart.rx.title).disposed(by: bag)\n        viewModel.text.asObservable().bind(to: textPart.rx.text).disposed(by: bag)\n\n        setupCardParts([titlePart, textPart])\n    }\n}\n\nclass TestViewModel {\n\n    var title = BehaviorRelay(value: \"\")\n    var text = BehaviorRelay(value: \"\")\n\n    init() {\n\n        \u002F\u002F When these values change, the UI in the TestCardController\n        \u002F\u002F will automatically update\n        title.accept(\"Hello, world!\")\n        text.accept(\"CardParts is awesome!\")\n    }\n}\n```\n\nThe above example creates a card that displays two card parts, a title card part and a text part. The bind calls setup automatic data binding between view model properties and the card part view properties so that whenever the view model properties change, the card part views will automatically update with the correct data.\n\nThe call to setupCardParts adds the card part views to the card. It takes an array of CardPartView that specifies which card parts to display, and in what order to display them.\n\n## `CardPartsFullScreenViewController`\n\nThis will make the card a full screen view controller. So if you do not want to build with an array of Cards, instead you can make a singular card full-screen.\n\n```swift\nclass TestCardController: CardPartsFullScreenViewController  {\n    ...\n}\n```\n\n## CardParts\n\nThe framework includes several predefined card parts that are ready to use. It is also possible to create custom card parts. The following sections list all the predefined card parts and their reactive properties that can be bound to view models.\n\n#### `CardPartTextView`\n\nCardPartTextView displays a single text string. The string can wrap to multiple lines. The initializer for CardPartTextView takes a type parameter which can be set to: normal, title, or detail. The type is used to set the default font and textColor for the text.\n\nCardPartTextView exposes the following reactive properties that can be bound to view model properties:\n\n```swift\nvar text: String?\nvar attributedText: NSAttributedString?\nvar font: UIFont!\nvar textColor: UIColor!\nvar textAlignment: NSTextAlignment\nvar lineSpacing: CGFloat\nvar lineHeightMultiple: CGFloat\nvar alpha: CGFloat\nvar backgroundColor: UIColor?\nvar isHidden: Bool\nvar isUserInteractionEnabled: Bool\nvar tintColor: UIColor?\n```\n\n#### `CardPartAttributedTextView`\n\nCardPartAttributedTextView is comparable to CardPartTextView, but it is built upon UITextView rather than UILabel. This allows for CardPartImageViews to be nested within the CardPartAttrbutedTextView and for text to be wrapped around these nested images. In addition, CardPartAttributedTextView allows for links to be set and opened.\nCartPartAttributedTextView exposes the following reactive properties that can be bound to view model properties:\n\n```swift\nvar text: String?\nvar attributedText: NSAttributedString?\nvar font: UIFont!\nvar textColor: UIColor!\nvar textAlignment: NSTextAlignment\nvar lineSpacing: CGFloat\nvar lineHeightMultiple: CGFloat\nvar isEditable: Bool\nvar dataDetectorTypes: UIDataDetectorTypes\nvar exclusionPath: [UIBezierPath]?\nvar linkTextAttributes: [NSAttributedString.Key : Any]\nvar textViewImage: CardPartImageView?\nvar isUserInteractionEnabled: Bool\nvar tintColor: UIColor?\n```\n\n#### `CardPartImageView`\n\nCardPartImageView displays a single image.\nCardPartImageView exposes the following reactive properties that can be bound to view model properties:\n\n```swift\nvar image: UIImage?\nvar imageName: String?\nvar alpha: CGFloat\nvar backgroundColor: UIColor?\nvar isHidden: Bool\nvar isUserInteractionEnabled: Bool\nvar tintColor: UIColor?\n```\n\n#### `CardPartButtonView`\n\nCardPartButtonView displays a single button.\n\nCardPartButtonView exposes the following reactive properties that can be bound to view model properties:\n\n```swift\nvar buttonTitle: String?\nvar isSelected: Bool?\nvar isHighlighted: Bool?\nvar contentHorizontalAlignment: UIControlContentHorizontalAlignment\nvar alpha: CGFloat\nvar backgroundColor: UIColor?\nvar isHidden: Bool\nvar isUserInteractionEnabled: Bool\nvar tintColor: UIColor?\n```\n\n#### `CardPartTitleView`\n\nCardPartTitleView displays a view with a title, and an optional options menu. The initializer requires a type parameter which can be set to either titleOnly or titleWithMenu. If the type is set to titleWithMenu the card part will display a menu icon, that when tapped will display a menu containing the options specified in the menuOptions array. The menuOptionObserver property can be set to a block that will be called when the user selects an item from the menu.\n\nAs an example for a title with menu buttons:\n\n```swift\nlet titlePart = CardPartTitleView(type: .titleWithMenu)\ntitlePart.menuTitle = \"Hide this offer\"\ntitlePart.menuOptions = [\"Hide\"]\ntitlePart.menuOptionObserver  = {[weak self] (title, index) in\n    \u002F\u002F Logic to determine which menu option was clicked\n    \u002F\u002F and how to respond\n    if index == 0 {\n        self?.hideOfferClicked()\n    }\n}\n```\n\nCardPartButtonView exposes the following reactive properties that can be bound to view model properties:\n\n```swift\nvar title: String?\nvar titleFont: UIFont\nvar titleColor: UIColor\nvar menuTitle: String?\nvar menuOptions: [String]?\nvar menuButtonImageName: String\nvar alpha: CGFloat\nvar backgroundColor: UIColor?\nvar isHidden: Bool\nvar isUserInteractionEnabled: Bool\nvar tintColor: UIColor?\n```\n\n#### `CardPartTitleDescriptionView`\n\nCardPartTitleDescriptionView allows you to have a left and right title and description label, however, you are able to also choose the alignment of the right title\u002Fdescription labels. See below:\n\n```swift\nlet rightAligned = CardPartTitleDescriptionView(titlePosition: .top, secondaryPosition: .right) \u002F\u002F This will be right aligned\nlet centerAligned = CardPartTitleDescriptionView(titlePosition: .top, secondaryPosition: .center(amount: 0)) \u002F\u002F This will be center aligned with an offset of 0.  You may increase that amount param to shift right your desired amount\n```\n\n#### `CardPartPillLabel`\n\nCardPartPillLabel provides you the rounded corners, text aligned being at the center along with vertical and horizontal padding capability.\n\n```swift\nvar verticalPadding:CGFloat\nvar horizontalPadding:CGFloat\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FpillLabels.png\" width=\"300\" alt=\"pillLabel\"\u002F>\n\u003C\u002Fp>\n\nSee the example app for a working example.\n\n#### `CardPartIconLabel`\n\nCardPartIconLabel provides the capability to add images in eithet directions supporting left , right and center text alignments along with icon binding capability.\n\n```swift\n    let iconLabel = CardPartIconLabel()\n    iconLabel.verticalPadding = 10\n    iconLabel.horizontalPadding = 10\n    iconLabel.backgroundColor = UIColor.blue\n    iconLabel.font = UIFont.systemFont(ofSize: 12)\n    iconLabel.textColor = UIColor.black\n    iconLabel.numberOfLines = 0\n    iconLabel.iconPadding = 5\n    iconLabel.icon = UIImage(named: \"cardIcon\")\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FcardPartIconLabel.png\" width=\"300\" alt=\"cardPartIconLabel\"\u002F>\n\u003C\u002Fp>\n\n#### `CardPartSeparatorView`\n\nCardPartSeparatorView displays a separator line. There are no reactive properties define for CardPartSeparatorView.\n\n#### `CardPartVerticalSeparatorView`\n\nAs the name describes, it shows a vertical separator view opposed to a horizontal one\n\n#### `CardPartStackView`\n\nCardPartStackView displays a UIStackView that can contain other card parts, and even other CardPartStackViews. Using CardPartStackView allows for creating custom layouts of card parts. By nesting CardPartStackViews you can create almost any layout.\n\nTo add a card part to the stack view call its addArrangedSubview method, specifying the card part's view property as the view to be added to the stack view. For example:\n\n```swift\nhorizStackPart.addArrangedSubview(imagePart)\n```\n\nAlso,provides an option to round the corners of the stackview\n\n```swift\nlet roundedStackView = CardPartStackView()\nroundedStackView.cornerRadius = 10.0\nroundedStackView.pinBackground(roundedStackView.backgroundView, to: roundedStackView)\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FroundedStackView.png\" width=\"300\" alt=\"roundedStackView\"\u002F>\n\u003C\u002Fp>\n\nThere are no reactive properties defined for CardPartStackView. However you can use the default UIStackView properties (distribution, alignment, spacing, and axis) to configure the stack view.\n\n#### `CardPartTableView`\n\nCardPartTableView displays a table view as a card part such that all items in the table view are displayed in the card part (i.e. the table view does not scroll). CardPartTableView leverages Bond's reactive data source support allowing a MutableObservableArray to be bound to the table view.\n\nTo setup the data source binding the view model class should expose MutableObservableArray property that contains the table view's data. For example:\n\n```swift\nvar listData = MutableObservableArray([\"item 1\", \"item 2\", \"item 3\", \"item 4\"])\n```\n\nThen in the view controller the data source binding can be setup as follows:\n\n```swift\nviewModel.listData.bind(to: tableViewPart.tableView) { listData, indexPath, tableView in\n\n    guard let cell = tableView.dequeueReusableCell(withIdentifier: tableViewPart.kDefaultCellId, for: indexPath) as? CardPartTableViewCell else { return UITableViewCell() }\n\n    cell.leftTitleLabel.text = listData[indexPath.row]\n\n    return cell\n}\n```\n\nThe last parameter to the bind call is block that will be called when the tableview's cellForRowAt data source method is called. The first parameter to the block is the MutableObservableArray being bound to.\n\nCardPartTableView registers a default cell class (CardPartTableViewCell) that can be used with no additional work. CardPartTableViewCell contains 4 labels, a left justified title, left justified description, right justified title, and a right justified description. Each label can be optionally used, if no text is specified in a label the cell's layout code will correctly layout the remaining labels.\n\nIt is also possible to register your own custom cells by calling the register method on `tableViewPart.tableView`.\n\nYou also have access to two delegate methods being called by the tableView as follows:\n\n```swift\n@objc public protocol CardPartTableViewDelegate {\n\tfunc tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)\n\t@objc optional func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat\n}\n```\n\n#### `CardPartTableViewCell`\n\nCardPartTableViewCell is the default cell registered for CardPartTableView. The cell contains the following properties:\n\n```swift\nvar leftTitleLabel: UILabel\nvar leftDescriptionLabel: UILabel\nvar rightTitleLabel: UILabel\nvar rightDescriptionLabel: UILabel\nvar rightTopButton: UIButton\nvar shouldCenterRightLabel = false\nvar leftTitleFont: UIFont\nvar leftDescriptionFont: UIFont\nvar rightTitleFont: UIFont\nvar rightDescriptionFont: UIFont\nvar leftTitleColor: UIColor\nvar leftDescriptionColor: UIColor\nvar rightTitleColor: UIColor\nvar rightDescriptionColor: UIColor\n```\n\n#### `CardPartTableViewCardPartsCell`\n\nThis will give you the ability to create custom tableView cells out of CardParts. The following code allows you to create a cell:\n\n```swift\nclass MyCustomTableViewCell: CardPartTableViewCardPartsCell {\n\n    let bag = DisposeBag()\n\n    let attrHeader1 = CardPartTextView(type: .normal)\n    let attrHeader2 = CardPartTextView(type: .normal)\n    let attrHeader3 = CardPartTextView(type: .normal)\n\n    override public init(style: UITableViewCellStyle, reuseIdentifier: String?) {\n        super.init(style: style, reuseIdentifier: reuseIdentifier)\n\n        selectionStyle = .none\n\n        setupCardParts([attrHeader1, attrHeader2, attrHeader3])\n    }\n\n    required init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    func setData(_ data: MyCustomStruct) {\n        \u002F\u002F Do something in here\n    }\n}\n```\n\nIf you do create a custom cell, you must register it to the `CardPartTableView`:\n\n```swift\ntableViewCardPart.tableView.register(MyCustomTableViewCell.self, forCellReuseIdentifier: \"MyCustomTableViewCell\")\n```\n\nAnd then as normal, you would bind to your viewModel's data:\n\n```swift\nviewModel.listData.bind(to: tableViewPart.tableView) { tableView, indexPath, data in\n\n    guard let cell = tableView.dequeueReusableCell(withIdentifier: \"MyCustomTableViewCell\", for: indexPath) as? MyCustomTableViewCell else { return UITableViewCell() }\n\n    cell.setData(data)\n\n    return cell\n}\n```\n\n#### `CardPartCollectionView`\n\nCardPartCollectionView underlying engine is [RxDataSource](https:\u002F\u002Fgithub.com\u002FRxSwiftCommunity\u002FRxDataSources). You can look at their documentation for a deeper look but here is an overall approach to how it works:\n\nStart by initializing a `CardPartCollectionView` with a custom `UICollectionViewFlowLayout`:\n\n```swift\nlazy var collectionViewCardPart = CardPartCollectionView(collectionViewLayout: collectionViewLayout)\nvar collectionViewLayout: UICollectionViewFlowLayout = {\n    let layout = UICollectionViewFlowLayout()\n    layout.minimumInteritemSpacing = 12\n    layout.minimumLineSpacing = 12\n    layout.scrollDirection = .horizontal\n    layout.itemSize = CGSize(width: 96, height: 128)\n    return layout\n}()\n```\n\nNow say you have a custom struct you want to pass into your CollectionViewCell:\n\n```swift\nstruct MyStruct {\n    var title: String\n    var description: String\n}\n```\n\nYou will need to create a new struct to conform to `SectionModelType`:\n\n```swift\nstruct SectionOfCustomStruct {\n    var header: String\n    var items: [Item]\n}\n\nextension SectionOfCustomStruct: SectionModelType {\n\n    typealias Item = MyStruct\n\n    init(original: SectionOfCustomStruct, items: [Item]) {\n        self = original\n        self.items = items\n    }\n}\n```\n\nNext, create a data source that you will bind to you data:\n_Note: You can create a custom CardPartCollectionViewCell as well - see below._\n\n```swift\nlet dataSource = RxCollectionViewSectionedReloadDataSource\u003CSectionOfCustomStruct>(configureCell: {[weak self] (_, collectionView, indexPath, data) -> UICollectionViewCell in\n\n    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: \"Cell\", for: indexPath)\n\n    return cell\n})\n```\n\nFinally, bind your viewModel data to the collectionView and its newly created data source:\n\n```swift\nviewModel.data.asObservable().bind(to: collectionViewCardPart.collectionView.rx.items(dataSource: dataSource)).disposed(by: bag)\n```\n\n_Note: `viewModel.data` will be a reactive array of `SectionOfCustomStruct`_:\n\n```swift\ntypealias ReactiveSection = BehaviorRelay\u003C[SectionOfCustomStruct]>\nvar data = ReactiveSection(value: [])\n```\n\n#### `CardPartCollectionViewCardPartsCell`\n\nJust how CardPartTableViewCell has the ability to create tableView cells out of CardParts - so do CollectionViews. Below is an example of how you may create a custom `CardPartCollectionViewCardPartsCell`:\n\n```swift\nclass MyCustomCollectionViewCell: CardPartCollectionViewCardPartsCell {\n    let bag = DisposeBag()\n\n    let mainSV = CardPartStackView()\n    let titleCP = CardPartTextView(type: .title)\n    let descriptionCP = CardPartTextView(type: .normal)\n\n    override init(frame: CGRect) {\n\n        super.init(frame: frame)\n\n        mainSV.axis = .vertical\n        mainSV.alignment = .center\n        mainSV.spacing = 10\n\n        mainSV.addArrangedSubview(titleCP)\n        mainSV.addArrangedSubview(descriptionCP)\n\n        setupCardParts([mainSV])\n    }\n\n    required init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    func setData(_ data: MyStruct) {\n\n        titleCP.text = data.title\n        descriptionCP.text = data.description\n    }\n}\n```\n\nTo use this, you must register it to the CollectionView during `viewDidLoad` as follows:\n\n```swift\ncollectionViewCardPart.collectionView.register(MyCustomCollectionViewCell.self, forCellWithReuseIdentifier: \"MyCustomCollectionViewCell\")\n```\n\nThen, inside your data source, simply dequeue this cell:\n\n```swift\nlet dataSource = RxCollectionViewSectionedReloadDataSource\u003CSectionOfSuggestedAccounts>(configureCell: {[weak self] (_, collectionView, indexPath, data) -> UICollectionViewCell in\n\n    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: \"MyCustomCollectionViewCell\", for: indexPath) as? MyCustomCollectionViewCell else { return UICollectionViewCell() }\n\n    cell.setData(data)\n\n    return cell\n})\n```\n\n#### `CardPartBarView`\n\nCardPartBarView present a horizontal bar graph that can be filled to a certain percentage of your choice. Both the color of the fill and the percent is reactive\n\n```swift\nlet barView = CardPartBarView()\nviewModel.percent.asObservable().bind(to: barView.rx.percent).disposed(by:bag)\nviewModel.barColor.asObservable().bind(to: barView.rx.barColor).disposed(by: bag)\n```\n\n#### `CardPartPagedView`\n\nThis CardPart allows you to create a horizontal paged carousel with page controls. Simply feed it with your desired height and an array of `CardPartStackView`:\n\n```swift\nlet cardPartPages = CardPartPagedView(withPages: initialPages, andHeight: desiredHeight)\ncardPartPages.delegate = self\n```\n\nThis CardPart also has a delegate:\n\n```swift\nfunc didMoveToPage(page: Int)\n```\n\nWhich will fire whenever the user swipes to another page\n\nYou also have the abililty to automatically move to a specific page by calling the following function on `CardPartPagedView`\n\n```swift\nfunc moveToPage(_ page: Int)\n```\n\n#### `CardPartSliderView`\n\nYou can set min and max value as well as bind to the current set amount:\n\n```swift\nlet slider = CardPartSliderView()\nslider.minimumValue = sliderViewModel.min\nslider.maximumValue = sliderViewModel.max\nslider.value = sliderViewModel.defaultAmount\nslider.rx.value.asObservable().bind(to: sliderViewModel.amount).disposed(by: bag)\n```\n\n#### `CardPartMultiSliderView`\n\nYou can set min and max value as well as tint color and outer track color:\n\n```swift\nlet slider = CardPartMultiSliderView()\nslider.minimumValue = sliderViewModel.min\nslider.maximumValue = sliderViewModel.max\nslider.orientation = .horizontal\nslider.value = [10, 40]\nslider.trackWidth = 8\nslider.tintColor = .purple\nslider.outerTrackColor = .gray\n```\n\n#### `CardPartSpacerView`\n\nAllows you to add a space between card parts in case you need a space larger than the default margin. Initialize it with a specific height:\n\n```swift\nCardPartSpacerView(height: 30)\n```\n\n#### `CardPartTextField`\n\nCardPartTextField can take a parameter of type `CardPartTextFieldFormat` which determines formatting for the UITextField. You may also set properties such as `keyboardType`, `placeholder`, `font`, `text`, etc.\n\n```swift\nlet amount = CardPartTextField(format: .phone)\namount.keyboardType = .numberPad\namount.placeholder = textViewModel.placeholder\namount.font = dataFont\namount.textColor = UIColor.colorFromHex(0x3a3f47)\namount.text = textViewModel.text.value\namount.rx.text.orEmpty.bind(to: textViewModel.text).disposed(by: bag)\n```\n\nThe different formats are as follows:\n\n```swift\npublic enum CardPartTextFieldFormat {\n    case none\n    case currency(maxLength: Int)\n    case zipcode\n    case phone\n    case ssn\n}\n```\n\n#### `CardPartOrientedView`\n\n`CardPartOrientedView` allows you to create an oriented list view of card part elements. This is similar to the `CardPartStackView` except that this view can orient elements to the top or bottom of the view. This is advantageous when you are using horizontal stack views and need elements to be oriented differently (top arranged or bottom arranged) relative to the other views in the horizontal stack view. To see a good example of this element please take a look at the example application.\n\nThe supported orientations are as follows:\n\n```swift\npublic enum Orientation {\n    case top\n    case bottom\n}\n```\n\nTo create an oriented view you can use the following code:\n\n```\nlet orientedView = CardPartOrientedView(cardParts: [\u003Celements to list vertically>], orientation: .top)\n```\n\nAdd the above orientedView to any list of card parts or an existing stack view to orient your elements to the top or bottom of the enclosing view.\n\n#### `CardPartCenteredView`\n\n`CardPartCenteredView` is a CardPart that fits a centered card part proportionally on the phone screen while allowing a left and right side card part to scale appropriately. To create a centered card part please use the following example:\n\n```swift\nclass TestCardController : CardPartsViewController {\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        let rightTextCardPart = CardPartTextView(type: .normal)\n        rightTextCardPart.text = \"Right text in a label\"\n\n        let centeredSeparator = CardPartVerticalSeparator()\n\n        let leftTextCardPart = CardPartTextView(type: .normal)\n        leftTextCardPart.text = \"Left text in a label\"\n\n        let centeredCardPart = CardPartCenteredView(leftView: leftTextCardPart, centeredView: centeredSeparator, rightView: rightTextCardPart)\n\n        setupCardParts([centeredCardPart])\n    }\n}\n```\n\nA `CardPartCenteredView` can take in any card part that conforms to `CardPartView` as the left, center, and right components. To see a graphical example of the centered card part please look at the example application packaged with this cocoapod.\n\n#### `CardPartConfettiView`\n\nProvides the capability to add confetti with various types ( diamonds, star, mixed ) and colors, along with different level of intensity\n\n```swift\n    let confettiView = CardPartConfettiView()\n    confettiView.type  = .diamond\n    confettiView.shape = CAEmitterLayerEmitterShape.line\n    confettiView.startConfetti()\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002Fconfetti.gif\" width=\"300\" alt=\"Confetti\"\u002F>\n\u003C\u002Fp>\n\n### `CardPartProgressBarView`\n\nProvides the capability to configure different colors and custom marker , it's position to indicate the progress based on the value provided.\n\n```swift\n    let progressBarView = CardPartProgressBarView(barValues: barValues, barColors: barColors, marker: nil, markerLabelTitle: \"\", currentValue: Double(720), showShowBarValues: false)\n    progressBarView.barCornerRadius = 4.0\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FprogressBarView.png\" width=\"300\" alt=\"ProgressBarView\"\u002F>\n\u003C\u002Fp>\n\n### `CardPartMapView`\n\nProvides the capability to display a MapView and reactively configure location, map type, and coordinate span (zoom). You also have direct access to the MKMapView instance so that you can add annotations, hook into it's MKMapViewDelegate, or whatever else you'd normally do with Maps.\n\nBy default the card part will be rendered at a height of 300 points but you can set a custom height just be resetting the CardPartMapView.intrensicHeight property.\n\nHere's a small example of how to reactively set the location from a changing address field (See the Example project for a working example):\n\n```swift\n    let initialLocation = CLLocation(latitude: 37.430489, longitude: -122.096260)\n    let cardPartMapView = CardPartMapView(type: .standard, location: initialLocation, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))\n\n    cardPartTextField.rx.text\n            .flatMap { self.viewModel.getLocation(from: $0) }\n            .bind(to: cardPartMapView.rx.location)\n            .disposed(by: bag)\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FmapView.png\" width=\"300\" alt=\"MapView\" \u002F>\n\u003C\u002Fp>\n\n### `CardPartRadioButton`\n\nProvides the capability to add radio buttons with configurable inner\u002Fouter circle line width , colors along with tap etc..\n\n```swift\n    let radioButton = CardPartRadioButton()\n    radioButton.outerCircleColor = UIColor.orange\n    radioButton.outerCircleLineWidth = 2.0\n\n    radioButton2.rx.tap.subscribe(onNext: {\n        print(\"Radio Button Tapped\")\n    }).disposed(by: bag)\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FradioButtons.png\" width=\"300\" alt=\"RadioButton\" \u002F>\n\u003C\u002Fp>\n\n#### `CardPartSwitchView`\n\nProvides the capability to add a switch with configurable colors.\n\n```swift\n    let switchComponent = CardPartSwitchView()\n    switchComponent.onTintColor = .blue\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fcdn-images-1.medium.com\u002Fmax\u002F1600\u002F1*ccfEt-tGF1bDO6sV_yNc8w.gif\" width=\"200\" alt=\"RadioButton\" \u002F>\n\u003C\u002Fp>\n\n### `CardPartHistogramView`\n\nProvides the caoability to generate the bar graph based on the data ranges with customizable bars , lines, colors etc..\n\n```swift\n    let dataEntries = self.generateRandomDataEntries()\n    barHistogram.width = 8\n    barHistogram.spacing = 8\n    barHistogram.histogramLines = HistogramLine.lines(bottom: true, middle: false, top: false)\n    self.barHistogram.updateDataEntries(dataEntries: dataEntries, animated: true)\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002Fhistogram.gif\" width=\"334\" alt=\"Histogram\" \u002F>\n\u003C\u002Fp>\n\n### `CardPartsBottomSheetViewController`\n\nCardPartsBottomSheetViewController provides the capability to show a highly-customizable modal bottom sheet. At its simplest, all you need to do is set the `contentVC` property to a view controller that you create to control the content of the bottom sheet:\n\n```swift\n    let bottomSheetViewController = CardPartsBottomSheetViewController()\n    bottomSheetViewController.contentVC = MyViewController()\n    bottomSheetViewController.presentBottomSheet()\n```\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FbottomSheet.gif\" width=\"300\" alt=\"bottom sheet\" \u002F>\n\u003C\u002Fp>\n\n`CardPartsBottomSheetViewController` also supports being used as a sticky view at the bottom of the screen, and can be presented on any view (default is `keyWindow`). For example, the following code creates a sticky view that still permits scrolling behind it and can only be dismissed programmatically.\n\n```swift\n    let bottomSheetViewController = CardPartsBottomSheetViewController()\n    bottomSheetViewController.contentVC = MyStickyViewController()\n    bottomSheetViewController.configureForStickyMode()\n    bottomSheetViewController.addShadow()\n    bottomSheetViewController.presentBottomSheet(on: self.view)\n```\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FbottomSheetSticky.gif\" width=\"300\" alt=\"sticky bottom sheet\" \u002F>\n\u003C\u002Fp>\n\nThere are also over two dozen other properties that you can set to further customize the bottom sheet for your needs. You can configure the colors, height, gesture recognizers, handle appearance, animation times, and callback functions with the following properties.\n\n- `var contentVC: UIViewController?`: View controller for the content of the bottom sheet. Should set this parameter before presenting bottom sheet.\n- `var contentHeight: CGFloat?`: Manually set a content height. If not set, height will try to be inferred from `contentVC`.\n- `var bottomSheetBackgroundColor: UIColor`: Background color of bottom sheet. Default is white.\n- `var bottomSheetCornerRadius: CGFloat`: Corner radius of bottom sheet. Default is 16.\n- `var handleVC: CardPartsBottomSheetHandleViewController`: Pill-shaped handle at the top of the bottom sheet. Can configure `handleVC.handleHeight`, `handleVC.handleWidth`, and `handleVC.handleColor`.\n- `var handlePosition: BottomSheetHandlePosition`: Positioning of handle relative to bottom sheet. Options are `.above(bottomPadding)`, `.inside(topPadding)`, `.none`. Default is above with padding 8.\n- `var overlayColor: UIColor`: Color of the background overlay. Default is black.\n- `var shouldIncludeOverlay: Bool`: Whether or not to include a background overlay. Default is true.\n- `var overlayMaxAlpha: CGFloat`: Maximum alpha  value of background overlay. Will fade to 0 proportionally with height as bottom sheet is dragged down. Default is 0.5.\n- `var dragHeightRatioToDismiss: CGFloat`: Ratio of how how far down user must have dragged bottom sheet before releasing it in order to trigger a dismissal. Default is 0.4.\n- `var dragVelocityToDismiss: CGFloat`: Velocity that must be exceeded in order to dismiss bottom sheet if height ratio is greater than `dragHeightRatioToDismiss`. Default is 250.\n- `var pullUpResistance: CGFloat`: Amount that the bottom sheet resists being dragged up. Default 5 means that for every 5 pixels the user drags up, the bottom sheet goes up 1 pixel.\n- `var appearAnimationDuration: TimeInterval`: Animation time for bottom sheet to appear. Default is 0.5.\n- `var dismissAnimationDuration: TimeInterval`: Animation time for bottom sheet to dismiss. Default is 0.5.\n- `var snapBackAnimationDuration: TimeInterval`: Animation time for bottom sheet to snap back to its height. Default is 0.25.\n- `var animationOptions: UIView.AnimationOptions`: Animation options for bottom sheet animations. Default is UIView.AnimationOptions.curveEaseIn.\n- `var changeHeightAnimationDuration: TimeInterval`: Animation time for bottom sheet to adjust to a new height when height is changed. Default is 0.25.\n- `var shouldListenToOverlayTap: Bool`: Whether or not to dismiss if a user taps in the overlay. Default is true.\n- `var shouldListenToHandleDrag: Bool`: Whether or not to respond to dragging on the handle. Default is true.\n- `var shouldListenToContentDrag: Bool`: Whether or not to respond to dragging in the content. Default is true.\n- `var shouldListenToContainerDrag: Bool`: Whether or not to respond to dragging in the container. Default is true.\n- `var shouldRequireVerticalDrag: Bool`: Whether or not to require a drag to start in the vertical direction. Default is true.\n- `var adjustsForSafeAreaBottomInset: Bool`: Boolean value for whether or not bottom sheet should automatically add to its height to account for bottom safe area inset. Default is true.\n- `var didShow: (() -> Void)?`: Callback function to be called when bottom sheet is done preseting.\n- `var didDismiss: ((_ dismissalType: BottomSheetDismissalType) -> Void)?`: Callback function to be called when bottom sheet is done dismissing itself. Parameter `dismissalType`: information about how the bottom sheet was dismissed - `.tapInOverlay`, `.swipeDown`, `.programmatic(info)`.\n- `var didChangeHeight: ((_ newHeight: CGFloat) -> Void)?`: Callback function to be called when bottom sheet height changes from dragging or a call to `updateHeight`.\n- `var preferredGestureRecognizers: [UIGestureRecognizer]?`: Gesture recognizers that should block the vertical dragging of bottom sheet. Will automatically find and use all gesture recognizers if nil, otherwise will use recognizers in the array. Default is empty array.\n-  ` var shouldListenToKeyboardNotifications: Bool`: If there is a text field in the bottom sheet we may want to automatically have the bottom sheet adjust for the keyboard. Default is false.\n- `var isModalAccessibilityElement: Bool`: Whether or not to treat the bottom sheet as a modal accessibility element, which will block interaction with views underneath. Default is true. It is not recommended that you override this unless you are using the bottom sheet in sticky mode or otherwise without the overlay.\n- `var allowsAccessibilityGestureToDismiss: Bool`: Whether or not users can use the accessibility escape gesture to dismiss the bottom sheet. Default is true. It is not recommended that you override this unless you are using the bottom sheet in sticky mode or otherwise disabling dismissal or providing another way for VoiceOver users to dismiss.\n\nIf you change the `contentVC` or `contentHeight` properties, the bottom sheet will automatically update its height. You can also call `updateHeight()` to trigger an update of the height (this is mainly for if the content of the `contentVC` has changed and you want the bottom sheet to update to match the new content size).\n\nBecause it is uncommon to have access to the bottom sheet view controller from the `contentVC`,we define a `CardPartsBottomSheetDelegate` with default implementations for updating to a new `contentVC` or `contentHeight`, updating the height, or dismissing the bottom sheet programmatically. In order to use this delegate and its default function implementations, simply have your class conform to `CardPartsBottomSheetDelegate` and define a `var bottomSheetViewController: CardPartsBottomSheetViewController`. Then, set that class to be a delegate for your content view controller and you can interface with the bottom sheet through the delegate.\n\n### `CardPartVideoView`\n\nProvides the capability to embed AVPlayer inside a cardpart view.\n\n```swift\nguard let videoUrl = URL(string: \"https:\u002F\u002Fwww.learningcontainer.com\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fsample-mp4-file.mp4\")  else  { return }\nlet cardPartVideoView = CardPartVideoView(videoUrl: videoUrl)\n```\n\nIf you need to access the underlying `AVPlayerViewController` to further customize it or set its delegate, you can do so through the `CardPartVideoView`'s `viewController` property. For example:\n\n```swift\nguard let controller = cardPartVideoView.viewController as? AVPlayerViewController else { return }\ncontroller.delegate = self\ncontroller.entersFullScreenWhenPlaybackBegins = true\n```\n\n\u003Cp align=\"center\">\n\u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002FIntuit\u002FCardParts\u002Fmaster\u002Fimages\u002FcardPartVideoView.gif\" width=\"334\" alt=\"CardPartVideoView\" \u002F>\n\u003C\u002Fp>\n\n## Card States\n\nCardPartsViewController can optionally support the notion of card states, where a card can be in 3 different states: loading, empty, and hasData. For each state you can specify a unique set of card parts to display. Then when the CardPartsViewController state property is changed, the framework will automatically switch the card parts to display the card parts for that state. Typically you would bind the state property to a state property in your view model so that when the view model changes state the card parts are changed. A simple example:\n\n```swift\npublic enum CardState {\n    case none\n    case loading\n    case empty\n    case hasData\n    case custom(String)\n}\n\nclass TestCardController : CardPartsViewController  {\n\n    var viewModel = TestViewModel()\n    var titlePart = CardPartTitleView(type: .titleOnly)\n    var textPart = CardPartTextView(type: .normal)\n    var loadingText = CardPartTextView(type: .normal)\n    var emptyText = CardPartTextView(type: .normal)\n    var customText = CardPartTextView(type: .normal)\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        viewModel.title.asObservable().bind(to: titlePart.rx.title).disposed(by: bag)\n        viewModel.text.asObservable().bind(to: textPart.rx.text).disposed(by: bag)\n\n        loadingText.text = \"Loading...\"\n        emptyText.text = \"No data found.\"\n        customText.text = \"I am some custom state\"\n\n        viewModel.state.asObservable().bind(to: self.rx.state).disposed(by: bag)\n\n        setupCardParts([titlePart, textPart], forState: .hasData)\n        setupCardParts([titlePart, loadingText], forState: .loading)\n        setupCardParts([titlePart, emptyText], forState: .empty)\n        setupCardParts([titlePart, customText], forState: .custom(\"myCustomState\"))\n    }\n}\n```\n\n_Note: There is a `custom(String)` state which allows you to use more than our predefined set of states:_\n\n```swift\n.custom(\"myCustomState\")\n```\n\n## Data Binding\n\nData binding is implemented using the RxSwift library (https:\u002F\u002Fgithub.com\u002FReactiveX\u002FRxSwift). View models should expose their data as bindable properties using the Variable class. In the example above the view model might look like this:\n\n```swift\nclass TestViewModel {\n\n    var title = BehaviorRelay(value: \"Testing\")\n    var text = BehaviorRelay(value: \"Card Part Text\")\n}\n```\n\nLater when the view model's data has changed it can update its property by setting the value attribute of the property:\n\n```swift\ntitle.accept(“Hello”)\n```\n\nThe view controller can bind the view model property to a view:\n\n```swift\nviewModel.title.asObservable().bind(to: titlePart.rx.title).disposed(by: bag)\n```\n\nNow, whenever the view model's title property value is changed it will automatically update the titlePart's title.\n\nRxSwift use the concept of \"Disposable\" and \"DisposeBag\" to remove bindings. Each call to bind returns a Disposable that can be added to a DisposeBag. CardPartsViewController defines an instance of DisposeBag called \"bag\" that you can use to automatically remove all your bindings when your CardPartsViewController is deallocated. See the RxSwift documentation for more information on disposables and DisposeBags.\n\n## Themes\n\nOut of the box we support 2 themes: Mint and Turbo. These are the 2 Intuit app's that are currently built on top of CardParts. As you can find in the file `CardPartsTheme.swift` we have a protocol called `CardPartsTheme`. You may create a class that conforms to `CardPartsTheme` and set all properties in order to theme CardParts however you may like. Below is an example of some of the themeable properties:\n\n```swift\n\u002F\u002F CardPartTextView\nvar smallTextFont: UIFont { get set }\nvar smallTextColor: UIColor { get set }\nvar normalTextFont: UIFont { get set }\nvar normalTextColor: UIColor { get set }\nvar titleTextFont: UIFont { get set }\nvar titleTextColor: UIColor { get set }\nvar headerTextFont: UIFont { get set }\nvar headerTextColor: UIColor { get set }\nvar detailTextFont: UIFont { get set }\nvar detailTextColor: UIColor { get set }\n\n\u002F\u002F CardPartTitleView\nvar titleFont: UIFont { get set }\nvar titleColor: UIColor { get set }\n\n\u002F\u002F CardPartButtonView\nvar buttonTitleFont: UIFont { get set }\nvar buttonTitleColor: UIColor { get set }\nvar buttonCornerRadius: CGFloat { get set }\n\n```\n\n### Applying a theme\n\nGenerate a class as follows:\n\n```swift\npublic class YourCardPartTheme: CardPartsTheme {\n    ...\n}\n```\n\nAnd then in your `AppDelegete` call `YourCardPartTheme().apply()` it apply your theme. If you use storyboards with `CardsViewController`s in your storyboard, the `required init(coder:)` initializer gets called prior to `AppDelegate`. In this case, you will need to apply the theme in this initializer of the first view controller in your storyboard to be initialized, and changes will take effect in all other view controllers. For example:\n\n```swift\nrequired init?(coder: NSCoder) {\n\tYourCardPartTheme().apply()\n\tsuper.init(coder: coder)\n}\n```\n\n## Clickable Cards\n\nYou have the ability to add a tap action for each [state](#card-states) of any given card. If a part of the card is clicked, the given action will be fired:\n\n```swift\nself.cardTapped(forState: .empty) {\n    print(\"Card was tapped in .empty state!\")\n}\n\nself.cardTapped(forState: .hasData) {\n    print(\"Card was tapped in .hasData state!\")\n}\n\n\u002F\u002F The default state for setupCardParts([]) is .none\nself.cardTapped {\n    print(\"Card was tapped in .none state\")\n}\n```\n\n_Note: It is always a good idea to weakify self in a closure:_\n\n```swift\n{[weak self] in\n\n}\n```\n\n## Listeners\n\nCardParts also supports a listener that allows you to listen to visibility changes in the cards that you have created. In your `CardPartsViewController` you may implement the `CardVisibilityDelegate` to gain insight into the visibility of your card within the `CardsViewController` you have created. This optional delegate can be implemented as follows:\n\n```swift\npublic class YourCardPartsViewController: CardPartsViewController, CardVisibilityDelegate {\n    ...\n\n    \u002F**\n    Notifies your card parts view controller of the ratio that the card is visible in its container\n    and the ratio of its container that the card takes up.\n    *\u002F\n     func cardVisibility(cardVisibilityRatio: CGFloat, containerCoverageRatio: CGFloat) {\n        \u002F\u002F Any logic you would like to perform based on these ratios\n    }\n}\n```\n\n## Delegates\n\nAny view controller which is a subclass of CardPartsViewController supports gesture delegate for long press on the view. Just need to conform your controller to CardPartsLongPressGestureRecognizerDelegate protocol.\n\nWhen the view is long pressed `didLongPress(_:)` will be called where you can custom handle the gesture.\nExample: Zoom in and Zoom out on gesture state begin\u002Fended.\n\n```swift\n    func didLongPress(_ gesture: UILongPressGestureRecognizer) -> Void\n```\n\nYou can set the minimumPressDuration for your press to register as gesture began. The value is in seconds.\n`default is set to 1 second`.\n\n```swift\n    var minimumPressDuration: CFTimeInterval { get } \u002F\u002F In seconds\n```\n\nExample:\n\n```swift\nextension MYOwnCardPartController: CardPartsLongPressGestureRecognizerDelegate {\n\tfunc didLongPress(_ gesture: UILongPressGestureRecognizer) {\n\t\tguard let v = gesture.view else { return }\n\n\t\tswitch gesture.state {\n\t\tcase .began:\n\t\t\t\u002F\u002F Zoom in\n\t\tcase .ended, .cancelled:\n\t\t\t\u002F\u002F Zoom out\n\t\tdefault: break\n\t\t}\n\t}\n\t\u002F\u002F Gesture starts registering after pressing for more than 0.5 seconds.\n\tvar minimumPressDuration: CFTimeInterval { return 0.5 }\n}\n```\n\n# Apps That Love CardParts\n\n- [Mint - Personal Finance & Money](https:\u002F\u002Fitunes.apple.com\u002Fus\u002Fapp\u002Fmint-personal-finance-money\u002Fid300238550?mt=8)\n- [Turbo: Scores-Income & Credit](https:\u002F\u002Fitunes.apple.com\u002Fus\u002Fapp\u002Fturbo-scores-income-credit\u002Fid1242998361?mt=8)\n- More to come.. hopefully!\n\n# Publications\n\n- [Rebuilding Mint’s UI From the Ground Up Using CardParts (iOS)](https:\u002F\u002Fmedium.com\u002Fblueprint-by-intuit\u002Frebuilding-mints-ui-from-the-ground-up-using-cardparts-ios-817aac6c863f)\n- [Top 10 Swift Open Source of the Month](https:\u002F\u002Fmedium.mybridge.co\u002Ftop-10-swift-open-source-of-the-month-v-may-2018-c581e2accc66)\n- [23 Amazing iOS UI Libraries](https:\u002F\u002Fmedium.mybridge.co\u002F23-amazing-ios-ui-libraries-written-in-swift-for-the-past-year-v-2019-3e5456318768)\n\n# License\n\nCardParts is available under the Apache 2.0 license. See the [LICENSE](https:\u002F\u002Fgithub.com\u002Fintuit\u002FCardParts\u002Fblob\u002Fmaster\u002FLICENSE) file for more info.\n","CardParts 是一个基于UIKit为iOS开发者构建的响应式卡片UI框架。它使用Swift语言开发，提供了丰富的卡片组件和自定义选项，支持多种卡片样式如透明、阴影、圆角等，并且可以轻松地添加到视图控制器中。该框架通过其高度可定制性和灵活性，能够帮助开发者快速创建美观且功能强大的卡片界面。适用于需要构建复杂但结构清晰的用户界面的应用场景，比如新闻阅读器、购物应用或任何希望以卡片形式展示内容的iOS应用程序。",2,"2026-06-11 03:10:41","top_language"]