Skip to content

The most user-friendly carousel library, supporting configuration through spells.

License

Notifications You must be signed in to change notification settings

YuLeiFuYun/CardCarousel

Repository files navigation

CardCarousel

Swift Platform Release SPM License

English | 简体中文

CardCarousel is a powerful yet easy-to-use carousel component that you can even configure with spells.

Features

  • Supports method chaining

  • Supports setting page dimensions in a manner similar to NSCollectionLayoutSize

  • Supports multiple loop modes

  • Supports setting page alignment when scrolling stops

  • Supports setting scroll direction

  • Supports setting scroll animation effects for automatic scrolling

  • Supports setting page transition effects

  • Supports setting pagination threshold

  • Supports setting the deceleration rate of pages during sliding

  • Supports SwiftUI

  • Supports configuration through spells

  • And more...

Installation

Swift Package Manager

In Xcode, select File > Add Package Dependencies... , paste https://github.com/YuLeiFuYun/CardCarousel.git .

CocoaPods

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

target '<Your Target Name>' do
    pod 'CardCarousel', '~> 2.0'
end

Requirements

  • iOS 12+
  • Swift 5.9+
  • Xcode 15+

Usage

In UIKit

  • Simple use
import CardCarousel

let cardCarousel = CardCarousel(frame: ...).add(to: view)

// For local images, you can directly assign an array of UIImage objects.
cardCarousel.items = [UIImage]

// Before using remote images in CardCarousel, set up the ImageLoadingManager with appropriate loading, prefetching, and cancellation behaviors. This is typically done using a third-party library like SDWebImage or Kingfisher.
// Here’s an example of how you might configure the ImageLoadingManager:
import Kingfisher

ImageLoadingManager.shared.configure { url, imageView, placeholder, completion in
    imageView.kf.setImage(with: url, placeholder: placeholder) { _ in
        completion()
    }
} prefetch: { urls in
    ImagePrefetcher(urls: urls).start()
} cancel: { urls in
    ImagePrefetcher(urls: urls).stop()
}

// After configuring ImageLoadingManager, you can set the items of CardCarousel to be an array of URL strings:
cardCarousel.items = [
    "https://example.com/image1.jpg",
    "https://example.com/image2.jpg",
    "https://example.com/image3.jpg"
]

The IT Crowd

  • Custom cell
CardCarousel(items: items) { (cell: CustomCell, index: Int, itemIdentifier: Item) in
    cell.imageView.kf.setImage(with: url)
    cell.indexLabel.backgroundColor = itemIdentifier.color
    cell.indexLabel.text = itemIdentifier.index
}
.cardLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(0.7))
.cardTransformMode(.liner(minimumAlpha: 0.3))
.cardCornerRadius(10)
.add(to: view, layoutConstraints: { cardCarouselView, superView in
    NSLayoutConstraint.activate([
        cardCarouselView.leadingAnchor.constraint(equalTo: superView.leadingAnchor),
        cardCarouselView.trailingAnchor.constraint(equalTo: superView.trailingAnchor),
        cardCarouselView.topAnchor.constraint(equalTo: superView.topAnchor, constant: 100),
        cardCarouselView.heightAnchor.constraint(equalToConstant: 200)
    ])
})

罗小黑战记

  • SwiftUI View
CardCarousel(items: items) { index, itemIdentifier in
    HStack {
        Text(itemIdentifier)
            .font(.system(size: 18))
        Spacer()
    }
}
.scrollDirection(.topToBottom)
.add(to: view)

道与碳基猴子饲养守则

  • Custom page control
public protocol CardCarouselPageControlType: UIView {
    var numberOfPages: Int { get set }
    
    var currentPage: Int { get }
}

public protocol CardCarouselNormalPageControlType: CardCarouselPageControlType {
    var currentPage: Int { get set }
}

public protocol CardCarouselContinousPageControlType: CardCarouselPageControlType {
    var progress: Double { get set }
}

extension UIPageControl: CardCarouselNormalPageControlType { }

Example:

extension CustomPageControl: CardCarouselContinousPageControlType {
    ...
}

CardCarousel(itemsPublisher: $items) { index, itemIdentifier in
    Text(itemIdentifier.text)
        .font(.title)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(itemIdentifier.color)
}
.cardLayoutSize(widthDimension: .fractionalWidth(0.9), heightDimension: .absolute(200))
.minimumLineSpacing(20)
.scrollStopAlignment(.head(offset: 10))
.pageControl(makePageControl: {
    let pageControl = CustomPageControl()
    pageControl.currentPageTintColor = .white
    pageControl.tintColor = .green
    pageControl.radius = 4
    pageControl.padding = 15
    return pageControl
}, position: .centerXBottom(offset: CGPoint(x: 0, y: -10)))
.add(to: view)

The_Stormlight_Archive

In SwiftUI

struct Content: View {
    @State var items = [
        "飞光飞光 劝尔一杯酒",
        "吾不识青天高 黄地厚",
        "惟见月寒日暖 来煎人寿",
        "食熊则肥 食蛙则瘦",
    ]
    
    
    var body: some View {
        CardCarouselView($items, content: { index, itemIdentifier in
            if index.isMultiple(of: 2) {
                ZStack {
                    Color.blue
                    Text(itemIdentifier)
                }
            } else {
                Text(itemIdentifier)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Color.yellow)
                    .clipShape(Capsule())
            }
        })
      	.scrollMode(.automatic(timeInterval: 3))
        .cardTransformMode(.coverflow(minimumAlpha: 0.3))
        .cardLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(0.7))
        .minimumLineSpacing(-20)
        .cardCornerRadius(10)
        // The larger the value, the further the slide after the user releases the drag, default is 0.9924.
        .decelerationRate(0.999)
        .onCardSelected({ index in
            print(index)
        })
        .onCardChanged({ index in
            print(index)
        })
        .frame(height: 200)
    }
}

昼苦短

Spell

For spells in the styles of 高级动物 and 催妆曲, please separate function names, parameter names, and arguments with full-width commas. Separate multiple spells (i.e., multiple function calls) with spaces.

  • 动物协鸣
CardCarousel(咒语: "汪咕呦汪叽嗡呜汪叽 喵呜 呜啾 嘎啾", 施法材料: items, 作用域: CGRect(x: 0, y: 100, width: 393, height: 200))
    .法术目标(view)

// The effect is equivalent to
CardCarousel(frame: CGRect(x: 0, y: 100, width: 393, height: 200), items: items)
    .cardLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(0.7))
    .cardTransformMode(.liner)
    .scrollDirection(.rightToLeft)
    .loopMode(.rollback)
    .add(to: view)
  • 高级动物
// Same effect as above
CardCarousel(咒语: "矛盾,自私,好色,爱喜,无聊,善良,爱喜 贪婪,真诚 善变,暗淡 无奈,埋怨", 施法材料: items, 作用域: CGRect(x: 0, y: 100, width: 393, height: 200))
    .法术目标(view)
  • 催妆曲
// Same effect as above
CardCarousel(咒语: "醒呀,画眉在杏枝上歌,画眉人不起是因何,黛棕,远峰尖滴着新黛,正好蘸来描画双蛾,黛棕 晨鸡声呖呖在相催,日神也捧着金镜 画眉在杏枝上歌,她对着如镜的池塘 远峰尖滴着新黛,春莺儿衔了额黄归", 施法材料: items, 作用域: CGRect(x: 0, y: 100, width: 393, height: 200))
    .法术目标(view)
  • 大威天龙
let 白素贞 = view
CardCarousel(咒语: "大威天龙", 施法材料: items)
    .法术目标(白素贞)

// he effect is equivalent to
CardCarousel(items: items)
    .minimumLineSpacing(10)
    .pageControl(makePageControl: { UIPageControl() }, position: .centerXBottom)
    .add(to: view)

大威天龙

All Method

public protocol CardCarouselInterface {
    /// Card layout size, filling the entirety of the super view by default.
    ///
    /// 动物协鸣:汪;0-9:["汪", "啾", "喵", "咩", "哞", "呱", "嘎", "叽", "吱", "嘶"]
    /// widthDimension:咕;heightDimension:嗡
    /// fractionalWidth:呦;fractionalHeight:呜;absolute:嗷;inset:嘤
    ///
    /// 高级动物:矛盾;0-9:["爱", "贪", "嗔", "痴", "恨", "苦", "忧", "喜", "怨", "怒"]
    /// widthDimension:自私;heightDimension:无聊
    /// fractionalWidth:好色;fractionalHeight:善良;absolute:博爱;inset:诡辩
    ///
    /// 催妆曲:醒呀;0-9:["黛", "墨", "碧", "朱", "紫", "黄", "蓝", "棕", "灰", "白"]
    /// widthDimension:画眉在杏枝上歌;heightDimension:远峰尖滴着新黛
    /// fractionalWidth:画眉人不起是因何;fractionalHeight:正好蘸来描画双蛾;absolute:春莺儿衔了额黄归;inset:起呀
    func cardLayoutSize(widthDimension: CardLayoutDimension, heightDimension: CardLayoutDimension) -> Self
    
    /// Minimum card spacing, default 0.
    ///
    /// 动物协鸣:啾;0-9:["汪", "啾", "喵", "咩", "哞", "呱", "嘎", "叽", "吱", "嘶"]
    ///
    /// 高级动物:虚伪;0-9:["爱", "贪", "嗔", "痴", "恨", "苦", "忧", "喜", "怨", "怒"]
    ///
    /// 催妆曲:从睡乡醒回;0-9:["黛", "墨", "碧", "朱", "紫", "黄", "蓝", "棕", "灰", "白"]
    func minimumLineSpacing(_ spacing: CGFloat) -> Self
    
    /// Card transform mode, default .none.
    ///
    /// 动物协鸣:喵;liner:呜;coverflow:嗷
    ///
    /// 高级动物:贪婪;liner:真诚;coverflow:金钱
    ///
    /// 催妆曲:晨鸡声呖呖在相催;liner:日神也捧着金镜;coverflow:等候你起来梳早妆
    func cardTransformMode(_ mode: CardTransformMode) -> Self
    
    /// By default, the current card always remains at the forefront. invoking this method may result in it being obscured by other cards.
    ///
    /// 动物协鸣:咩
    ///
    /// 动物协鸣:咩
    ///
    /// 高级动物:欺骗
    ///
    /// 催妆曲:看呀
    func disableCurrentCardAlwaysOnTop() -> Self
    
    /// The margin on both sides of the sliding direction takes effect only when loopMode is set to non-circular. The default value is 0.
    ///
    /// 动物协鸣:哞;0-9:["汪", "啾", "喵", "咩", "哞", "呱", "嘎", "叽", "吱", "嘶"]
    ///
    /// 高级动物:幻想;0-9:["爱", "贪", "嗔", "痴", "恨", "苦", "忧", "喜", "怨", "怒"]
    ///
    /// 催妆曲:霞织的五彩衣裳;0-9:["黛", "墨", "碧", "朱", "紫", "黄", "蓝", "棕", "灰", "白"]
    func sideMargin(_ margin: CGFloat) -> Self
    
    /// Card alignment when scroll stops, default center alignment.
    ///
    /// 动物协鸣:呱;0-9:["汪", "啾", "喵", "咩", "哞", "呱", "嘎", "叽", "吱", "嘶"]
    /// center:咕;head:嗡
    ///
    /// 高级动物:疑惑;0-9:["爱", "贪", "嗔", "痴", "恨", "苦", "忧", "喜", "怨", "怒"]
    /// center:地狱;head:天堂
    ///
    /// 催妆曲:趁草际珠垂;0-9:["黛", "墨", "碧", "朱", "紫", "黄", "蓝", "棕", "灰", "白"]
    /// center:画眉在杏枝上歌;head:画眉人不起是因何
    func scrollStopAlignment(_ alignment: CardScrollStopAlignment) -> Self
    
    /// Alignment for single card, default center alignment.
    ///
    /// 动物协鸣:呦;0-9:["汪", "啾", "喵", "咩", "哞", "呱", "嘎", "叽", "吱", "嘶"]
    /// center:咕;head:嗡
    ///
    /// 高级动物:简单;0-9:["爱", "贪", "嗔", "痴", "恨", "苦", "忧", "喜", "怨", "怒"]
    /// center:地狱;head:天堂
    ///
    /// 催妆曲:春莺儿衔了额黄归;0-9:["黛", "墨", "碧", "朱", "紫", "黄", "蓝", "棕", "灰", "白"]
    /// center:画眉在杏枝上歌;head:画眉人不起是因何
    func singleCardAlignment(_ alignment: CardScrollStopAlignment) -> Self
    
    /// scroll eirection, default .leftToRight.
    ///
    /// 动物协鸣:呜
    /// leftToRight:汪;rightToLeft:啾;topToBottom:喵;bottomToTop:咩
    ///
    /// 高级动物:善变
    /// leftToRight:辉煌;rightToLeft:暗淡;topToBottom:得意;bottomToTop:伤感
    ///
    /// 催妆曲:画眉在杏枝上歌
    /// leftToRight:杨柳的丝发飘扬;rightToLeft:她对着如镜的池塘;topToBottom:百花是薰沐已毕;bottomToTop:她们身上喷出芬芳
    func scrollDirection(_ direction: CardScrollDirection) -> Self
    
    /// Scrolling animation effect for automatic scrolling, default .system.
    func autoScrollAnimation(_ animationOptions: CardScrollAnimationOptions) -> Self
    
    /// Automatic scrolling or manual scrolling, default .automatic(timeInterval: 3).
    ///
    /// 动物协鸣:嗡;0-9:["汪", "啾", "喵", "咩", "哞", "呱", "嘎", "叽", "吱", "嘶"]
    /// automatic:咕;manual:嗡
    ///
    /// 高级动物:好强;0-9:["爱", "贪", "嗔", "痴", "恨", "苦", "忧", "喜", "怨", "怒"]
    /// automatic:怀恨;manual:报复
    ///
    /// 催妆曲:画眉人不起是因何;0-9:["黛", "墨", "碧", "朱", "紫", "黄", "蓝", "棕", "灰", "白"]
    /// automatic:远峰尖滴着新黛;manual:正好蘸来描画双蛾
    ///
    /// 注意:用咒语调用时时间间隔只能设为整数!
    func scrollMode(_ mode: CardScrollMode) -> Self
    
    /// loop mode, default .circular.
    ///
    /// 动物协鸣:嘎
    /// circular:汪;rollback:啾;single:喵
    ///
    /// 高级动物:无奈
    /// circular:争夺;rollback:埋怨;single:冒险
    ///
    /// 催妆曲:远峰尖滴着新黛
    /// circular:趁草际珠垂;rollback:春莺儿衔了额黄归;single:赶快拿妆梳理好
    func loopMode(_ mode: CardLoopMode) -> Self
    
    /// Card paging threshold, half the default card width.
    ///
    /// 动物协鸣:叽;0-9:["汪", "啾", "喵", "咩", "哞", "呱", "嘎", "叽", "吱", "嘶"]
    /// fractional:咕;absolute:嗡
    ///
    /// 高级动物:孤独;0-9:["爱", "贪", "嗔", "痴", "恨", "苦", "忧", "喜", "怨", "怒"]
    /// fractional:伟大;absolute:渺小
    ///
    /// 催妆曲:正好蘸来描画双蛾;0-9:["黛", "墨", "碧", "朱", "紫", "黄", "蓝", "棕", "灰", "白"]
    /// fractional:画眉在杏枝上歌;absolute:画眉人不起是因何
    func pagingThreshold(_ pagingThreshold: CardPagingThreshold) -> Self
    
    /// A floating-point value determines the deceleration rate after the user lifts their finger; the larger the value, the farther the slide after lifting the hand. This setting is ineffective when loopMode is set to rollback. The default value is 0.9924.
    ///
    /// 动物协鸣:吱;0-9:["汪", "啾", "喵", "咩", "哞", "呱", "嘎", "叽", "吱", "嘶"]
    ///
    /// 高级动物:脆弱;0-9:["爱", "贪", "嗔", "痴", "恨", "苦", "忧", "喜", "怨", "怒"]
    ///
    /// 催妆曲:杨柳的丝发飘扬;["黛", "墨", "碧", "朱", "紫", "黄", "蓝", "棕", "灰", "白"]
    func decelerationRate(_ value: CGFloat) -> Self
    
    /// Disable user swipe.
    ///
    /// 动物协鸣:嘶
    ///
    /// 高级动物:忍让
    ///
    /// 催妆曲:她对着如镜的池塘
    func disableUserSwipe() -> Self
    
    /// Setting backgroundView.
    func backgroundView(_ view: UIView) -> Self
    
    /// Card rounded corner Settings.
    ///
    /// 动物协鸣:嗷,不支持 maskedCorners 设置
    ///
    /// 高级动物:复杂,不支持 maskedCorners 设置
    ///
    /// 催妆曲:她们身上喷出芬芳,不支持 maskedCorners 设置
    func cardCornerRadius(_ value: CGFloat, maskedCorners: CACornerMask) -> Self
    
    /// Disable bounce effect.
    func disableBounce() -> Self
    
    /// Setting the card border width and color.
    func border(width: CGFloat, color: CGColor?) -> Self
    
    /// Setting the placeholder image when using the default card.
    func placeholder(_ image: UIImage) -> Self
    
    /// Settings Pertaining to Shadow Configuration.
    func shadow(offset: CGSize, color: CGColor?, radius: CGFloat, opacity: Float, path: CGPath?) -> Self
    
    /// Setting page control.
    func pageControl(makePageControl: @escaping () -> CardCarouselPageControlType, position: PageControlPosition) -> Self
    
    /// Called when the card is clicked.
    func onCardSelected(_ handler: @escaping (_ index: Int) -> Void) -> Self
    
    /// Called when the card scrolls.
    func onScroll(_ handler: @escaping (_ offset: CGPoint, _ progress: CGFloat) -> Void) -> Self
    
    /// Called when the card is switched.
    func onCardChanged(_ handler: @escaping (_ index: Int) -> Void) -> Self
    
    /// Called when the card will begin dragging.
    func onWillBeginDragging(_ handler: @escaping (_ index: Int) -> Void) -> Self
    
    /// Called when the card will end dragging.
    func onWillEndDragging(_ handler: @escaping (_ index: Int) -> Void) -> Self
    
    /// Prefetch items.
    func onPrefetchItems(_ handler: @escaping (_ indexs: [IndexPath]) -> Void) -> Self
    
    /// Cancel items.
    func onCancelPrefetchItems(_ handler: @escaping (_ indexs: [IndexPath]) -> Void) -> Self
}

Acknowledgments

License

CardCarousel is released under the MIT license. See LICENSE for details.

About

The most user-friendly carousel library, supporting configuration through spells.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published