1675613734
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.
We build this package because we wanted to:
Customize the angle
Customize the threshold (when the card should slide away)
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.
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,
),
),
);
}
}
Parameter | Default | Description | Required |
---|---|---|---|
cards | - | List of Widgets for the swiper | true |
controller | - | Trigger swipe | false |
padding | EdgeInsets.symmetric(horizontal: 20, vertical: 25) | Control swiper padding | false |
duration | 200 milliseconds | The duration that every animation should last | false |
maxAngle | 30 | Maximum angle the card reaches while swiping | false |
threshold | 50 | Threshold from which the card is swiped away | false |
scale | 0.9 | Scale of the card that is behind the front card | false |
isDisabled | false | Set to true if swiping should be disabled, has no impact when triggered from the outside | false |
onTapDisabled | - | Function that get triggered when the swiper is disabled | false |
onSwipe | - | Called with the new index and detected swipe direction when the user swiped | false |
onEnd | - | Called when there is no Widget left to be swiped away | false |
direction | right | Direction in which the card is swiped away when triggered from the outside | false |
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.
Method | Description |
---|---|
swipe | Changes the state of the controller to swipe and swipes the card in your selected direction. |
swipeLeft | Changes the state of the controller to swipe left and swipes the card to the left side. |
swipeRight | Changes the state of the controller to swipe right and swipes the card to the right side. |
swipeTop | Changes the state of the controller to swipe top and swipes the card to the top side. |
swipeBottom | Changes the state of the controller to swipe bottom and swipes the card to the |
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.
Now in your Dart code, you can use:
import 'package:flutter_card_swiper/flutter_card_swiper.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
1674023454
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.
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>
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:
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>
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:
In your index.html
file copy the code for the single card and paste it so there are a total of 6 cards.
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;
}
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:
Thanks for reading my article today. You can get the source code here.
Original article source at https://www.freecodecamp.org
#css #javascript
1673976000
CardsLayout is a lightweight Collection Layout.
You can use CocoaPods to install CardsLayout
by adding it to your Podfile
:
platform :ios, '9.0'
use_frameworks!
pod 'CardsLayout'
import CardsLayout
CardsCollectionViewLayout
file to your projectcollectionView
: collectionView.collectionViewLayout = CardsCollectionViewLayout()
collectionView.isPagingEnabled = true
collectionView.showsHorizontalScrollIndicator = false
Author: filletofish
Source Code: https://github.com/filletofish/CardsLayout
License: MIT license
1670079125
Power Apps cards are simple lightweight micro-apps which holds and display enterprise data along with workflow and logic functions.
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.
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.
To find the Cards go to https://make.powerapps.com/ on the left side navigation you can find a new feature available.
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.
You can add
And so on.
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.
Power Platform connectors can be connected within no time with all the safety and security enabled to the environment.
Not just static data but you can definitely build business logic using Power Fx which can be added to create
And those responses can be targeted and inserted into the UI Elements available in the cards.
You can send cards from the card designer to a Microsoft Teams chat or channel to share your cards with others.
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.
Yes definitely, please visit the Microsoft Learn docs to read more about it.
Original article source at:: https://www.c-sharpcorner.com/
1669894200
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.
Here is a sneak-peak of what we will be building in this tutorial:
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:
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.
<!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!
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:
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.
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:
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.
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.
.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/
1668353748
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
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
ListView(
children: <Widget>[
FlutterSlimyCard()
]
);
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),
))
],
),
);
}
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.
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
1668073800
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:
And here's a demo of the actual Swift project:
Note: you can pivot the cards in any direction, it all depends on where your finger is on the card.
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.
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
Author: Saoudrizwan
Source Code: https://github.com/saoudrizwan/CardSlider
1668061983
A marriage between the Shazam Discover UI and Tinder, built with UICollectionView in Swift.
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.
To install with CocoaPods, simply add the following line to your Podfile:
pod 'VerticalCardSwiper'
To install with Carthage, simply add the following line to your Podfile:
github "JoniVR/VerticalCardSwiper"
To try out VerticalCardSwiper
pod try VerticalCardSwiper
or open the project and run the Example.
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
}
}
/// 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]
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])
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).
}
}
Subclass the CardCell
to customize the cards.
class ExampleCardCell: CardCell {
}
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!
Author: JoniVR
Source Code: https://github.com/JoniVR/VerticalCardSwiper
License: MIT license
1666777380
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
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
help wanted
.bug
.feature
.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.
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 CardController
s. Each instance of a CardController
will be rendered as a single card. The loadCards
method does this by getting the view controller for each CardController
and adding them as child view controllers to the card cells. Here is an example:
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.
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.
By default, the margins of your CardsViewController
will match the theme's cardCellMargins
property. You can change the margins for all CardsViewController
s in your application by applying a new theme 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)
}
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
shadowColor: lightGray, shadowRadius: 10.0, shadowOpacity: 0.5
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
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
}
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
}
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 {
...
}
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
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")
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)
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()
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
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)
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)
CardPartSwitchView
Provides the capability to add a switch with configurable colors.
let switchComponent = CardPartSwitchView()
switchComponent.onTintColor = .blue
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)
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()
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)
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
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 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.
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 }
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 CardsViewController
s in your storyboard, the required init(coder:)
initializer gets called prior to AppDelegate
. In this case, you will need to apply the theme in this initializer of the first view controller in your storyboard to be initialized, and changes will take effect in all other view controllers. For example:
required init?(coder: NSCoder) {
YourCardPartTheme().apply()
super.init(coder: coder)
}
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
}
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
}
}
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
Author: intuit
Source Code: https://github.com/intuit/CardParts
License: View license
1666220700
Cards brings to Xcode the card views seen in the new iOS XI Appstore.
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)
use_frameworks!
pod 'Cards'
//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
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)
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)
See the Wiki, to learn in depth infos about Cards.
GO!
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).
Author: PaoloCuscela
Source Code: https://github.com/PaoloCuscela/Cards
License: MIT license
1666113120
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.
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 .
KolodaView requires ARC.
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.
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.
For a better effect, we added a few bounce animations and that was it! The prototype was ready for development.
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:
DraggableCardView
– a card that displays content.OverlayView
– a dynamic view that changes depending on where a user drags a card (to the left or to the right).KolodaView
– a view that controls loading and interactions between cards.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
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
}
}
}
}
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
.
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.
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.
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
}
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.
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.
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.
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.
The KolodaView follows the Apple convention for data-driven views by providing two protocol interfaces, KolodaViewDataSource and KolodaViewDelegate.
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
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.
Version 5.0.1
Version 5.0
Version 4.7
Version 4.6
Version 4.5
Version 4.4
isLoop
property via @brownsooVersion 4.3
Version 4.0
Version 3.1
Version 3.0
Version 2.0
Version 1.1
Version 1.0
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!
Author: Yalantis
Source Code: https://github.com/Yalantis/Koloda
License: MIT license
1666104960
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:
A demo project is included in the BulletinBoard
workspace. It demonstrates how to:
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.
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")
To install BulletinBoard using CocoaPods, add this line to your Podfile
:
pod 'BulletinBoard'
To install BulletinBoard using Carthage, add this line to your Cartfile
:
github "alexaubry/BulletinBoard"
BulletinBoard
, check out our Getting Started guide.Thank you for your interest in the project! Contributions are welcome and appreciated.
Make sure to read these guides before getting started:
Author: Alexisakers
Source Code: https://github.com/alexisakers/BulletinBoard
License: MIT license
1664005817
This tutorial shows 5 JavaScript API project ideas for beginners with source code:
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.
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’.
<!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>
* {
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;
}
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);
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.
<!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>😂</span>
<p id="joke"></p>
<button id="btn">Get Random Joke</button>
</div>
<!-- Script -->
<script src="script.js"></script>
</body>
</html>
*{
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;
}
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();
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.
<!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>
* {
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;
}
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();
}
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.
<!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>
* {
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;
}
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>`;
}
});
});
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.
<!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>
* {
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;
}
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
1661621182
circular_card
A new Flutter package to easily generate a circular card
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.
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.
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
1661618074
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*
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
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
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.
Now in your Dart code, you can use:
import 'package:credit_card_validator/credit_card_validator.dart';
Example usage
Download Details:
Author: cholojuanito
Source Code: https://github.com/cholojuanito/credit_card_validator