Tinder Style Card Swipe in Flutter

flutter_card_swiper

This is a Flutter package for a Tinder-like card swiper. ✨

It allows you to swipe left, right, up, and down and define your own business logic for each direction.

Very smooth animations supporting Android, iOS, Web & Desktop.

Why?

We build this package because we wanted to:

  • have a completely customizable slider
  • be able to swipe in every direction
  • choose our own settings for the swiper such as duration, angle, padding, and more
  • trigger slide to any direction you want using the controller
  • add callbacks while wiping, on end or when the swiper is disabled
  • detect the direction (left, right, top, bottom) in which the card was swiped away

Show Cases

Customize the angle

Customize the threshold (when the card should slide away)

Installation

Add this to your package's pubspec.yaml file:

card_swiper: ...

OR run

flutter pub add flutter_card_swiper

in your project's root directory.

Usage

You can place your CardSwiper inside of a Scaffold like we did here. Optional parameters can be defined to enable different features. See the following example:

import 'package:flutter_card_swiper/flutter_card_swiper.dart';
import 'package:flutter/material.dart';

class Example extends StatelessWidget {
  List<Container> cards = [
    Container(
      alignment: Alignment.center,
      child: const Text('1'),
      color: Colors.blue,
    ),
    Container(
      alignment: Alignment.center,
      child: const Text('2'),
      color: Colors.red,
    ),
    Container(
      alignment: Alignment.center,
      child: const Text('3'),
      color: Colors.purple,
    )
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Flexible(
        child: CardSwiper(
          cards: cards,
        ),
      ),
    );
  }
}

Constructor

Basic

ParameterDefaultDescriptionRequired
cards-List of Widgets for the swipertrue
controller-Trigger swipefalse
paddingEdgeInsets.symmetric(horizontal: 20, vertical: 25)Control swiper paddingfalse
duration200 millisecondsThe duration that every animation should lastfalse
maxAngle30Maximum angle the card reaches while swipingfalse
threshold50Threshold from which the card is swiped awayfalse
scale0.9Scale of the card that is behind the front cardfalse
isDisabledfalseSet to true if swiping should be disabled, has no impact when triggered from the outsidefalse
onTapDisabled-Function that get triggered when the swiper is disabledfalse
onSwipe-Called with the new index and detected swipe direction when the user swipedfalse
onEnd-Called when there is no Widget left to be swiped awayfalse
directionrightDirection in which the card is swiped away when triggered from the outsidefalse

Controller

The Controller is used to swipe the card from outside of the widget. You can create a controller called CardSwiperController and save the instance for further usage. Please have a closer look at our Example for the usage.

MethodDescription
swipeChanges the state of the controller to swipe and swipes the card in your selected direction.
swipeLeftChanges the state of the controller to swipe left and swipes the card to the left side.
swipeRightChanges the state of the controller to swipe right and swipes the card to the right side.
swipeTopChanges the state of the controller to swipe top and swipes the card to the top side.
swipeBottomChanges the state of the controller to swipe bottom and swipes the card to the

Credits

  • Ricardo Dalarme (Package maintainer)
  • Appinio GmbH (Original project creator)

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_card_swiper

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_card_swiper: ^1.2.0

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_card_swiper/flutter_card_swiper.dart'; 

example/lib/main.dart

import 'package:example/example_candidate_model.dart';
import 'package:example/example_card.dart';
import 'package:flutter/material.dart';
import 'package:flutter_card_swiper/flutter_card_swiper.dart';

void main() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Example(),
    ),
  );
}

class Example extends StatefulWidget {
  const Example({
    Key? key,
  }) : super(key: key);

  @override
  State<Example> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<Example> {
  final CardSwiperController controller = CardSwiperController();

  final cards = candidates.map((candidate) => ExampleCard(candidate)).toList();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Flexible(
              child: CardSwiper(
                controller: controller,
                cards: cards,
                onSwipe: _swipe,
                padding: const EdgeInsets.all(24.0),
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 16.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  FloatingActionButton(
                    onPressed: controller.swipe,
                    child: const Icon(Icons.rotate_right),
                  ),
                  FloatingActionButton(
                    onPressed: controller.swipeLeft,
                    child: const Icon(Icons.keyboard_arrow_left),
                  ),
                  FloatingActionButton(
                    onPressed: controller.swipeRight,
                    child: const Icon(Icons.keyboard_arrow_right),
                  ),
                  FloatingActionButton(
                    onPressed: controller.swipeTop,
                    child: const Icon(Icons.keyboard_arrow_up),
                  ),
                  FloatingActionButton(
                    onPressed: controller.swipeBottom,
                    child: const Icon(Icons.keyboard_arrow_down),
                  ),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }

  void _swipe(int index, CardSwiperDirection direction) {
    debugPrint('the card $index was swiped to the: ${direction.name}');
  }
} 

Download Details:

Author: ricardodalarme

Source Code: https://github.com/ricardodalarme/flutter_card_swiper

#flutter #card #swiper 

Tinder Style Card Swipe in Flutter
Sean Robertson

Sean Robertson

1674023454

How to Create a Toggle to Display Items in Grid from List

In this tutorial, you'll learn how to create a toggle to display items in a grid from a list using JavaScript and CSS Grid.

What We Will Be Creating

gridList_tyuymu

How to Create Our Starter Files

Let's start by creating three files called index.html and style.css and app.js. In your index.html file add the following starter code:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Grid List Toggle using CSS Grid</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>

	<script src="app.js"></script>
</body>
</html>

In the style.css file add the following starter code:

body{
  padding: 0;
  margin: 0;
  height: 100vh;
  background-color: azure;
}

For now, we will leave the app.js file blank. We will put in the code later.

How to Add Our Toggle Buttons

The first thing we want to do is to create two buttons. These two buttons will have a title Grid and List. When clicked, these buttons will change the layout of the cards on the screen.

The entire content of the page will be placed inside a div with a class of container. The two buttons will be placed inside a div with a class of buttons. Each button will have a class of btn. Each button will have a unique id assigned to it. Later on we will use this id to handle when the button is clicked.

Add the following code inside the body tag in your index.html file:

	<div class="container">
		<div class="buttons">
			<button class="btn" id="gridBtn">Grid</button>
			<button class="btn" id="listBtn">List</button>
		</div>
	</div>

How to Style Our Container and Buttons

Everything we display is wrapped inside a div with a class of container. We want this container to have a specified width that is centered on the screen.

Add this code to your style.css file:

.container {
	width: 90vw;
	max-width: 800px;
	margin: 50px auto;
}

Next, we want our two buttons to be centered on the screen. The buttons are wrapped by a div with a class of buttons. This class will use CSS Grid to center the buttons.

We want these two buttons to have a blue background with white text. To make them look like they are lifted off the page we will add a box shadow.

Add the following code to your style.css file:

.btn {
	height: 55px;
	background: #2364d2;
	color: white;
	padding: 10px 20px;
	margin-right: 20px;
	border: none;
	border-radius: 5px;
	font-size: 22px;
	font-weight: 500;
	font-family: "Source Sans Pro", sans-serif;
	box-shadow: 3px 10px 20px 0px rgba(35, 100, 210, 0.3);
}

This is what our buttons look like:

Screenshot_2023-01-01_at_4.48.43_PM_ozo4hs

How to Add The Cards

I want to add 6 cards that will be displayed. Each card will have a title and a paragraph of Lorem Ipsum text.

I want all of the cards to be contained within a div. The styling on this div will determine if the cards are displayed in a grid or in a list. I will add both an id and a class to this div.

Each card is wrapped by a div with a class of card. It will have a title and a paragraph of text. Since each card will be the same, once I have created one card, I can copy and paste it to create a total of 6 cards.

Add this code to your index.html file:

		<div id="card-wrapper" class="list">
			<div class="card">
				<h3>Title</h3>
				<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin non felis euismod lectus convallis interdum eget eu eros.</p>
			</div>
		</div>

How to Add Styling to Our Cards

We will not add much styling to the card. We will give each card a border and some padding so that the text is not next to the border. We will also add a margin below each card.

Add the following code to your style.css file:

.card {
	border: 1px solid black;
	padding: 10px;
	background: white;
	margin-bottom: 20px;
}

This is what our card looks like:

Screenshot_2023-01-01_at_4.58.13_PM_r0ffnu

In your index.html file copy the code for the single card and paste it so there are a total of 6 cards.

How to Style our Grid and List

We will add styling for card-wrapper to make it display using CSS Grid and to have a width of 100% of our container. I will also add a margin on top so there is some spacing between the buttons and the cards. I also want to add a gap between each card.

Add the following code to your style.css file:

#card-wrapper {
	display: grid;
	width: 100%;
	margin-top: 50px;
	gap: 10px 20px;
}

With CSS Grid you can define the number of columns. For our grid layout, we want to have 3 columns. For our list layout, we want to have just 1 column.

Add the following code:

.grid {
	grid-template-columns: 1fr 1fr 1fr;
}

.list {
	grid-template-columns: 1fr;
}

Final Result

Now when you view the code in your browser, you will see the layout change as your click each of the buttons.

Here is what it looks like:

gridList_tyuymu

Let's Connect

Thanks for reading my article today. You can get the source code here.

Original article source at https://www.freecodecamp.org

#css #javascript 

How to Create a Toggle to Display Items in Grid from List
Rupert  Beatty

Rupert Beatty

1673976000

CardsLayout: Custom Card-designed CollectionView Layout

CardsLayout

CardsLayout is a lightweight Collection Layout.

Preview

Installation

CocoaPods

You can use CocoaPods to install CardsLayout by adding it to your Podfile:

platform :ios, '9.0'
use_frameworks!
pod 'CardsLayout'
import CardsLayout

Manual

  1. Add CardsCollectionViewLayout file to your project
  2. Configure collectionView:
    collectionView.collectionViewLayout = CardsCollectionViewLayout()
    collectionView.isPagingEnabled = true
    collectionView.showsHorizontalScrollIndicator = false

Download Details:

Author: filletofish
Source Code: https://github.com/filletofish/CardsLayout 
License: MIT license

#swift #ios #card #uikit 

CardsLayout: Custom Card-designed CollectionView Layout
Gordon  Murray

Gordon Murray

1670079125

An Amazing New Feature in Power Platform

What is Power Apps Card?

Power Apps cards are simple lightweight micro-apps which holds and display enterprise data along with workflow and logic functions.

Does creating Power Apps Cards require coding knowledge?

No need, using simple drag and drop function cards can be quickly built and shared with other applications as an actionable app without any coding expertise.

What is the difference between Power Apps data card and Power Apps Card?

Data card are inserted inside the Power Apps itself as an integrated card. But Power Apps cards are micro-apps which are standalone in the Power Platform ecosystem where this card UI elements can be used by other applications to display the content.

Where can I find the Cards feature in the Power Platform?

To find the Cards go to https://make.powerapps.com/ on the left side navigation you can find a new feature available.

An awesome new feature in Power Platform : Power Apps Cards

What are the key components of a card?

  1. Card designer
  2. Power Apps data and resource management

What is the use of Card Designer?

Microsoft has provided an interface to create cards. The UI controls can be dragged and dropped in the card and attach to a function logic and connect to all the Power Platform connectors to display data.

What are the UI controls available in the Card Designer?

You can add

  1. Buttons
  2. Tables
  3. Labels
  4. Images
  5. Checkboxes
  6. Textboxes
  7. Date Picker

And so on.

What are Power Apps data and resource management?

It is a service where you can manage the cards which is used to send and receive data and to embed the data and the workflow.

What is the advantage of adding data and services with connectors?

Power Platform connectors can be connected within no time with all the safety and security enabled to the environment.

Can I add any business logic to this card or static data will be displayed?

 Not just static data but you can definitely build business logic using Power Fx which can be added to create

  • Inline calculations
  • Dynamic calculations
  • Data operations

And those responses can be targeted and inserted into the UI Elements available in the cards.

What are other applications that can use these cards?

You can send cards from the card designer to a Microsoft Teams chat or channel to share your cards with others.

Is this available for all?

No, this is a preview feature and previews are not suitable for the production environment. You can play around and wait for the general availability.

Can I read more about the Cards

Yes definitely, please visit the Microsoft Learn docs to read more about it.

Original article source at:: https://www.c-sharpcorner.com/

#powerapp #power #platform #card 

An Amazing New Feature in Power Platform

How to Making A Simple Credit Card Validation form

In this quick tutorial we will show you how to create a simple credit card form. We'll build the whole thing from scratch, with a little help from Bootstrap 3 for the interface, and Payform.js for client-side form validation.

Project Overview

Here is a sneak-peak of what we will be building in this tutorial:

credit-card-form.png

Credit Card Form Demo

You can get the full code for this project from the Download button near the top of the article. An overview of the files can be seen below:

project-overview.png

There are two .css files and two .js files which we will need to include in our HTML. All other resources such as the Bootstrap framework, jQuery, and web fonts will be included externally via CDN.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Credit Card Validation Demo</title>

    <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
    <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="assets/css/styles.css">
    <link rel="stylesheet" type="text/css" href="assets/css/demo.css">
</head>

<body>

    <!-- The HTML for our form will go here -->

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script src="assets/js/jquery.payform.min.js" charset="utf-8"></script>
    <script src="assets/js/script.js"></script>
</body>
</html>

Now that everything is set up, we can start building our credit card form. Let's start with the HTML layout!

Layout

A credit card dialog needs to be simple, short, and straightforward. Here are the four input fields that every credit card form needs to have:

  • Credit card owner name
  • Card number
  • Secret code (also known as CVV/CVC/CID)
  • Expiration Date

All we need to do is create a <form> and add all the required input fields. For the owner, card number, and CVV we will use simple text fields. For the expiration date we'll put a combination of two selects with predefined options.

Besides that our form will have a heading, a submit button, and images for popular credit card vendors. Since we are working with Bootstrap there is a little extra markup, but it helps keep the code organized and the layout responsive.

<div class="creditCardForm">
    <div class="heading">
        <h1>Confirm Purchase</h1>
    </div>
    <div class="payment">
        <form>
            <div class="form-group owner">
                <label for="owner">Owner</label>
                <input type="text" class="form-control" id="owner">
            </div>
            <div class="form-group CVV">
                <label for="cvv">CVV</label>
                <input type="text" class="form-control" id="cvv">
            </div>
            <div class="form-group" id="card-number-field">
                <label for="cardNumber">Card Number</label>
                <input type="text" class="form-control" id="cardNumber">
            </div>
            <div class="form-group" id="expiration-date">
                <label>Expiration Date</label>
                <select>
                    <option value="01">January</option>
                    <option value="02">February </option>
                    <option value="03">March</option>
                    <option value="04">April</option>
                    <option value="05">May</option>
                    <option value="06">June</option>
                    <option value="07">July</option>
                    <option value="08">August</option>
                    <option value="09">September</option>
                    <option value="10">October</option>
                    <option value="11">November</option>
                    <option value="12">December</option>
                </select>
                <select>
                    <option value="16"> 2016</option>
                    <option value="17"> 2017</option>
                    <option value="18"> 2018</option>
                    <option value="19"> 2019</option>
                    <option value="20"> 2020</option>
                    <option value="21"> 2021</option>
                </select>
            </div>
            <div class="form-group" id="credit_cards">
                <img src="assets/images/visa.jpg" id="visa">
                <img src="assets/images/mastercard.jpg" id="mastercard">
                <img src="assets/images/amex.jpg" id="amex">
            </div>
            <div class="form-group" id="pay-now">
                <button type="submit" class="btn btn-default" id="confirm-purchase">Confirm</button>
            </div>
        </form>
    </div>
</div>

Now that we have the needed input fields, we can setup the validation rules.

Validation

All of the validation we will show here is client side and done exclusively in the JavaScript. If it is HTML validation that you are interested in, check out this article.

To kick things off we will define all the jQuery selectors we will need:

var owner = $('#owner'),
    cardNumber = $('#cardNumber'),
    cardNumberField = $('#card-number-field'),
    CVV = $("#cvv"),
    mastercard = $("#mastercard"),
    confirmButton = $('#confirm-purchase'),
    visa = $("#visa"),
    amex = $("#amex");

Then, using Payform.js, we will turn our basic input fields into specialized input for credit card data. We simply need to call the right function and the library will automatically handle text formatting and maximum string length for us:

cardNumber.payform('formatCardNumber'); CVV.payform('formatCardCVC');

Next, we want to be able to give real-time feedback to users while they are typing in their card number. To do so we will write a simple function that does two things:

  1. Check if the current text in the field is а valid card number or not. Add appropriate coloring to the text field.
  2. Depending on the present input characters, see if the card is either Visa, MasterCard, or American Express. This is done using the payform.parseCardType() method.

Since we want to execute the above actions every time a new character is typed in, we will use the jQuery keyup() event listener.

cardNumber.keyup(function() {
    amex.removeClass('transparent');
    visa.removeClass('transparent');
    mastercard.removeClass('transparent');

    if ($.payform.validateCardNumber(cardNumber.val()) == false) {
        cardNumberField.removeClass('has-success');
        cardNumberField.addClass('has-error');
    } else {
        cardNumberField.removeClass('has-error');
        cardNumberField.addClass('has-success');
    }

    if ($.payform.parseCardType(cardNumber.val()) == 'visa') {
        mastercard.addClass('transparent');
        amex.addClass('transparent');
    } else if ($.payform.parseCardType(cardNumber.val()) == 'amex') {
        mastercard.addClass('transparent');
        visa.addClass('transparent');
    } else if ($.payform.parseCardType(cardNumber.val()) == 'mastercard') {
        amex.addClass('transparent');
        visa.addClass('transparent');
    }
});

There is one more thing we have to do and that is is check if all the field are holding valid data when the user tries to submit the form.

Name validation can be quite tricky. To keep this tutorial light, we won't be going into that subject, and we will only check if the input name is at least 5 characters long. Payform provides us with the needed methods for validating the rest of the form.

confirmButton.click(function(e) {
    e.preventDefault();

    var isCardValid = $.payform.validateCardNumber(cardNumber.val());
    var isCvvValid = $.payform.validateCardCVC(CVV.val());

    if(owner.val().length < 5){
        alert("Wrong owner name");
    } else if (!isCardValid) {
        alert("Wrong card number");
    } else if (!isCvvValid) {
        alert("Wrong CVV");
    } else {
        // Everything is correct. Add your form submission code here.
        alert("Everything is correct");
    }
});

The above validation is for educational purposes only and shouldn't be used on commercial projects. Always include both client-side and server-side validation to your forms, especially when working with credit card data.

Styles

We are using Bootstrap, so most of the styling is done by the framework. Our CSS mostly covers the size of the input fields and various padding, margin and font tweaks.

styles.css

.creditCardForm {
    max-width: 700px;
    background-color: #fff;
    margin: 100px auto;
    overflow: hidden;
    padding: 25px;
    color: #4c4e56;
}
.creditCardForm label {
    width: 100%;
    margin-bottom: 10px;
}
.creditCardForm .heading h1 {
    text-align: center;
    font-family: 'Open Sans', sans-serif;
    color: #4c4e56;
}
.creditCardForm .payment {
    float: left;
    font-size: 18px;
    padding: 10px 25px;
    margin-top: 20px;
    position: relative;
}
.creditCardForm .payment .form-group {
    float: left;
    margin-bottom: 15px;
}
.creditCardForm .payment .form-control {
    line-height: 40px;
    height: auto;
    padding: 0 16px;
}
.creditCardForm .owner {
    width: 63%;
    margin-right: 10px;
}
.creditCardForm .CVV {
    width: 35%;
}
.creditCardForm #card-number-field {
    width: 100%;
}
.creditCardForm #expiration-date {
    width: 49%;
}
.creditCardForm #credit_cards {
    width: 50%;
    margin-top: 25px;
    text-align: right;
}
.creditCardForm #pay-now {
    width: 100%;
    margin-top: 25px;
}
.creditCardForm .payment .btn {
    width: 100%;
    margin-top: 3px;
    font-size: 24px;
    background-color: #2ec4a5;
    color: white;
}
.creditCardForm .payment select {
    padding: 10px;
    margin-right: 15px;
}
.transparent {
    opacity: 0.2;
}
@media(max-width: 650px) {
    .creditCardForm .owner,
    .creditCardForm .CVV,
    .creditCardForm #expiration-date,
    .creditCardForm #credit_cards {
        width: 100%;
    }
    .creditCardForm #credit_cards {
        text-align: left;
    }
}

With this our Credit Card Validation Form is complete!

Original article source at: https://tutorialzine.com/

#validation #card 

How to Making A Simple Credit Card Validation form

SlimyCard Animated In Flutter

flutter_slimy_card

SlimyCard provides a nice slime-like animation of a Card that splits into two different Cards, one on top and one on the bottom. It is possible to put any custom widget into these two separate cards.

How install this package

Getting Started

1. Depend on it Add this to your package's pubspec.yaml file:

dependencies:
  flutter_slimy_card: ^1.0.2

2. Install it

You can install packages from the command line with Flutter:

$ flutter pub get

How use this package

1. Import this package

Now in your Dart code, you can use:

import 'package:flutter_slimy_card/flutter_slimy_card.dart';

2. Use this package

Basic Use

ListView(
  children: <Widget>[
    FlutterSlimyCard()
  ]
);

Custom Usuage Example

Widget build(BuildContext context) {
    return Scaffold(
        body: ListView(
      children: <Widget>[
        FlutterSlimyCard(
          topCardHeight: 160,
          bottomCardHeight: 120,
          topCardWidget: topWidget(),
          bottomCardWidget: bottomWidget(),
        ),
      ],
    ));
  }

  topWidget() {
    return Container(
      child: SafeArea(
        child: Column(
          children: [
            Container(height: 75, child: Image(image: AssetImage('assets/run_horse.png'))),
            SizedBox(
              height: 5,
            ),
            Text(
              'A Horse',
              style: TextStyle(color: Colors.white, fontSize: 18),
            ),
            SizedBox(
              height: 5,
            ),
          ],
        ),
      ),
    );
  }

  bottomWidget() {
    return Container(
      margin: EdgeInsets.only(top: 5),
      child: Column(
        children: [
          SizedBox(height: 10),
          Flexible(
              child: Text(
            'A horse is a large animal which people can ride. Some horses are used for pulling ploughs and carts. Say Hello to a Funny Hourse.',
            style: TextStyle(color: Colors.white),
          ))
        ],
      ),
    );
  }

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add flutter_slimy_card

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  flutter_slimy_card: ^1.0.2

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:flutter_slimy_card/flutter_slimy_card.dart'; 

Download Details:

Author: 

Source Code: https://pub.dev/packages/flutter_slimy_card

#flutter #card 

SlimyCard Animated In Flutter
Rupert  Beatty

Rupert Beatty

1668073800

CardSlider: Innovative Twist to Tinder Cards for iOS

Card Slider for Swift

Tons of apps use a Tinder-style interface with cards that users can swipe right to 'like' or left to 'dislike'. But Yaroslav Zubko came up with an innovative and fresh approach to giving users more options besides just 'like' or 'dislike'. Here's Yaroslav's Dribbble shot that inspired me to create a 100% Swift project of this unique & new interface:

Dribbble shot

And here's a demo of the actual Swift project:

Demo

Note: you can pivot the cards in any direction, it all depends on where your finger is on the card.

Usage

This project isn't a framework, it's more so of a demonstration of how to approach this sort of user interface. Card Slider basically uses a UIPanGestureRecognizer in conjunction with several UIKit Dynamics behaviors. Because of this, ideally you would want all the card logic code in a view controller class, so I opted not make an external class that uses delegation to talk to the view controller.

CardView.swift

Most of the logic code is in the ViewController class, but each card is a subview of CardView. In the demo project, ImageCard is a subview of CardView and has its own custom subviews and layouts. You can create your own subclass of CardView and modify the cards data structure in ViewController to swap in your own custom cards. You can also modify the CardOption enum to show your own custom text on the cards for each of the 6 options (you may even add more, but that would require dealing with more emojis and laying them out properly.)

EmojiOptionsOverlay.swift

This file has all the logic code associated with showing the 6 emojis on the sides when the user pans the card around, as well as the heart emoji on the top right.

Credits

Yaroslav Zubko, the creator of the Dribbble shot that inspired this project, was kind enough to send me his design files. This concept and any artwork (including the emojis) is thanks to Yaroslav. https://dribbble.com/Yar_Z

Download Details:

Author: Saoudrizwan
Source Code: https://github.com/saoudrizwan/CardSlider 

#swift #ios #card #tinder 

CardSlider: Innovative Twist to Tinder Cards for iOS
Rupert  Beatty

Rupert Beatty

1668061983

VerticalCardSwiper: A Marriage Between The Shazam Discover UI & Tinder

VerticalCardSwiper

A marriage between the Shazam Discover UI and Tinder, built with UICollectionView in Swift.

example

Project goal and information

The goal of this project is to recreate the Discover UI in Shazam (which I think is a great, fun way to display content) in combination with a Tinder style of swiping cards to the left/right. The idea behind this is that in some cases, you don't want to swipe away cards, but keep them available for later on. This implementation allows for that. And it's a fun way to interact with content.

It's built with a UICollectionView and a custom flowLayout.

Requirements

  • iOS 9.0+
  • Swift 5

Installation

CocoaPods

To install with CocoaPods, simply add the following line to your Podfile:

pod 'VerticalCardSwiper'

Carthage

To install with Carthage, simply add the following line to your Podfile:

github "JoniVR/VerticalCardSwiper"

Example

To try out VerticalCardSwiper

pod try VerticalCardSwiper

or open the project and run the Example.

Usage

VerticalCardSwiper behaves a lot like a standard UICollectionView. To use it inside your UIViewController:

import VerticalCardSwiper

class ExampleViewController: UIViewController, VerticalCardSwiperDatasource {
    
    private var cardSwiper: VerticalCardSwiper!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        cardSwiper = VerticalCardSwiper(frame: self.view.bounds)
        view.addSubview(cardSwiper)
        
        cardSwiper.datasource = self
        
        // register cardcell for storyboard use
        cardSwiper.register(nib: UINib(nibName: "ExampleCell", bundle: nil), forCellWithReuseIdentifier: "ExampleCell")
    }
    
    func cardForItemAt(verticalCardSwiperView: VerticalCardSwiperView, cardForItemAt index: Int) -> CardCell {
        
        if let cardCell = verticalCardSwiperView.dequeueReusableCell(withReuseIdentifier: "ExampleCell", for: index) as? ExampleCardCell {
            return cardCell
        }
        return CardCell()
    }
    
    func numberOfCards(verticalCardSwiperView: VerticalCardSwiperView) -> Int {
        return 100
    }
}

Properties

/// Indicates if side swiping on cards is enabled. Set to false if you don't want side swiping. Default is `true`.
@IBInspectable public var isSideSwipingEnabled: Bool = true
/// Allows you to enable/disable the stacking effect. Default is `true` (enabled).
@IBInspectable public var isStackingEnabled: Bool = true
/// The transform animation that is shown on the top card when scrolling through the cards. Default is 0.05.
@IBInspectable public var firstItemTransform: CGFloat = 0.05
/// The inset (spacing) at the top for the cards. Default is 40.
@IBInspectable public var topInset: CGFloat = 40
/// The inset (spacing) at each side of the cards. Default is 20.
@IBInspectable public var sideInset: CGFloat = 20
/// Sets how much of the next card should be visible. Default is 50.
@IBInspectable public var visibleNextCardHeight: CGFloat = 50
/// Vertical spacing between the focussed card and the bottom (next) card. Default is 40.
@IBInspectable public var cardSpacing: CGFloat = 40
/// Allows you to set the view to Stack at the Top or at the Bottom. Default is true.
@IBInspectable public var isStackOnBottom: Bool = true
/// Sets how many cards of the stack are visible in the background
@IBInspectable public var stackedCardsCount: Int = 1
/** 
 Returns an array of indexes (as Int) that are currently visible in the `VerticalCardSwiperView`.
 This includes cards that are stacked (behind the focussed card).
*/
public var indexesForVisibleCards: [Int]

Other

Just like with a regular UICollectionView, you can reload the data by calling

cardSwiper.reloadData()

Get the current focussed card index

cardSwiper.focussedCardIndex

Scroll to a specifc card by calling

cardSwiper.scrollToCard(at: Int, animated: Bool) -> Bool

Get a card at a specified index

cardSwiper.cardForItem(at: Int) -> CardCell?

Swipe a card away programatically

cardSwiper.swipeCardAwayProgrammatically(at: Int, to: SwipeDirection, withDuration: TimeInterval = 0.3) -> Bool

Moving/Deleting/Inserting cards at runtime

Make sure to update your datasource first, otherwise an error will occur.

cardSwiper.moveCard(at: Int, to: Int)
cardSwiper.deleteCards(at: [Int])
cardSwiper.insertCards(at: [Int])

Delegation

To handle swipe gestures, implement the VerticalCardSwiperDelegate.

class ViewController: UIViewController, VerticalCardSwiperDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        cardSwiper.delegate = self
    }
    
    func willSwipeCardAway(card: CardCell, index: Int, swipeDirection: SwipeDirection) {
    
        // called right before the card animates off the screen (optional).
    }

    func didSwipeCardAway(card: CardCell, index: Int, swipeDirection: SwipeDirection) {

        // handle swipe gestures (optional).
    }
    
    func didCancelSwipe(card: CardCell, index: Int) {
        
        // Called when a card swipe is cancelled (when the threshold wasn't reached)
    }
    
    func sizeForItem(verticalCardSwiperView: VerticalCardSwiperView, index: Int) -> CGSize {
    
        // Allows you to return custom card sizes (optional).
        return CGSize(width: verticalCardSwiperView.frame.width * 0.75, height: verticalCardSwiperView.frame.height * 0.75)
    }
    
    func didScroll(verticalCardSwiperView: VerticalCardSwiperView) {
    
        // Tells the delegate when the user scrolls through the cards (optional).
    }
    
    func didEndScroll(verticalCardSwiperView: VerticalCardSwiperView) {
    
        // Tells the delegate when scrolling through the cards came to an end (optional).
    }
    
    func didDragCard(card: CardCell, index: Int, swipeDirection: SwipeDirection) {
    
        // Called when the user starts dragging a card to the side (optional).
    }
    
    func didTapCard(verticalCardSwiperView: VerticalCardSwiperView, index: Int) {
    
        // Tells the delegate when the user taps a card (optional).
    }
    
    func didHoldCard(verticalCardSwiperView: VerticalCardSwiperView, index: Int, state: UIGestureRecognizer.State) {
    
        // Tells the delegate when the user holds a card (optional).
    }
}

Customization

Subclass the CardCell to customize the cards.

class ExampleCardCell: CardCell {

}

Key Features

  •  Shazam Discover UI with paging
  •  Tinder-style swiping
  •  Option to disable side swiping
  •  Set custom number of stacked cards
  •  Code documentation in README.md file
  •  Cocoapods support
  •  Carthage support
  •  SPM support
  •  Diff support

More

Feel free to submit a pull request, open an issue or fork this project. Any help is always appreciated. A big thank you to all the contributors!

Download Details:

Author: JoniVR
Source Code: https://github.com/JoniVR/VerticalCardSwiper 
License: MIT license

#swift #ios #card #animation 

VerticalCardSwiper: A Marriage Between The Shazam Discover UI & Tinder
Rupert  Beatty

Rupert Beatty

1666777380

A Reactive, Card-based UI Framework Built on UIKit for iOS Developers

CardParts

A reactive, card-based UI framework built on UIKit for iOS developers.

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

In ViewController.swift you will be able to change the cards displayed and/or their order by commenting out one of the loadCards(cards: ) functions. If 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.

Requirements

  • iOS 10.0+
  • Xcode 10.2+
  • Swift 5.0+
  • CocoaPods 1.6.1+

Installation

CardParts is available through CocoaPods. You can install it with the following command:

$ gem install cocoapods

To add CardParts to your project, simply add the following line to your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target '<Your Target Name>' do
    pod 'CardParts'
end

Then, run the following command:

$ pod install

Communication and Contribution

  • If you need help, open an issue and tag as help wanted.
  • If you found a bug, open an issue and tag as bug.
  • If you have a feature request, open an issue and tag as feature.
  • If you want to contribute, submit a pull request.
    • In order to submit a pull request, please fork this repo and submit a PR from your forked repo.
    • Have a detailed message as to what your PR fixes/enhances/adds.
    • Each PR must get two approvals from our team before we will merge.

Overview

CardParts 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), 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.

CardPart Example in Mint

Quick Start

See how quickly you can get a card displayed on the screen while adhering to the MVVM design pattern:

import RxCocoa

class MyCardsViewController: CardsViewController {

    let cards: [CardController] = [TestCardController()]

    override func viewDidLoad() {
        super.viewDidLoad()

        loadCards(cards: cards)
    }
}

class TestCardController: CardPartsViewController  {

    var viewModel = TestViewModel()
    var titlePart = CardPartTitleView(type: .titleOnly)
    var textPart = CardPartTextView(type: .normal)

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.title.asObservable().bind(to: titlePart.rx.title).disposed(by: bag)
        viewModel.text.asObservable().bind(to: textPart.rx.text).disposed(by: bag)

        setupCardParts([titlePart, textPart])
    }
}

class TestViewModel {

    var title = BehaviorRelay(value: "")
    var text = BehaviorRelay(value: "")

    init() {

        // When these values change, the UI in the TestCardController
        // will automatically update
        title.accept("Hello, world!")
        text.accept("CardParts is awesome!")
    }
}

Note: RxCocoa is required for BehaviorRelay, thus you must import it wherever you may find yourself using it.

Architecture

There 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).

CardsViewController

The 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.

To use a CardsViewController, you first need to subclass it. Then in the viewDidLoad method call the super class loadCards method passing an array of CardControllers. 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:

class TestCardsViewController: CardsViewController {

    let cards: [CardController] = [TestCardController(), AnotherTestCardController()]

    override func viewDidLoad() {
        super.viewDidLoad()

        loadCards(cards: cards)
    }
}

Each card must implement the CardController protocol (note that CardPartsViewController discussed later implements this protocol already). The CardController protocol has a single method:

protocol CardController : NSObjectProtocol {

    func viewController() -> UIViewController

}

The 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.

Load specific cards

While 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.

Custom Card Margins

By default, the margins of your CardsViewController will match the theme's cardCellMargins property. You can change the margins for all CardsViewControllers in your application by applying a new theme 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. 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:

class MyCardsViewController: CardsViewController {

    init() {
        // set up properties
        super.init(nibName: nil, bundle: nil)
        self.cardCellMargins = UIEdgeInsets(/* custom card margins */)
    }

    ...
}

If 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:

required init?(coder: NSCoder) {
    YourCardPartTheme().apply()
    super.init(coder: coder)
}

Card Traits

The 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:

class MyCard: UIViewController, CardController, TransparentCardTrait {

}

MyCard 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.

Most 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.

NoTopBottomMarginsCardTrait

By 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.

TransparentCardTrait

Each 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.

EditableCardTrait

If 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.

HiddenCardTrait

The HiddenCardTrait trait requires the CardController to implement an isHidden variable:

    var isHidden: BehaviorRelay<Bool> { get }

The 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.

ShadowCardTrait

The ShadowCardTrait protocol requires CardController to implement shadowColor(), shadowRadius(), shadowOpacity() and shadowOffset() methods.

    func shadowColor() -> CGColor {
        return UIColor.lightGray.cgColor
    }

    func shadowRadius() -> CGFloat {
        return 10.0
    }

    // The value can be from 0.0 to 1.0.
    // 0.0 => lighter shadow
    // 1.0 => darker shadow
    func shadowOpacity() -> Float {
        return 1.0
    }

    func shadowOffset() -> CGSize {
        return CGSize(width: 0, height: 5)
    }

shadowColor: lightGray, shadowRadius: 5.0, shadowOpacity: 0.5 
Shadow radius 5.0

shadowColor: lightGray, shadowRadius: 10.0, shadowOpacity: 0.5 
Shadow radius 10.0

RoundedCardTrait

Use this protocol to define the roundness for the card by implementing the method cornerRadius().

    func cornerRadius() -> CGFloat {
        return 10.0
    }

cornerRadius: 10.0 
Shadow radius 5.0

GradientCardTrait

Use 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.

    func gradientColors() -> [UIColor] {
        return [UIColor.lavender, UIColor.aqua]
    }

    func gradientAngle() -> Float {
        return 45.0
    }

Shadow radius 10.0

BorderCardTrait

Use this protocol to add border color and border width for the card, implement borderWidth(), and borderColor() methods.

    func borderWidth() -> CGFloat {
        return 2.0
    }

    func borderColor() -> CGColor {
        return UIColor.darkGray.cgColor
    }

border

CustomMarginCardTrait

Use 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.

    func customMargin() -> CGFloat {
        return 42.0
    }

CardPartsViewController

CardPartsViewController 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 s UI guidelines.

In 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:

class TestCardController: CardPartsViewController  {

    var viewModel = TestViewModel()
    var titlePart = CardPartTitleView(type: .titleOnly)
    var textPart = CardPartTextView(type: .normal)

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.title.asObservable().bind(to: titlePart.rx.title).disposed(by: bag)
        viewModel.text.asObservable().bind(to: textPart.rx.text).disposed(by: bag)

        setupCardParts([titlePart, textPart])
    }
}

class TestViewModel {

    var title = BehaviorRelay(value: "")
    var text = BehaviorRelay(value: "")

    init() {

        // When these values change, the UI in the TestCardController
        // will automatically update
        title.accept("Hello, world!")
        text.accept("CardParts is awesome!")
    }
}

The 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.

The 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.

CardPartsFullScreenViewController

This 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.

class TestCardController: CardPartsFullScreenViewController  {
    ...
}

CardParts

The 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.

CardPartTextView

CardPartTextView 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.

CardPartTextView exposes the following reactive properties that can be bound to view model properties:

var text: String?
var attributedText: NSAttributedString?
var font: UIFont!
var textColor: UIColor!
var textAlignment: NSTextAlignment
var lineSpacing: CGFloat
var lineHeightMultiple: CGFloat
var alpha: CGFloat
var backgroundColor: UIColor?
var isHidden: Bool
var isUserInteractionEnabled: Bool
var tintColor: UIColor?

CardPartAttributedTextView

CardPartAttributedTextView 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. CartPartAttributedTextView exposes the following reactive properties that can be bound to view model properties:

var text: String?
var attributedText: NSAttributedString?
var font: UIFont!
var textColor: UIColor!
var textAlignment: NSTextAlignment
var lineSpacing: CGFloat
var lineHeightMultiple: CGFloat
var isEditable: Bool
var dataDetectorTypes: UIDataDetectorTypes
var exclusionPath: [UIBezierPath]?
var linkTextAttributes: [NSAttributedString.Key : Any]
var textViewImage: CardPartImageView?
var isUserInteractionEnabled: Bool
var tintColor: UIColor?

CardPartImageView

CardPartImageView displays a single image. CardPartImageView exposes the following reactive properties that can be bound to view model properties:

var image: UIImage?
var imageName: String?
var alpha: CGFloat
var backgroundColor: UIColor?
var isHidden: Bool
var isUserInteractionEnabled: Bool
var tintColor: UIColor?

CardPartButtonView

CardPartButtonView displays a single button.

CardPartButtonView exposes the following reactive properties that can be bound to view model properties:

var buttonTitle: String?
var isSelected: Bool?
var isHighlighted: Bool?
var contentHorizontalAlignment: UIControlContentHorizontalAlignment
var alpha: CGFloat
var backgroundColor: UIColor?
var isHidden: Bool
var isUserInteractionEnabled: Bool
var tintColor: UIColor?

CardPartTitleView

CardPartTitleView 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.

As an example for a title with menu buttons:

let titlePart = CardPartTitleView(type: .titleWithMenu)
titlePart.menuTitle = "Hide this offer"
titlePart.menuOptions = ["Hide"]
titlePart.menuOptionObserver  = {[weak self] (title, index) in
    // Logic to determine which menu option was clicked
    // and how to respond
    if index == 0 {
        self?.hideOfferClicked()
    }
}

CardPartButtonView exposes the following reactive properties that can be bound to view model properties:

var title: String?
var titleFont: UIFont
var titleColor: UIColor
var menuTitle: String?
var menuOptions: [String]?
var menuButtonImageName: String
var alpha: CGFloat
var backgroundColor: UIColor?
var isHidden: Bool
var isUserInteractionEnabled: Bool
var tintColor: UIColor?

CardPartTitleDescriptionView

CardPartTitleDescriptionView 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/description labels. See below:

let rightAligned = CardPartTitleDescriptionView(titlePosition: .top, secondaryPosition: .right) // This will be right aligned
let centerAligned = CardPartTitleDescriptionView(titlePosition: .top, secondaryPosition: .center(amount: 0)) // This will be center aligned with an offset of 0.  You may increase that amount param to shift right your desired amount

CardPartPillLabel

CardPartPillLabel provides you the rounded corners, text aligned being at the center along with vertical and horizontal padding capability.

var verticalPadding:CGFloat
var horizontalPadding:CGFloat

pillLabel

See the example app for a working example.

CardPartIconLabel

CardPartIconLabel provides the capability to add images in eithet directions supporting left , right and center text alignments along with icon binding capability.

    let iconLabel = CardPartIconLabel()
    iconLabel.verticalPadding = 10
    iconLabel.horizontalPadding = 10
    iconLabel.backgroundColor = UIColor.blue
    iconLabel.font = UIFont.systemFont(ofSize: 12)
    iconLabel.textColor = UIColor.black
    iconLabel.numberOfLines = 0
    iconLabel.iconPadding = 5
    iconLabel.icon = UIImage(named: "cardIcon")

cardPartIconLabel

CardPartSeparatorView

CardPartSeparatorView displays a separator line. There are no reactive properties define for CardPartSeparatorView.

CardPartVerticalSeparatorView

As the name describes, it shows a vertical separator view opposed to a horizontal one

CardPartStackView

CardPartStackView 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.

To 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:

horizStackPart.addArrangedSubview(imagePart)

Also,provides an option to round the corners of the stackview

let roundedStackView = CardPartStackView()
roundedStackView.cornerRadius = 10.0
roundedStackView.pinBackground(roundedStackView.backgroundView, to: roundedStackView)

roundedStackView

There 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.

CardPartTableView

CardPartTableView 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.

To setup the data source binding the view model class should expose MutableObservableArray property that contains the table view's data. For example:

var listData = MutableObservableArray(["item 1", "item 2", "item 3", "item 4"])

Then in the view controller the data source binding can be setup as follows:

viewModel.listData.bind(to: tableViewPart.tableView) { listData, indexPath, tableView in

    guard let cell = tableView.dequeueReusableCell(withIdentifier: tableViewPart.kDefaultCellId, for: indexPath) as? CardPartTableViewCell else { return UITableViewCell() }

    cell.leftTitleLabel.text = listData[indexPath.row]

    return cell
}

The 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.

CardPartTableView 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.

It is also possible to register your own custom cells by calling the register method on tableViewPart.tableView.

You also have access to two delegate methods being called by the tableView as follows:

@objc public protocol CardPartTableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    @objc optional func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
}

CardPartTableViewCell

CardPartTableViewCell is the default cell registered for CardPartTableView. The cell contains the following properties:

var leftTitleLabel: UILabel
var leftDescriptionLabel: UILabel
var rightTitleLabel: UILabel
var rightDescriptionLabel: UILabel
var rightTopButton: UIButton
var shouldCenterRightLabel = false
var leftTitleFont: UIFont
var leftDescriptionFont: UIFont
var rightTitleFont: UIFont
var rightDescriptionFont: UIFont
var leftTitleColor: UIColor
var leftDescriptionColor: UIColor
var rightTitleColor: UIColor
var rightDescriptionColor: UIColor

CardPartTableViewCardPartsCell

This will give you the ability to create custom tableView cells out of CardParts. The following code allows you to create a cell:

class MyCustomTableViewCell: CardPartTableViewCardPartsCell {

    let bag = DisposeBag()

    let attrHeader1 = CardPartTextView(type: .normal)
    let attrHeader2 = CardPartTextView(type: .normal)
    let attrHeader3 = CardPartTextView(type: .normal)

    override public init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        selectionStyle = .none

        setupCardParts([attrHeader1, attrHeader2, attrHeader3])
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setData(_ data: MyCustomStruct) {
        // Do something in here
    }
}

If you do create a custom cell, you must register it to the CardPartTableView:

tableViewCardPart.tableView.register(MyCustomTableViewCell.self, forCellReuseIdentifier: "MyCustomTableViewCell")

And then as normal, you would bind to your viewModel's data:

viewModel.listData.bind(to: tableViewPart.tableView) { tableView, indexPath, data in

    guard let cell = tableView.dequeueReusableCell(withIdentifier: "MyCustomTableViewCell", for: indexPath) as? MyCustomTableViewCell else { return UITableViewCell() }

    cell.setData(data)

    return cell
}

CardPartCollectionView

CardPartCollectionView underlying engine is RxDataSource. You can look at their documentation for a deeper look but here is an overall approach to how it works:

Start by initializing a CardPartCollectionView with a custom UICollectionViewFlowLayout:

lazy var collectionViewCardPart = CardPartCollectionView(collectionViewLayout: collectionViewLayout)
var collectionViewLayout: UICollectionViewFlowLayout = {
    let layout = UICollectionViewFlowLayout()
    layout.minimumInteritemSpacing = 12
    layout.minimumLineSpacing = 12
    layout.scrollDirection = .horizontal
    layout.itemSize = CGSize(width: 96, height: 128)
    return layout
}()

Now say you have a custom struct you want to pass into your CollectionViewCell:

struct MyStruct {
    var title: String
    var description: String
}

You will need to create a new struct to conform to SectionModelType:

struct SectionOfCustomStruct {
    var header: String
    var items: [Item]
}

extension SectionOfCustomStruct: SectionModelType {

    typealias Item = MyStruct

    init(original: SectionOfCustomStruct, items: [Item]) {
        self = original
        self.items = items
    }
}

Next, create a data source that you will bind to you data: Note: You can create a custom CardPartCollectionViewCell as well - see below.

let dataSource = RxCollectionViewSectionedReloadDataSource<SectionOfCustomStruct>(configureCell: {[weak self] (_, collectionView, indexPath, data) -> UICollectionViewCell in

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)

    return cell
})

Finally, bind your viewModel data to the collectionView and its newly created data source:

viewModel.data.asObservable().bind(to: collectionViewCardPart.collectionView.rx.items(dataSource: dataSource)).disposed(by: bag)

Note: viewModel.data will be a reactive array of SectionOfCustomStruct:

typealias ReactiveSection = BehaviorRelay<[SectionOfCustomStruct]>
var data = ReactiveSection(value: [])

CardPartCollectionViewCardPartsCell

Just 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:

class MyCustomCollectionViewCell: CardPartCollectionViewCardPartsCell {
    let bag = DisposeBag()

    let mainSV = CardPartStackView()
    let titleCP = CardPartTextView(type: .title)
    let descriptionCP = CardPartTextView(type: .normal)

    override init(frame: CGRect) {

        super.init(frame: frame)

        mainSV.axis = .vertical
        mainSV.alignment = .center
        mainSV.spacing = 10

        mainSV.addArrangedSubview(titleCP)
        mainSV.addArrangedSubview(descriptionCP)

        setupCardParts([mainSV])
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setData(_ data: MyStruct) {

        titleCP.text = data.title
        descriptionCP.text = data.description
    }
}

To use this, you must register it to the CollectionView during viewDidLoad as follows:

collectionViewCardPart.collectionView.register(MyCustomCollectionViewCell.self, forCellWithReuseIdentifier: "MyCustomCollectionViewCell")

Then, inside your data source, simply dequeue this cell:

let dataSource = RxCollectionViewSectionedReloadDataSource<SectionOfSuggestedAccounts>(configureCell: {[weak self] (_, collectionView, indexPath, data) -> UICollectionViewCell in

    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCustomCollectionViewCell", for: indexPath) as? MyCustomCollectionViewCell else { return UICollectionViewCell() }

    cell.setData(data)

    return cell
})

CardPartBarView

CardPartBarView 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

let barView = CardPartBarView()
viewModel.percent.asObservable().bind(to: barView.rx.percent).disposed(by:bag)
viewModel.barColor.asObservable().bind(to: barView.rx.barColor).disposed(by: bag)

CardPartPagedView

This CardPart allows you to create a horizontal paged carousel with page controls. Simply feed it with your desired height and an array of CardPartStackView:

let cardPartPages = CardPartPagedView(withPages: initialPages, andHeight: desiredHeight)
cardPartPages.delegate = self

This CardPart also has a delegate:

func didMoveToPage(page: Int)

Which will fire whenever the user swipes to another page

You also have the abililty to automatically move to a specific page by calling the following function on CardPartPagedView

func moveToPage(_ page: Int)

CardPartSliderView

You can set min and max value as well as bind to the current set amount:

let slider = CardPartSliderView()
slider.minimumValue = sliderViewModel.min
slider.maximumValue = sliderViewModel.max
slider.value = sliderViewModel.defaultAmount
slider.rx.value.asObservable().bind(to: sliderViewModel.amount).disposed(by: bag)

CardPartMultiSliderView

You can set min and max value as well as tint color and outer track color:

let slider = CardPartMultiSliderView()
slider.minimumValue = sliderViewModel.min
slider.maximumValue = sliderViewModel.max
slider.orientation = .horizontal
slider.value = [10, 40]
slider.trackWidth = 8
slider.tintColor = .purple
slider.outerTrackColor = .gray

CardPartSpacerView

Allows 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:

CardPartSpacerView(height: 30)

CardPartTextField

CardPartTextField 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.

let amount = CardPartTextField(format: .phone)
amount.keyboardType = .numberPad
amount.placeholder = textViewModel.placeholder
amount.font = dataFont
amount.textColor = UIColor.colorFromHex(0x3a3f47)
amount.text = textViewModel.text.value
amount.rx.text.orEmpty.bind(to: textViewModel.text).disposed(by: bag)

The different formats are as follows:

public enum CardPartTextFieldFormat {
    case none
    case currency(maxLength: Int)
    case zipcode
    case phone
    case ssn
}

CardPartOrientedView

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.

The supported orientations are as follows:

public enum Orientation {
    case top
    case bottom
}

To create an oriented view you can use the following code:

let orientedView = CardPartOrientedView(cardParts: [<elements to list vertically>], orientation: .top)

Add 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.

CardPartCenteredView

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:

class TestCardController : CardPartsViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let rightTextCardPart = CardPartTextView(type: .normal)
        rightTextCardPart.text = "Right text in a label"

        let centeredSeparator = CardPartVerticalSeparator()

        let leftTextCardPart = CardPartTextView(type: .normal)
        leftTextCardPart.text = "Left text in a label"

        let centeredCardPart = CardPartCenteredView(leftView: leftTextCardPart, centeredView: centeredSeparator, rightView: rightTextCardPart)

        setupCardParts([centeredCardPart])
    }
}

A 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.

CardPartConfettiView

Provides the capability to add confetti with various types ( diamonds, star, mixed ) and colors, along with different level of intensity

    let confettiView = CardPartConfettiView()
    confettiView.type  = .diamond
    confettiView.shape = CAEmitterLayerEmitterShape.line
    confettiView.startConfetti()

Confetti

CardPartProgressBarView

Provides the capability to configure different colors and custom marker , it's position to indicate the progress based on the value provided.

    let progressBarView = CardPartProgressBarView(barValues: barValues, barColors: barColors, marker: nil, markerLabelTitle: "", currentValue: Double(720), showShowBarValues: false)
    progressBarView.barCornerRadius = 4.0

ProgressBarView

CardPartMapView

Provides 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.

By 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.

Here's a small example of how to reactively set the location from a changing address field (See the Example project for a working example):

    let initialLocation = CLLocation(latitude: 37.430489, longitude: -122.096260)
    let cardPartMapView = CardPartMapView(type: .standard, location: initialLocation, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))

    cardPartTextField.rx.text
            .flatMap { self.viewModel.getLocation(from: $0) }
            .bind(to: cardPartMapView.rx.location)
            .disposed(by: bag)

MapView

CardPartRadioButton

Provides the capability to add radio buttons with configurable inner/outer circle line width , colors along with tap etc..

    let radioButton = CardPartRadioButton()
    radioButton.outerCircleColor = UIColor.orange
    radioButton.outerCircleLineWidth = 2.0

    radioButton2.rx.tap.subscribe(onNext: {
        print("Radio Button Tapped")
    }).disposed(by: bag)

RadioButton

CardPartSwitchView

Provides the capability to add a switch with configurable colors.

    let switchComponent = CardPartSwitchView()
    switchComponent.onTintColor = .blue

RadioButton

CardPartHistogramView

Provides the caoability to generate the bar graph based on the data ranges with customizable bars , lines, colors etc..

    let dataEntries = self.generateRandomDataEntries()
    barHistogram.width = 8
    barHistogram.spacing = 8
    barHistogram.histogramLines = HistogramLine.lines(bottom: true, middle: false, top: false)
    self.barHistogram.updateDataEntries(dataEntries: dataEntries, animated: true)

Histogram

CardPartsBottomSheetViewController

CardPartsBottomSheetViewController 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:

    let bottomSheetViewController = CardPartsBottomSheetViewController()
    bottomSheetViewController.contentVC = MyViewController()
    bottomSheetViewController.presentBottomSheet()

bottom sheet

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.

    let bottomSheetViewController = CardPartsBottomSheetViewController()
    bottomSheetViewController.contentVC = MyStickyViewController()
    bottomSheetViewController.configureForStickyMode()
    bottomSheetViewController.addShadow()
    bottomSheetViewController.presentBottomSheet(on: self.view)

sticky bottom sheet

There 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.

  • var contentVC: UIViewController?: View controller for the content of the bottom sheet. Should set this parameter before presenting bottom sheet.
  • var contentHeight: CGFloat?: Manually set a content height. If not set, height will try to be inferred from contentVC.
  • var bottomSheetBackgroundColor: UIColor: Background color of bottom sheet. Default is white.
  • var bottomSheetCornerRadius: CGFloat: Corner radius of bottom sheet. Default is 16.
  • var handleVC: CardPartsBottomSheetHandleViewController: Pill-shaped handle at the top of the bottom sheet. Can configure handleVC.handleHeight, handleVC.handleWidth, and handleVC.handleColor.
  • var handlePosition: BottomSheetHandlePosition: Positioning of handle relative to bottom sheet. Options are .above(bottomPadding), .inside(topPadding), .none. Default is above with padding 8.
  • var overlayColor: UIColor: Color of the background overlay. Default is black.
  • var shouldIncludeOverlay: Bool: Whether or not to include a background overlay. Default is true.
  • 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.
  • 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.
  • var dragVelocityToDismiss: CGFloat: Velocity that must be exceeded in order to dismiss bottom sheet if height ratio is greater than dragHeightRatioToDismiss. Default is 250.
  • 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.
  • var appearAnimationDuration: TimeInterval: Animation time for bottom sheet to appear. Default is 0.5.
  • var dismissAnimationDuration: TimeInterval: Animation time for bottom sheet to dismiss. Default is 0.5.
  • var snapBackAnimationDuration: TimeInterval: Animation time for bottom sheet to snap back to its height. Default is 0.25.
  • var animationOptions: UIView.AnimationOptions: Animation options for bottom sheet animations. Default is UIView.AnimationOptions.curveEaseIn.
  • var changeHeightAnimationDuration: TimeInterval: Animation time for bottom sheet to adjust to a new height when height is changed. Default is 0.25.
  • var shouldListenToOverlayTap: Bool: Whether or not to dismiss if a user taps in the overlay. Default is true.
  • var shouldListenToHandleDrag: Bool: Whether or not to respond to dragging on the handle. Default is true.
  • var shouldListenToContentDrag: Bool: Whether or not to respond to dragging in the content. Default is true.
  • var shouldListenToContainerDrag: Bool: Whether or not to respond to dragging in the container. Default is true.
  • var shouldRequireVerticalDrag: Bool: Whether or not to require a drag to start in the vertical direction. Default is true.
  • 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.
  • var didShow: (() -> Void)?: Callback function to be called when bottom sheet is done preseting.
  • 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).
  • var didChangeHeight: ((_ newHeight: CGFloat) -> Void)?: Callback function to be called when bottom sheet height changes from dragging or a call to updateHeight.
  • 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.
  • 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.
  • 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.
  • 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.

If 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).

Because 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.

CardPartVideoView

Provides the capability to embed AVPlayer inside a cardpart view.

guard let videoUrl = URL(string: "https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4")  else  { return }
let cardPartVideoView = CardPartVideoView(videoUrl: videoUrl)

If 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:

guard let controller = cardPartVideoView.viewController as? AVPlayerViewController else { return }
controller.delegate = self
controller.entersFullScreenWhenPlaybackBegins = true

CardPartVideoView

Card States

CardPartsViewController 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:

public enum CardState {
    case none
    case loading
    case empty
    case hasData
    case custom(String)
}

class TestCardController : CardPartsViewController  {

    var viewModel = TestViewModel()
    var titlePart = CardPartTitleView(type: .titleOnly)
    var textPart = CardPartTextView(type: .normal)
    var loadingText = CardPartTextView(type: .normal)
    var emptyText = CardPartTextView(type: .normal)
    var customText = CardPartTextView(type: .normal)

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.title.asObservable().bind(to: titlePart.rx.title).disposed(by: bag)
        viewModel.text.asObservable().bind(to: textPart.rx.text).disposed(by: bag)

        loadingText.text = "Loading..."
        emptyText.text = "No data found."
        customText.text = "I am some custom state"

        viewModel.state.asObservable().bind(to: self.rx.state).disposed(by: bag)

        setupCardParts([titlePart, textPart], forState: .hasData)
        setupCardParts([titlePart, loadingText], forState: .loading)
        setupCardParts([titlePart, emptyText], forState: .empty)
        setupCardParts([titlePart, customText], forState: .custom("myCustomState"))
    }
}

Note: There is a custom(String) state which allows you to use more than our predefined set of states:

.custom("myCustomState")

Data Binding

Data binding is implemented using the RxSwift library (https://github.com/ReactiveX/RxSwift). View models should expose their data as bindable properties using the Variable class. In the example above the view model might look like this:

class TestViewModel {

    var title = BehaviorRelay(value: "Testing")
    var text = BehaviorRelay(value: "Card Part Text")
}

Later when the view model's data has changed it can update its property by setting the value attribute of the property:

title.accept(“Hello”)

The view controller can bind the view model property to a view:

viewModel.title.asObservable().bind(to: titlePart.rx.title).disposed(by: bag)

Now, whenever the view model's title property value is changed it will automatically update the titlePart's title.

RxSwift 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.

Themes

Out 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:

// CardPartTextView
var smallTextFont: UIFont { get set }
var smallTextColor: UIColor { get set }
var normalTextFont: UIFont { get set }
var normalTextColor: UIColor { get set }
var titleTextFont: UIFont { get set }
var titleTextColor: UIColor { get set }
var headerTextFont: UIFont { get set }
var headerTextColor: UIColor { get set }
var detailTextFont: UIFont { get set }
var detailTextColor: UIColor { get set }

// CardPartTitleView
var titleFont: UIFont { get set }
var titleColor: UIColor { get set }

// CardPartButtonView
var buttonTitleFont: UIFont { get set }
var buttonTitleColor: UIColor { get set }
var buttonCornerRadius: CGFloat { get set }

Applying a theme

Generate a class as follows:

public class YourCardPartTheme: CardPartsTheme {
    ...
}

And then in your AppDelegete call YourCardPartTheme().apply() it apply your theme. If you use storyboards with CardsViewControllers 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:

required init?(coder: NSCoder) {
    YourCardPartTheme().apply()
    super.init(coder: coder)
}

Clickable Cards

You have the ability to add a tap action for each state of any given card. If a part of the card is clicked, the given action will be fired:

self.cardTapped(forState: .empty) {
    print("Card was tapped in .empty state!")
}

self.cardTapped(forState: .hasData) {
    print("Card was tapped in .hasData state!")
}

// The default state for setupCardParts([]) is .none
self.cardTapped {
    print("Card was tapped in .none state")
}

Note: It is always a good idea to weakify self in a closure:

{[weak self] in

}

Listeners

CardParts 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:

public class YourCardPartsViewController: CardPartsViewController, CardVisibilityDelegate {
    ...

    /**
    Notifies your card parts view controller of the ratio that the card is visible in its container
    and the ratio of its container that the card takes up.
    */
     func cardVisibility(cardVisibilityRatio: CGFloat, containerCoverageRatio: CGFloat) {
        // Any logic you would like to perform based on these ratios
    }
}

Delegates

Any 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.

When the view is long pressed didLongPress(_:) will be called where you can custom handle the gesture. Example: Zoom in and Zoom out on gesture state begin/ended.

    func didLongPress(_ gesture: UILongPressGestureRecognizer) -> Void

You can set the minimumPressDuration for your press to register as gesture began. The value is in seconds. default is set to 1 second.

    var minimumPressDuration: CFTimeInterval { get } // In seconds

Example:

extension MYOwnCardPartController: CardPartsLongPressGestureRecognizerDelegate {
    func didLongPress(_ gesture: UILongPressGestureRecognizer) {
        guard let v = gesture.view else { return }

        switch gesture.state {
        case .began:
            // Zoom in
        case .ended, .cancelled:
            // Zoom out
        default: break
        }
    }
    // Gesture starts registering after pressing for more than 0.5 seconds.
    var minimumPressDuration: CFTimeInterval { return 0.5 }
}

Apps That Love CardParts

Publications

Download Details:

Author: intuit
Source Code: https://github.com/intuit/CardParts 
License: View license

#swift #ios #ui #card 

A Reactive, Card-based UI Framework Built on UIKit for iOS Developers
Rupert  Beatty

Rupert Beatty

1666220700

Cards: Awesome IOS 11 Appstore Cards in Swift 5

Cards

Cards brings to Xcode the card views seen in the new iOS XI Appstore.

Getting Started

Storyboard

  • Go to main.storyboard and add a blank UIView
  • Open the Identity Inspector and type 'CardHighlight' the 'class' field
  • Make sure you have 'Cards' selected in 'Module' field
  • Switch to the Attributes Inspector and configure it as you like.

CardViewStoryboard

  • Drag a blank UIViewController and design its view as you like
  • Move to the Identity inspector
  • Type 'CardContent' in the StoryboardID field.

DetailViewStoryboard

Code

import Cards

// Aspect Ratio of 5:6 is preferred
let card = CardHighlight(frame: CGRect(x: 10, y: 30, width: 200 , height: 240))

card.backgroundColor = UIColor(red: 0, green: 94/255, blue: 112/255, alpha: 1)
card.icon = UIImage(named: "flappy")
card.title = "Welcome \nto \nCards !"
card.itemTitle = "Flappy Bird"
card.itemSubtitle = "Flap That !"
card.textColor = UIColor.white
    
card.hasParallax = true
    
let cardContentVC = storyboard!.instantiateViewController(withIdentifier: "CardContent")
card.shouldPresent(cardContentVC, from: self, fullscreen: false)
    
view.addSubview(card)

GetStarted

Prerequisites

  • Xcode 10.2 or newer
  • Swift 5.0

Installation

Cocoapods

use_frameworks!
pod 'Cards'

Manual

  • Download the repo
  • ⌘C ⌘V the 'Cards' folder in your project
  • In your Project's Info go to 'Build Phases'
  • Open 'Compile Sources' and add all the files in the folder

Overview

Customization

//Shadow settings
var shadowBlur: CGFloat
var shadowOpacity: Float
var shadowColor: UIColor
var backgroundImage: UIImage?
var backgroundColor: UIColor

var textColor: UIColor     //Color used for the labels
var insets: CGFloat     //Spacing between content and card borders
var cardRadius: CGFloat //Corner radius of the card
var icons: [UIImage]?     //DataSource for CardGroupSliding
var blurEffect: UIBlurEffectStyle //Blur effect of CardGroup

Usage

CardPlayer

let card = CardPlayer(frame: CGRect(x: 40, y: 50, width: 300 , height: 360))
card.textColor = UIColor.black
card.videoSource = URL(string: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
card.shouldDisplayPlayer(from: self)    //Required. 
        
card.playerCover = UIImage(named: "mvBackground")!  // Shows while the player is loading
card.playImage = UIImage(named: "CardPlayerPlayIcon")!  // Play button icon
        
card.isAutoplayEnabled = true
card.shouldRestartVideoWhenPlaybackEnds = true
        
card.title = "Big Buck Bunny"
card.subtitle = "Inside the extraordinary world of Buck Bunny"
card.category = "today's movie"
        
view.addSubview(card)

CardGroupSliding

    let icons: [UIImage] = [    
    
        UIImage(named: "grBackground")!,
        UIImage(named: "background")!,
        UIImage(named: "flappy")!,
        UIImage(named: "flBackground")!,
        UIImage(named: "icon")!,
        UIImage(named: "mvBackground")!
    
    ]   // Data source for CardGroupSliding
    
    let card = CardGroupSliding(frame: CGRect(x: 40, y: 50, width: 300 , height: 360))
    card.textColor = UIColor.black
    
    card.icons = icons
    card.iconsSize = 60
    card.iconsRadius = 30
    
    card.title = "from the editors"
    card.subtitle = "Welcome to XI Cards !"

    view.addSubview(card)

Documentation

See the Wiki, to learn in depth infos about Cards.
GO!

Issues & Feature requests

If you encounter any problems or have any trouble using Cards, feel free to open an issue. I'll answer you as soon as I see it.

New features, or improvements to the framework are welcome (open an issue).

Thanksto

Download Details:

Author: PaoloCuscela
Source Code: https://github.com/PaoloCuscela/Cards 
License: MIT license

#swift #ios #card #uikit 

Cards: Awesome IOS 11 Appstore Cards in Swift 5
Rupert  Beatty

Rupert Beatty

1666113120

KolodaView: A Class Designed to Simplify The Implementation

KolodaView

Our designer Dmitry Goncharov decided to create an animation that follows Tinder’s trend. We called our Tinder-style card-based animation Koloda which is a Ukrainian word for the deck (of cards). The component can be used in different local event apps, and even in Tinder if it adds a possibility to choose dating places. The concept created by Dmitriy was implemented by Eugene Andreyev, our iOS developer.

Preview Preview

Purpose

KolodaView is a class designed to simplify the implementation of Tinder like cards on iOS. It adds convenient functionality such as a UITableView-style dataSource/delegate interface for loading views dynamically, and efficient view loading, unloading .

Supported OS & SDK Versions

  • Supported build target - iOS 11.0 (Xcode 9)

ARC Compatibility

KolodaView requires ARC.

Thread Safety

KolodaView is subclassed from UIView and - as with all UIKit components - it should only be accessed from the main thread. You may wish to use threads for loading or updating KolodaView contents or items, but always ensure that once your content has loaded, you switch back to the main thread before updating the KolodaView.

Prototype of Koloda in Pixate

Our designer created the mock-up in Photoshop and used Pixate for prototyping Koloda. The prototype we created reproduced the behavior of cards exactly how we wanted it.

The main Pixate toolset includes layers, an action kit, and animations. After the assets are loaded and located on the artboard, you can start working on layers, and then proceed to reproduce interactions.

At first, we made the cards move horizontally and fly away from the screen once they cross a certain vertical line. The designer also made the cards change their transparency and spin a bit during interactions.

Then, we needed to make a new card appear in a way as if it collects itself from the background, so we had to stretch and scale it. We set a scale for the prototype from 3.5x (the size, when a card is still on the background) to 1x.

Preview

For a better effect, we added a few bounce animations and that was it! The prototype was ready for development.

Building Koloda animation

There are a few ready-made mobile libraries and iOS animation examples out there that an app developer can use.

We wanted the animation to be as simple and convenient as views like UITableView. Therefore, we created a custom component for the animation. It consists of the three main parts:

  1. DraggableCardView – a card that displays content.
  2. OverlayView – a dynamic view that changes depending on where a user drags a card (to the left or to the right).
  3. KolodaView – a view that controls loading and interactions between cards.

DraggableCardView implementation

We implemented DraggableCardView with the help of UIPanGestureRecognizer and CGAffineTransform. See the coding part below:

func panGestureRecognized(gestureRecognizer: UIPanGestureRecognizer) {
    xDistanceFromCenter = gestureRecognizer.translationInView(self).x
    yDistanceFromCenter = gestureRecognizer.translationInView(self).y

    let touchLocation = gestureRecognizer.locationInView(self)
    switch gestureRecognizer.state {
    case .Began:
        originalLocation = center

        animationDirection = touchLocation.y >= frame.size.height / 2 ? -1.0 : 1.0      
        layer.shouldRasterize = true
        break

    case .Changed:

        let rotationStrength = min(xDistanceFromCenter! / self.frame.size.width, rotationMax)
        let rotationAngle = animationDirection! * defaultRotationAngle * rotationStrength
        let scaleStrength = 1 - ((1 - scaleMin) * fabs(rotationStrength))
        let scale = max(scaleStrength, scaleMin)

        layer.rasterizationScale = scale * UIScreen.mainScreen().scale
 
        let transform = CGAffineTransformMakeRotation(rotationAngle)
        let scaleTransform = CGAffineTransformScale(transform, scale, scale)

        self.transform = scaleTransform
        center = CGPoint(x: originalLocation!.x + xDistanceFromCenter!, y: originalLocation!.y + yDistanceFromCenter!)
           
        updateOverlayWithFinishPercent(xDistanceFromCenter! / frame.size.width)
        //100% - for proportion
        delegate?.cardDraggedWithFinishPercent(self, percent: min(fabs(xDistanceFromCenter! * 100 / frame.size.width), 100))

        break
    case .Ended:
        swipeMadeAction()

        layer.shouldRasterize = false
    default:
        break
    }
}

The overlay gets updated with every move. It changes transparency in the process of animation ( 5% – hardly seen, 100% – clearly seen).

In order to avoid a card’s edges becoming sharp during movement, we used the shouldRasterize layer option.

We had to consider a reset situation which happens once a card fails to reach the action margin (ending point) and comes back to the initial state. We used the Facebook Pop framework for this situation, and also for the “undo” action.

OverlayView implementation

OverlayView is a view that is added on top of a card during animation. It has only one variable called overlayState with two options: when a user drags a card to the left, the overlayState adds a red hue to the card, and when a card is moved to the right, the variable uses the other option to make the UI become green.

To implement custom actions for the overlay, we should inherit from OverlayView, and reload the operation didSet in the overlayState:

public enum OverlayMode{
   case None
   case Left
   case Right
}

public class OverlayView: UIView {
    public var overlayState:OverlayMode = OverlayMode.None
}

class ExampleOverlayView: OverlayView {
override var overlayState:OverlayMode  {
    didSet {
        switch overlayState {
           case .Left :
               overlayImageView.image = UIImage(named: overlayLeftImageName)
           case .Right :
               overlayImageView.image = UIImage(named: overlayRightImageName)
           default:
               overlayImageView.image = nil
           }          

       }

   }

}

KolodaView implementation

The KolodaView class does a card loading and card management job. You can either implement it in the code or in the Interface Builder. Then, you should specify a data source and add a delegate (optional). After that, you should implement the following methods of the KolodaViewDataSource protocol in the data source-class:

func kolodaNumberOfCards(koloda: KolodaView) -> UInt
    func kolodaViewForCardAtIndex(koloda: KolodaView, index: UInt) -> UIView
    func kolodaViewForCardOverlayAtIndex(koloda: KolodaView, index: UInt) -> OverlayView?

KolodaView had to display a correct number of cards below the top card and make them occupy the right positions when the animation starts. To make it possible, we had to calculate frames for all the cards by adding the corresponding indexes to each element. For example, the first card has an [i] index, the second one would have an [i+1] index, the third – [i+2], and so on:

private func frameForCardAtIndex(index: UInt) -> CGRect {
    let bottomOffset:CGFloat = 0
    let topOffset = backgroundCardsTopMargin * CGFloat(self.countOfVisibleCards - 1)
    let xOffset = backgroundCardsLeftMargin * CGFloat(index)
    let scalePercent = backgroundCardsScalePercent
    let width = CGRectGetWidth(self.frame) * pow(scalePercent, CGFloat(index))
    let height = (CGRectGetHeight(self.frame) - bottomOffset - topOffset) * pow(scalePercent, CGFloat(index))
    let multiplier: CGFloat = index > 0 ? 1.0 : 0.0
    let previousCardFrame = index > 0 ? frameForCardAtIndex(max(index - 1, 0)) : CGRectZero
    let yOffset = (CGRectGetHeight(previousCardFrame) - height + previousCardFrame.origin.y + backgroundCardsTopMargin) * multiplier
    let frame = CGRect(x: xOffset, y: yOffset, width: width, height: height)     

    return frame
}

Now, since we know the indexes, card frames, and also the percent at which the animation ends (from the DraggableCardView), we can easily find out where the cards below will go once an upper card is swiped. After that, we can implement PercentDrivenAnimation.

Building Koloda v.2

The main difference between the first and second versions of the Koloda animation is in the cards’ layout. The front card in the new version is placed in the middle of the screen and the back card is stretched on the background. In addition, the back card does not respond to the movement of the front card and arrives with a bounce effect after the front card is swiped.

Also, the second version of Koloda was easier to build thanks to the prototype of it in Pixate.

Preview

Implementation of KolodaView v.2

To implement KolodaView v.2, we had to place the cards differently, so we put the method frameForCardAtIndex in the public interface.

In KolodaView inheritor we overrode the method and put the cards in the following order:

override func frameForCardAtIndex(index: UInt) -> CGRect {
    if index == 0 {
        let bottomOffset:CGFloat = defaultBottomOffset
        let topOffset:CGFloat = defaultTopOffset
        let xOffset:CGFloat = defaultHorizontalOffset
        let width = CGRectGetWidth(self.frame ) - 2 * defaultHorizontalOffset
        let height = width * defaultHeightRatio
        let yOffset:CGFloat = topOffset
        let frame = CGRect(x: xOffset, y: yOffset, width: width, height: height)
        return frame
    } else if index == 1 {
        let horizontalMargin = -self.bounds.width * backgroundCardHorizontalMarginMultiplier
        let width = self.bounds.width * backgroundCardScalePercent
        let height = width * defaultHeightRatio
        return CGRect(x: horizontalMargin, y: 0, width: width, height: height)
    }
    return CGRectZero
}

We place frontCard in the middle of KolodaView, and stretch the background card with a scalePercent that equals 1.5.

Preview

Bounce animation for the background card

Since the background card arrives with a bounce effect and changes its transparency while moving, we created a new delegate method:

KolodaView - func kolodaBackgroundCardAnimation(koloda: KolodaView) -> POPPropertyAnimation?

In this method, POPAnimation is created and passed to Koloda. Then, Koloda uses it for animating frame changes after a user swipes a card. If the delegate returns nil, it means that Koloda uses default animation.

Below you can see the implementation of this method in the delegate:

func kolodaBackgroundCardAnimation(koloda: KolodaView) -> POPPropertyAnimation? {
    let animation = POPSpringAnimation(propertyNamed: kPOPViewFrame)
    animation.springBounciness = frameAnimationSpringBounciness
    animation.springSpeed = frameAnimationSpringSpeed
    return animation
}

Installation

To install via CocoaPods add this lines to your Podfile. You need CocoaPods v. 1.1 or higher

use_frameworks!
pod "Koloda"

To install via Carthage add this lines to your Cartfile

github "Yalantis/Koloda"

To install manually the KolodaView class in an app, just drag the KolodaView, DraggableCardView, OverlayView class files (demo files and assets are not needed) into your project. Also you need to install facebook-pop. Or add bridging header if you are using CocoaPods.

Usage

Import Koloda module to your MyKolodaViewController class

import Koloda

Add KolodaView to MyKolodaViewController, then set dataSource and delegate for it

class MyKolodaViewController: UIViewController {
    @IBOutlet weak var kolodaView: KolodaView!

    override func viewDidLoad() {
        super.viewDidLoad()

        kolodaView.dataSource = self
        kolodaView.delegate = self
    }
}

Conform your MyKolodaViewController to KolodaViewDelegate protocol and override some methods if you need, e.g.

extension MyKolodaViewController: KolodaViewDelegate {
    func kolodaDidRunOutOfCards(_ koloda: KolodaView) {
        koloda.reloadData()
    }

    func koloda(_ koloda: KolodaView, didSelectCardAt index: Int) {
        UIApplication.shared.openURL(URL(string: "https://yalantis.com/")!)
    }
}

Conform MyKolodaViewController to KolodaViewDataSource protocol and implement all the methods , e.g.

extension MyKolodaViewController: KolodaViewDataSource {

    func kolodaNumberOfCards(_ koloda:KolodaView) -> Int {
        return images.count
    }

    func kolodaSpeedThatCardShouldDrag(_ koloda: KolodaView) -> DragSpeed {
        return .fast
    }

    func koloda(_ koloda: KolodaView, viewForCardAt index: Int) -> UIView {
        return UIImageView(image: images[index])
    }

    func koloda(_ koloda: KolodaView, viewForCardOverlayAt index: Int) -> OverlayView? {
        return Bundle.main.loadNibNamed("OverlayView", owner: self, options: nil)[0] as? OverlayView
    }
}

KolodaView works with default implementation. Override it to customize its behavior

Also check out an example project with carthage.

Properties

The KolodaView has the following properties:

weak var dataSource: KolodaViewDataSource?

An object that supports the KolodaViewDataSource protocol and can provide views to populate the KolodaView.

weak var delegate: KolodaViewDelegate?

An object that supports the KolodaViewDelegate protocol and can respond to KolodaView events.

private(set) public var currentCardIndex

The index of front card in the KolodaView (read only).

private(set) public var countOfCards

The count of cards in the KolodaView (read only). To set this, implement the kolodaNumberOfCards: dataSource method.

public var countOfVisibleCards

The count of displayed cards in the KolodaView.

Methods

The KolodaView class has the following methods:

public func reloadData()

This method reloads all KolodaView item views from the dataSource and refreshes the display.

public func resetCurrentCardIndex()

This method resets currentCardIndex and calls reloadData, so KolodaView loads from the beginning.

public func revertAction()

Applies undo animation and decrement currentCardIndex.

public func applyAppearAnimationIfNeeded()

Applies appear animation if needed.

public func swipe(_ direction: SwipeResultDirection, force: Bool = false)

Applies swipe animation and action, increment currentCardIndex.

open func frameForCard(at index: Int) -> CGRect

Calculates frames for cards. Useful for overriding. See example to learn more about it.

Protocols

The KolodaView follows the Apple convention for data-driven views by providing two protocol interfaces, KolodaViewDataSource and KolodaViewDelegate.

The KolodaViewDataSource protocol has the following methods:

func koloda(_ kolodaNumberOfCards koloda: KolodaView) -> Int

Return the number of items (views) in the KolodaView.

func koloda(_ koloda: KolodaView, viewForCardAt index: Int) -> UIView

Return a view to be displayed at the specified index in the KolodaView.

func koloda(_ koloda: KolodaView, viewForCardOverlayAt index: Int) -> OverlayView?

Return a view for card overlay at the specified index. For setting custom overlay action on swiping(left/right), you should override didSet of overlayState property in OverlayView. (See Example)

func kolodaSpeedThatCardShouldDrag(_ koloda: KolodaView) -> DragSpeed

Allow management of the swipe animation duration

The KolodaViewDelegate protocol has the following methods:

func koloda(_ koloda: KolodaView, allowedDirectionsForIndex index: Int) -> [SwipeResultDirection]

Return the allowed directions for a given card, defaults to [.left, .right]

func koloda(_ koloda: KolodaView, shouldSwipeCardAt index: Int, in direction: SwipeResultDirection) -> Bool

This method is called before the KolodaView swipes card. Return true or false to allow or deny the swipe.

func koloda(_ koloda: KolodaView, didSwipeCardAt index: Int, in direction: SwipeResultDirection)

This method is called whenever the KolodaView swipes card. It is called regardless of whether the card was swiped programatically or through user interaction.

func kolodaDidRunOutOfCards(_ koloda: KolodaView)

This method is called when the KolodaView has no cards to display.

func koloda(_ koloda: KolodaView, didSelectCardAt index: Int)

This method is called when one of cards is tapped.

func kolodaShouldApplyAppearAnimation(_ koloda: KolodaView) -> Bool

This method is fired on reload, when any cards are displayed. If you return YES from the method or don't implement it, the koloda will apply appear animation.

func kolodaShouldMoveBackgroundCard(_ koloda: KolodaView) -> Bool

This method is fired on start of front card swipping. If you return YES from the method or don't implement it, the koloda will move background card with dragging of front card.

func kolodaShouldTransparentizeNextCard(_ koloda: KolodaView) -> Bool

This method is fired on koloda's layout and after swiping. If you return YES from the method or don't implement it, the koloda will transparentize next card below front card.

func koloda(_ koloda: KolodaView, draggedCardWithPercentage finishPercentage: CGFloat, in direction: SwipeResultDirection)

This method is called whenever the KolodaView recognizes card dragging event.

func kolodaSwipeThresholdRatioMargin(_ koloda: KolodaView) -> CGFloat?

Return the percentage of the distance between the center of the card and the edge at the drag direction that needs to be dragged in order to trigger a swipe. The default behavior (or returning NIL) will set this threshold to half of the distance

func kolodaDidResetCard(_ koloda: KolodaView)

This method is fired after resetting the card.

func koloda(_ koloda: KolodaView, didShowCardAt index: Int)

This method is called after a card has been shown, after animation is complete

func koloda(_ koloda: KolodaView, didRewindTo index: Int)

This method is called after a card was rewound, after animation is complete

func koloda(_ koloda: KolodaView, shouldDragCardAt index: Int) -> Bool

This method is called when the card is beginning to be dragged. If you return YES from the method or don't implement it, the card will move in the direction of the drag. If you return NO the card will not move.

Release Notes

Version 5.0.1

  • added posibility to determine index of rewound card
  • fixed crash after drugging card

Version 5.0

Version 4.7

Version 4.6

Version 4.5

Version 4.4

Version 4.3

  • Swift 4 support
  • iOS 11 frame bugfix

Version 4.0

  • Swift 3 support
  • Get rid of UInt
  • Common bugfix

Version 3.1

  • Multiple Direction Support
  • Delegate methods for swipe disabling

Version 3.0

  • Ability to dynamically insert/delete/reload specific cards
  • External animator
  • Major refactoring. More information
  • Swift 2.2 support

Version 2.0

  • Swift 2.0 support

Version 1.1

  • New delegate methods
  • Fixed minor issues

Version 1.0

  • Release version.

Apps using KolodaView

Preview

Let us know!

We’d be really happy if you sent us links to your projects where you use our component. Just send an email to github@yalantis.com And do let us know if you have any questions or suggestion regarding the animation.

P.S. We’re going to publish more awesomeness wrapped in code and a tutorial on how to make UI for iOS (Android) better than better. Stay tuned!

Download Details:

Author: Yalantis
Source Code: https://github.com/Yalantis/Koloda 
License: MIT license

#swift #ios #card 

KolodaView: A Class Designed to Simplify The Implementation
Rupert  Beatty

Rupert Beatty

1666104960

BulletinBoard: General-purpose Contextual Cards for iOS

BulletinBoard

BulletinBoard is an iOS library that generates and manages contextual cards displayed at the bottom of the screen. It is especially well suited for quick user interactions such as onboarding screens or configuration.

It has an interface similar to the cards displayed by iOS for AirPods, Apple TV/HomePod configuration and NFC tag scanning. It supports both the iPhone, iPhone X and the iPad.

It has built-in support for accessibility features such as VoiceOver and Switch Control.

Here are some screenshots showing what you can build with BulletinBoard:

Demo Screenshots

Requirements

  • Xcode 11 and later
  • iOS 9 and later
  • Swift 5.1 and later (also works with Objective-C).

Demo

A demo project is included in the BulletinBoard workspace. It demonstrates how to:

  • integrate the library (setup, data flow)
  • create standard page cards
  • create custom page subclasses to add features
  • create custom cards from scratch

Two demo targets are available:

  • BB-Swift (demo written in Swift)
  • BB-ObjC (demo written in Objective-C)

Build and run the scheme for your favorite language to open the demo app.

Installation

Swift Package Manager

To install BulletinBoard using the Swift Package Manager, add this dependency to your Package.swift file:

.package(url: "https://github.com/alexaubry/BulletinBoard.git", from: "5.0.0")

CocoaPods

To install BulletinBoard using CocoaPods, add this line to your Podfile:

pod 'BulletinBoard'

Carthage

To install BulletinBoard using Carthage, add this line to your Cartfile:

github "alexaubry/BulletinBoard"

Documentation

  • The full library documentation is available here.
  • To learn how to start using BulletinBoard, check out our Getting Started guide.

Contributing

Thank you for your interest in the project! Contributions are welcome and appreciated.

Make sure to read these guides before getting started:

Download Details:

Author: Alexisakers
Source Code: https://github.com/alexisakers/BulletinBoard 
License: MIT license

#swift #card #ios 

BulletinBoard: General-purpose Contextual Cards for iOS

5 JavaScript API Project Ideas for Beginners

This tutorial shows 5 JavaScript API project ideas for beginners with source code:

  1. Pokemon Card Generator
  2. Random Joke Generator
  3. Dictionary App
  4. Country Guide App
  5. Quote Generator App

API Stands for “Application Programming Interface”. APIs are in trend there are plenty of developers using APIs to add extra features & functionalities to their applications.

APIs help developers to spend less time on coding. It allows you to get access to the data and set of functionalities already defined at the time of API development.


1. Pokemon Card Generator:

In this project, we have a ‘Generate’ button. When the user clicks on the ‘Generate’ button, a pokemon trump card with random pokemon is displayed. This card consists of the pokemon name, image and some cool stats. The API used for this project is – ‘PokeAPI’.

HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Pokemon Card Generator</title>
    <!-- Google Fonts -->
    <link
      href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap"
      rel="stylesheet"
    />
    <!-- Stylesheet -->
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="container">
      <div id="card"></div>
      <button id="btn">Generate</button>
    </div>
    <!-- Script -->
    <script src="script.js"></script>
  </body>
</html>

CSS:

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-family: "Poppins", sans-serif;
}
body {
  background-color: #eff3ff;
}
.container {
  width: 350px;
  position: absolute;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
}
#card {
  position: relative;
  width: 100%;
  padding: 30px 20px;
  box-shadow: 0 20px 30px rgba(0, 0, 0, 0.15);
  border-radius: 10px;
}
#card img {
  display: block;
  width: 180px;
  max-height: 200px;
  position: relative;
  margin: 20px auto;
}
.hp {
  width: 80px;
  background-color: #ffffff;
  text-align: center;
  padding: 8px 0;
  border-radius: 30px;
  margin-left: auto;
  font-weight: 400;
}
.poke-name {
  text-align: center;
  font-weight: 600;
}
.types {
  display: flex;
  justify-content: space-around;
  margin: 20px 0 40px 0;
}
.hp span,
.types span {
  font-size: 12px;
  letter-spacing: 0.4px;
  font-weight: 600;
}
.types span {
  padding: 5px 20px;
  border-radius: 20px;
  color: #ffffff;
}
.stats {
  display: flex;
  align-items: center;
  justify-content: space-between;
  text-align: center;
}
.stats p {
  color: #404060;
}
#btn {
  display: block;
  padding: 15px 60px;
  font-size: 18px;
  background-color: #101010;
  color: #ffffff;
  position: relative;
  margin: 30px auto;
  border: none;
  border-radius: 5px;
}

Javascript:

const typeColor = {
  bug: "#26de81",
  dragon: "#ffeaa7",
  electric: "#fed330",
  fairy: "#FF0069",
  fighting: "#30336b",
  fire: "#f0932b",
  flying: "#81ecec",
  grass: "#00b894",
  ground: "#EFB549",
  ghost: "#a55eea",
  ice: "#74b9ff",
  normal: "#95afc0",
  poison: "#6c5ce7",
  psychic: "#a29bfe",
  rock: "#2d3436",
  water: "#0190FF",
};
const url = " https://pokeapi.co/api/v2/pokemon/";
const card = document.getElementById("card");
const btn = document.getElementById("btn");

let getPokeData = () => {
  // Generate a random number between 1 and 150
  let id = Math.floor(Math.random() * 150) + 1;
  // Combine the pokeapi url with pokemon id
  const finalUrl = url + id;
  // Fetch generated URL
  fetch(finalUrl)
    .then((response) => response.json())
    .then((data) => {
      generateCard(data);
    });
};

//Generate Card

let generateCard = (data) => {
  // Get necessary data and assign it to variables
  console.log(data);
  const hp = data.stats[0].base_stat;
  const imgSrc = data.sprites.other.dream_world.front_default;
  const pokeName = data.name[0].toUpperCase() + data.name.slice(1);
  const statAttack = data.stats[1].base_stat;
  const statDefense = data.stats[2].base_stat;
  const statSpeed = data.stats[5].base_stat;

  // Set themeColor based on pokemon type
  const themeColor = typeColor[data.types[0].type.name];
  console.log(themeColor);
  card.innerHTML = `
        <p class="hp">
          <span>HP</span>
            ${hp}
        </p>
        <img src=${imgSrc} />
        <h2 class="poke-name">${pokeName}</h2>
        <div class="types">
         
        </div>
        <div class="stats">
          <div>
            <h3>${statAttack}</h3>
            <p>Attack</p>
          </div>
          <div>
            <h3>${statDefense}</h3>
            <p>Defense</p>
          </div>
          <div>
            <h3>${statSpeed}</h3>
            <p>Speed</p>
          </div>
        </div>
  `;
  appendTypes(data.types);
  styleCard(themeColor);
};
let appendTypes = (types) => {
  types.forEach((item) => {
    let span = document.createElement("SPAN");
    span.textContent = item.type.name;
    document.querySelector(".types").appendChild(span);
  });
};
let styleCard = (color) => {
  card.style.background = `radial-gradient(circle at 50% 0%, ${color} 36%, #ffffff 36%)`;
  card.querySelectorAll(".types span").forEach((typeColor) => {
    typeColor.style.backgroundColor = color;
  });
};

btn.addEventListener("click", getPokeData);
window.addEventListener("load", getPokeData);

2. Random Joke Generator:

We have used this API for the project. This app has a sleek UI and consists of a ‘Generate’ button. When the user clicks on this button, we fetch jokes from the API. We then display one of these jokes randomly. This project is suited for beginners who have little to no knowledge of fetch.

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Random Joke Generator</title>
    <!--Google Font-->
    <link href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;600&display=swap" rel="stylesheet">
    <!-- Stylesheet -->
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="wrapper">
        <span>&#128514;</span>
        <p id="joke"></p>
        <button id="btn">Get Random Joke</button>
    </div>

    <!-- Script -->
    <script src="script.js"></script>
</body>
</html>

CSS:

*{
    padding: 0;
    margin: 0;
    box-sizing: border-box;
    font-family: "Rubik",sans-serif;
}
body{
    background-color: #fab22e;
}
.wrapper{
    width: 80vmin;
    padding: 50px 40px;
    background-color: #15161a;
    position: absolute;
    transform: translate(-50%, -50%);
    top: 50%;
    left: 50%;
    border-radius: 5px;
    box-shadow: 20px 20px 40px rgba(97,63,0,0.4);
}
span{
    display: block;
    text-align: center;
    font-size: 100px;
}
p{
    font-size: 16px;
    color: #ffffff;
    font-weight: 400;
    text-align: center;
    word-wrap: break-word;
    line-height: 35px;
    margin: 30px 0;
    opacity: 0;
}
.fade{
    opacity: 1;
    transition: opacity 1.5s;
}
button{
    display: block;
    background-color: #fab22e;
    border: none;
    padding: 5px;
    font-size: 18px;
    color: #171721;
    font-weight: 600;
    padding: 12px 25px;
    margin: 0 auto;
    border-radius: 5px;
    cursor: pointer;
    outline: none;
}

Javascript:

const jokeContainer = document.getElementById("joke");
const btn = document.getElementById("btn");
const url = "https://v2.jokeapi.dev/joke/Any?blacklistFlags=nsfw,religious,political,racist,sexist,explicit&type=single";

let getJoke = () => {
    jokeContainer.classList.remove("fade");
    fetch(url)
    .then(data => data.json())
    .then(item =>{
        jokeContainer.textContent = `${item.joke}`;
        jokeContainer.classList.add("fade");
    });
}
btn.addEventListener("click",getJoke);
getJoke();

 


3. Dictionary App:

The third project in this compilation is – Dictionary App. The dictionary app is my favourite project. In this app, we have a search bar. The user enters a word in the search bar and hits the search button. We then display the part of speech for the word along with its pronunciation. We also output its meaning and usage in a sentence.

HTML:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <!-- Font Awesome -->
        <link
            rel="stylesheet"
            href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
        />
        <!-- Google Fonts -->
        <link
            href="https://fonts.googleapis.com/css2?family=Poppins&display=swap"
            rel="stylesheet"
        />
        <!-- Stylesheet -->
        <link rel="stylesheet" href="style.css" />
        <title>Dictionary</title>
    </head>
    <body>
        <audio id="sound"></audio>
        <div class="container">
            <div class="search-box">
                <input
                    type="text"
                    placeholder="Type the word here.."
                    id="inp-word"
                />
                <button id="search-btn">Search</button>
            </div>
            <div class="result" id="result"></div>
        </div>
        <!-- Script -->
        <script src="script.js"></script>
    </body>
</html>

CSS:

* {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}
*:not(i) {
    font-family: "Poppins", sans-serif;
}
body {
    background-color: #ae9cff;
}
.container {
    background-color: #ffffff;
    width: 90vmin;
    position: absolute;
    transform: translate(-50%, -50%);
    top: 50%;
    left: 50%;
    padding: 80px 50px;
    border-radius: 10px;
    box-shadow: 0 20px 40px rgba(38, 33, 61, 0.2);
}
.search-box {
    width: 100%;
    display: flex;
    justify-content: space-between;
}
.search-box input {
    padding: 5px;
    width: 70%;
    border: none;
    outline: none;
    border-bottom: 3px solid #ae9cff;
    font-size: 16px;
}
.search-box button {
    padding: 15px 0;
    width: 25%;
    background-color: #ae9cff;
    border: none;
    outline: none;
    color: #ffffff;
    border-radius: 5px;
}
.result {
    position: relative;
}
.result h3 {
    font-size: 30px;
    color: #1f194c;
}
.result .word {
    display: flex;
    justify-content: space-between;
    margin-top: 80px;
}
.result button {
    background-color: transparent;
    color: #ae9cff;
    border: none;
    outline: none;
    font-size: 18px;
}
.result .details {
    display: flex;
    gap: 10px;
    color: #b3b6d4;
    margin: 5px 0 20px 0;
    font-size: 14px;
}
.word-meaning {
    color: #575a7b;
}
.word-example {
    color: #575a7b;
    font-style: italic;
    border-left: 5px solid #ae9cff;
    padding-left: 20px;
    margin-top: 30px;
}
.error {
    margin-top: 80px;
    text-align: center;
}

Javascript:

const url = "https://api.dictionaryapi.dev/api/v2/entries/en/";
const result = document.getElementById("result");
const sound = document.getElementById("sound");
const btn = document.getElementById("search-btn");

btn.addEventListener("click", () => {
    let inpWord = document.getElementById("inp-word").value;
    fetch(`${url}${inpWord}`)
        .then((response) => response.json())
        .then((data) => {
            console.log(data);
            result.innerHTML = `
            <div class="word">
                    <h3>${inpWord}</h3>
                    <button onclick="playSound()">
                        <i class="fas fa-volume-up"></i>
                    </button>
                </div>
                <div class="details">
                    <p>${data[0].meanings[0].partOfSpeech}</p>
                    <p>/${data[0].phonetic}/</p>
                </div>
                <p class="word-meaning">
                   ${data[0].meanings[0].definitions[0].definition}
                </p>
                <p class="word-example">
                    ${data[0].meanings[0].definitions[0].example || ""}
                </p>`;
            sound.setAttribute("src", `https:${data[0].phonetics[0].audio}`);
        })
        .catch(() => {
            result.innerHTML = `<h3 class="error">Couldn't Find The Word</h3>`;
        });
});
function playSound() {
    sound.play();
}

4. Country Guide App:

The next project on our list is – The Country Guide App. In this project, the user enters the country name and hits search. We display an output that consists of the country name, country flag and other statistics about the country. This project is super-easy to create.

HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Country Guide App</title>
    <!--Google Fonts -->
    <link
      href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500&display=swap"
      rel="stylesheet"
    />
    <!-- Stylesheet -->
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="container">
      <div class="search-wrapper">
        <input
          type="text"
          id="country-inp"
          placeholder="Enter a country name here..."
        />
        <button id="search-btn">Search</button>
      </div>
      <div id="result"></div>
    </div>
    <!-- Script -->
    <script src="script.js"></script>
  </body>
</html>

CSS:

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-family: "Poppins", sans-serif;
}
body {
  background-color: #3d64e6;
}
.container {
  background-color: #ffffff;
  width: 80vw;
  max-width: 37.5em;
  padding: 3em 2.5em;
  position: absolute;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
  border-radius: 0.62em;
  box-shadow: 0 1.25em 1.8em rgba(8, 21, 65, 0.25);
}
.search-wrapper {
  display: grid;
  grid-template-columns: 9fr 3fr;
  gap: 1.25em;
}
.search-wrapper button {
  font-size: 1em;
  background-color: #3d64e6;
  color: #ffffff;
  padding: 0.8em 0;
  border: none;
  border-radius: 1.5em;
}
.search-wrapper input {
  font-size: 1em;
  padding: 0 0.62em;
  border: none;
  border-bottom: 2px solid #3d64e6;
  outline: none;
  color: #222a43;
}
#result {
  margin-top: 1.25em;
}
.container .flag-img {
  display: block;
  width: 45%;
  min-width: 7.5em;
  margin: 1.8em auto 1.2em auto;
}
.container h2 {
  font-weight: 600;
  color: #222a43;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 2px;
  margin-bottom: 1.8em;
}
.data-wrapper {
  margin-bottom: 1em;
  letter-spacing: 0.3px;
}
.container h4 {
  display: inline;
  font-weight: 500;
  color: #222a43;
}
.container span {
  color: #5d6274;
}
.container h3 {
  text-align: center;
  font-size: 1.2em;
  font-weight: 400;
  color: #ff465a;
}

Javascript:

let searchBtn = document.getElementById("search-btn");
let countryInp = document.getElementById("country-inp");
searchBtn.addEventListener("click", () => {
  let countryName = countryInp.value;
  let finalURL = `https://restcountries.com/v3.1/name/${countryName}?fullText=true`;
  console.log(finalURL);
  fetch(finalURL)
    .then((response) => response.json())
    .then((data) => {
      //   console.log(data[0]);
      //   console.log(data[0].capital[0]);
      //   console.log(data[0].flags.svg);
      //   console.log(data[0].name.common);
      //   console.log(data[0].continents[0]);
      //   console.log(Object.keys(data[0].currencies)[0]);
      //   console.log(data[0].currencies[Object.keys(data[0].currencies)].name);
      //   console.log(
      //     Object.values(data[0].languages).toString().split(",").join(", ")
      //   );
      result.innerHTML = `
        <img src="${data[0].flags.svg}" class="flag-img">
        <h2>${data[0].name.common}</h2>
        <div class="wrapper">
            <div class="data-wrapper">
                <h4>Capital:</h4>
                <span>${data[0].capital[0]}</span>
            </div>
        </div>
        <div class="wrapper">
            <div class="data-wrapper">
                <h4>Continent:</h4>
                <span>${data[0].continents[0]}</span>
            </div>
        </div>
         <div class="wrapper">
            <div class="data-wrapper">
                <h4>Population:</h4>
                <span>${data[0].population}</span>
            </div>
        </div>
        <div class="wrapper">
            <div class="data-wrapper">
                <h4>Currency:</h4>
                <span>${
                  data[0].currencies[Object.keys(data[0].currencies)].name
                } - ${Object.keys(data[0].currencies)[0]}</span>
            </div>
        </div>
         <div class="wrapper">
            <div class="data-wrapper">
                <h4>Common Languages:</h4>
                <span>${Object.values(data[0].languages)
                  .toString()
                  .split(",")
                  .join(", ")}</span>
            </div>
        </div>
      `;
    })
    .catch(() => {
      if (countryName.length == 0) {
        result.innerHTML = `<h3>The input field cannot be empty</h3>`;
      } else {
        result.innerHTML = `<h3>Please enter a valid country name.</h3>`;
      }
    });
});

 


5. Quote Generator App:

In the final project, we have a Quote Generator App. As the name suggests, we generate a random quote every time the user clicks on the generate button. This is once a beginner-friendly project.

HTML:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Random Quote Generator</title>
    <!-- Google Font -->
    <link
      href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap"
      rel="stylesheet"
    />
    <!-- Stylesheet -->
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="wrapper">
      <div class="container">
        <p id="quote">
          Lorem ipsum dolor, sit amet consectetur adipisicing elit. Voluptas,
          magni.
        </p>
        <h3 id="author">Lorem, ipsum.</h3>
        <button id="btn">Get Quote</button>
      </div>
    </div>
    <!-- Script -->
    <script src="script.js"></script>
  </body>
</html>

CSS:

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-family: "Poppins", sans-serif;
}
body {
  background-color: #f43543;
}
.wrapper {
  width: 400px;
  position: absolute;
  transform: translate(-50%, -50%);
  top: 50%;
  left: 50%;
}
.container {
  width: 100%;
  background-color: #f43543;
  padding: 50px 40px;
  box-shadow: 0 20px 65px rgba(87, 11, 16, 0.5);
  position: relative;
  border-radius: 8px;
  text-align: center;
}
.container:after {
  content: "";
  position: absolute;
  width: 80%;
  height: 120%;
  background-color: #ffffff;
  z-index: -1;
  top: -10%;
  left: 10%;
}
.container p {
  color: #fdd8d8;
  line-height: 2;
  font-size: 18px;
}
.container h3 {
  color: #ffffff;
  margin: 20px 0 60px 0;
  font-weight: 600;
  text-transform: capitalize;
}
.container button {
  background-color: #ffffff;
  border: none;
  padding: 15px 45px;
  border-radius: 5px;
  font-size: 18px;
  font-weight: 600;
  color: #f43543;
  cursor: pointer;
}

Javascript:

let quote = document.getElementById("quote");
let author = document.getElementById("author");
let btn = document.getElementById("btn");

const url = "https://api.quotable.io/random";

let getQuote = () => {
  fetch(url)
    .then((data) => data.json())
    .then((item) => {
      quote.innerText = item.content;
      author.innerText = item.author;
    });
};

window.addEventListener("load", getQuote);
btn.addEventListener("click", getQuote);

 

#html #css #javascript #api 

5 JavaScript API Project Ideas for Beginners

A New Flutter Package to Easily Generate A Circular Card

circular_card

A new Flutter package to easily generate a circular card

Getting Started

This project is a starting point for a Dart package, a library module containing code that can be shared easily across multiple Flutter or Dart projects.

For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference. 

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add circular_card

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  circular_card: ^0.0.6

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:circular_card/circular_card.dart'; 

Download Details:

Author: Jocelin-La-Roch

Source Code: https://github.com/Jocelin-La-Roch/circular_card

#flutter #circularized #card 

A New Flutter Package to Easily Generate A Circular Card

A Dart Package That Validates Credit Card Numbers

credit_card_validator | Credit Card Validator

A Dart package that validates credit card numbers, expiration dates, and security codes (CVV/CVC) of a credit card. It also determines the type of credit card as part of the validation process.

This package should be used to quickly validate credit card data inputs and provide feedback to the user in your application's UI. It includes support for "potentially valid" inputs so that you can appropriately display the results to the user as they type.

Important: This package does not verify the information with the user's bank or credit company. Please use a payment processing service like Stripe or Braintree for true verification and validation of the user's payment info.

Installing

Add dependency to pubspec.yaml

Get the current version in the 'Installing' tab on pub.dartlang.org

dependencies:
    credit_card_validator: *current-version*
  1. Import the package
import 'package:credit_card_validator/credit_card_validator.dart';

Usage

A basic example

import 'package:credit_card_validator/credit_card_validator.dart';

class CreditCardValidationBloc {
    CreditCardValidator _ccValidator = CreditCardValidator()

    bool validateCreditCardInfo(string ccNum, string expDate, string cvv, ...) {
        var ccNumResults = _ccValidator.validateCCNum(ccNum);
        var expDateResults = _ccValidator.validateExpDate(expDate);
        var cvvResults = _ccValidator.validateCVV(cvv, ccNumResults.ccType);
        ...

        if(ccNumResults.isPotentiallyValid) {
            # Call UI code that shows to the user their credit card number is invalid
            displayInvalidCardNumber();
        }
    }
}

Features

  • Supported cards:
    • Visa
    • Mastercard
    • American Express
    • Discover
    • Diners Club
    • JCB
    • Union Pay
    • Maestro
    • Mir
    • Elo
    • Hiper/Hipercard

Original Repo

This package is based off of Braintree's Credit Card Validator JS package

Related Repos

Author

Cholojuanito (Tanner Davis) - Creator and repo owner - Github Profile

Support

If you think this package is helpful, tell your friends, give it a star on GitHub, and a like on pub.dev

License

This project is licensed under the MIT License - see the LICENSE file for more details

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add credit_card_validator

With Flutter:

 $ flutter pub add credit_card_validator

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  credit_card_validator: ^2.0.1

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:credit_card_validator/credit_card_validator.dart'; 

example/README.md

Example usage 

Download Details:

Author: cholojuanito

Source Code: https://github.com/cholojuanito/credit_card_validator

#dart #validate #card 

A Dart Package That Validates Credit Card Numbers