├── .swift-version ├── KSImageCarouselExample ├── Assets.xcassets │ ├── Contents.json │ ├── black.imageset │ │ ├── Image.png │ │ └── Contents.json │ ├── blue.imageset │ │ ├── Image.png │ │ └── Contents.json │ ├── green.imageset │ │ ├── Image.png │ │ └── Contents.json │ ├── red.imageset │ │ ├── Image.png │ │ └── Contents.json │ ├── purple.imageset │ │ ├── Image.png │ │ └── Contents.json │ ├── yellow.imageset │ │ ├── Image.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── placeholder.jpg ├── Sample-Photos │ ├── Image-0.jpg │ ├── Image-1.jpg │ ├── Image-2.jpg │ ├── Image-3.jpg │ ├── Image-4.jpg │ └── Image-5.jpg ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── InfinityDemoViewController.swift ├── FiniteDemoViewController.swift └── AppDelegate.swift ├── KSImageCarousel ├── KSImageCarouselTests │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── red.imageset │ │ │ ├── Image.png │ │ │ └── Contents.json │ │ ├── black.imageset │ │ │ ├── Image.png │ │ │ └── Contents.json │ │ ├── blue.imageset │ │ │ ├── Image.png │ │ │ └── Contents.json │ │ ├── gray.imageset │ │ │ ├── Image.png │ │ │ └── Contents.json │ │ ├── green.imageset │ │ │ ├── Image.png │ │ │ └── Contents.json │ │ ├── white.imageset │ │ │ ├── Image.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── KSICFakeScrollerViewController.swift │ └── KSImageCarouselTests.swift ├── KSImageCarousel │ ├── KSImageCarousel.h │ ├── Info.plist │ └── Sources │ │ ├── KSICError.swift │ │ ├── Extensions.swift │ │ ├── KSICImageView.swift │ │ ├── KSImageCarouselDisplayable.swift │ │ ├── KSICImageCache.swift │ │ ├── KSICScrollerViewController.swift │ │ └── KSICCoordinator.swift └── KSImageCarousel.xcodeproj │ ├── xcshareddata │ └── xcschemes │ │ └── KSImageCarousel.xcscheme │ └── project.pbxproj ├── KSImageCarouselExample.xcworkspace └── contents.xcworkspacedata ├── KSImageCarousel.podspec ├── LICENSE ├── .gitignore ├── README.md └── KSImageCarouselExample.xcodeproj └── project.pbxproj /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /KSImageCarouselExample/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/placeholder.jpg -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /KSImageCarouselExample/Sample-Photos/Image-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Sample-Photos/Image-0.jpg -------------------------------------------------------------------------------- /KSImageCarouselExample/Sample-Photos/Image-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Sample-Photos/Image-1.jpg -------------------------------------------------------------------------------- /KSImageCarouselExample/Sample-Photos/Image-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Sample-Photos/Image-2.jpg -------------------------------------------------------------------------------- /KSImageCarouselExample/Sample-Photos/Image-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Sample-Photos/Image-3.jpg -------------------------------------------------------------------------------- /KSImageCarouselExample/Sample-Photos/Image-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Sample-Photos/Image-4.jpg -------------------------------------------------------------------------------- /KSImageCarouselExample/Sample-Photos/Image-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Sample-Photos/Image-5.jpg -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/black.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Assets.xcassets/black.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/blue.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Assets.xcassets/blue.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/green.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Assets.xcassets/green.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/red.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Assets.xcassets/red.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/purple.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Assets.xcassets/purple.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/yellow.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarouselExample/Assets.xcassets/yellow.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/red.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarousel/KSImageCarouselTests/Assets.xcassets/red.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/black.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarousel/KSImageCarouselTests/Assets.xcassets/black.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/blue.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarousel/KSImageCarouselTests/Assets.xcassets/blue.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/gray.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarousel/KSImageCarouselTests/Assets.xcassets/gray.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/green.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarousel/KSImageCarouselTests/Assets.xcassets/green.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/white.imageset/Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeeKahSeng/KSImageCarousel/HEAD/KSImageCarousel/KSImageCarouselTests/Assets.xcassets/white.imageset/Image.png -------------------------------------------------------------------------------- /KSImageCarouselExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/red.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/black.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/green.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/purple.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/yellow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/blue.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/gray.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/red.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/black.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/green.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/white.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Image.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel/KSImageCarousel.h: -------------------------------------------------------------------------------- 1 | // 2 | // KSImageCarousel.h 3 | // KSImageCarousel 4 | // 5 | // Created by Lee Kah Seng on 24/05/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for KSImageCarousel. 12 | FOUNDATION_EXPORT double KSImageCarouselVersionNumber; 13 | 14 | //! Project version string for KSImageCarousel. 15 | FOUNDATION_EXPORT const unsigned char KSImageCarouselVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.5 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /KSImageCarouselExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /KSImageCarousel.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | 4 | s.name = "KSImageCarousel" 5 | s.version = "1.0.5" 6 | s.summary = "Lightweight image carousel which can easily adapt to different type of image source." 7 | 8 | s.description = "KSImageCarousel support both finite and infinite scrolling mode. Just init KSImageCarousel with your desire UIImages or image URLs and it will do all the work for you. KSImageCarousel can also easily adapt to different type of image source other than UIImage and image URL" 9 | 10 | s.homepage = "https://github.com/LeeKahSeng/KSImageCarousel" 11 | s.license = { :type => 'MIT', :file => 'LICENSE' } 12 | s.author = { "Lee Kah Seng" => "kahseng.lee123@gmail.com" } 13 | 14 | s.swift_version = '4.2' 15 | s.platform = :ios, "10.3" 16 | s.source = { 17 | :git => 'https://github.com/LeeKahSeng/KSImageCarousel.git', 18 | :tag => s.version.to_s, 19 | :branch => 'master' 20 | } 21 | 22 | s.source_files = 'KSImageCarousel/KSImageCarousel/Sources/*.swift' 23 | 24 | end 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Lee Kah Seng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /KSImageCarouselExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel/Sources/KSICError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KSICError.swift 3 | // KSImageCarousel 4 | // 5 | // Created by Lee Kah Seng on 28/05/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. (https://github.com/LeeKahSeng/KSImageCarousel) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | // 26 | 27 | import Foundation 28 | 29 | enum CoordinatorError: Error { 30 | case pageOutOfRange 31 | case emptyModel 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /KSImageCarouselExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /KSImageCarouselExample/InfinityDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfinityDemoViewController.swift 3 | // KSImageCarouselExample 4 | // 5 | // Created by Lee Kah Seng on 23/07/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import KSImageCarousel 11 | 12 | class InfinityDemoViewController: UIViewController { 13 | 14 | @IBOutlet weak var container: UIView! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | // Setup carousel 20 | setupCarousel() 21 | } 22 | 23 | override func didReceiveMemoryWarning() { 24 | super.didReceiveMemoryWarning() 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | func setupCarousel() { 29 | 30 | // Create model for carousel 31 | let model = [ 32 | UIImage(named: "Image-0.jpg")!, 33 | UIImage(named: "Image-1.jpg")!, 34 | UIImage(named: "Image-2.jpg")!, 35 | UIImage(named: "Image-3.jpg")!, 36 | UIImage(named: "Image-4.jpg")!, 37 | UIImage(named: "Image-5.jpg")!, 38 | ] 39 | 40 | // Use coordinator to show the carousel 41 | if let coordinator = try? KSICInfiniteCoordinator(with: model, initialPage: 0) { 42 | coordinator.showCarousel(inside: container, of: self) 43 | coordinator.delegate = self 44 | coordinator.startAutoScroll(withDirection: .left, interval: 2) 45 | } 46 | } 47 | } 48 | 49 | extension InfinityDemoViewController: KSICCoordinatorDelegate { 50 | 51 | func carouselDidTappedImage(at index: Int, coordinator: KSICCoordinator) { 52 | let alert = UIAlertController(title: "KSImageCarousel", message: "You tapped image at index \(index)", preferredStyle: .alert) 53 | let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil) 54 | alert.addAction(okAction) 55 | present(alert, animated: true, completion: nil) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel/Sources/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // KSImageCarousel 4 | // 5 | // Created by Lee Kah Seng on 01/06/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. (https://github.com/LeeKahSeng/KSImageCarousel) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | // 26 | // 27 | 28 | import Foundation 29 | 30 | extension UIView { 31 | func addSameSizeSubview(_ subview: UIView) { 32 | self.addSubview(subview) 33 | subview.translatesAutoresizingMaskIntoConstraints = false 34 | let widthContraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(0)-[subview]-(0)-|", options: .alignAllCenterX, metrics: nil, views: ["subview": subview]) 35 | let heightContraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|-(0)-[subview]-(0)-|", options: .alignAllCenterX, metrics: nil, views: ["subview": subview]) 36 | self.addConstraints(widthContraints) 37 | self.addConstraints(heightContraints) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /KSImageCarouselExample/FiniteDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FiniteDemoViewController.swift 3 | // KSImageCarouselExample 4 | // 5 | // Created by Lee Kah Seng on 23/07/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import KSImageCarousel 11 | 12 | class FiniteDemoViewController: UIViewController { 13 | 14 | @IBOutlet weak var container: UIView! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | // Setup carousel 20 | setupCarousel() 21 | } 22 | 23 | override func didReceiveMemoryWarning() { 24 | super.didReceiveMemoryWarning() 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | func setupCarousel() { 29 | 30 | // Create model for carousel 31 | let model = [ 32 | URL(string: "https://via.placeholder.com/375x281/403ADA/FFFFFF?text=Image-0")!, 33 | URL(string: "https://via.placeholder.com/375x281/5D0F25/FFFFFF?text=Image-1")!, 34 | URL(string: "https://via.placeholder.com/375x281/83B002/FFFFFF?text=Image-2")!, 35 | URL(string: "https://via.placeholder.com/375x281/1B485D/FFFFFF?text=Image-3")!, 36 | URL(string: "https://via.placeholder.com/375x281/E6581C/FFFFFF?text=Image-4")!, 37 | ] 38 | 39 | // Use coordinator to show the carousel 40 | if let coordinator = try? KSICFiniteCoordinator(with: model, initialPage: 0) { 41 | coordinator.activityIndicatorStyle = .white 42 | coordinator.showCarousel(inside: container, of: self) 43 | coordinator.delegate = self 44 | } 45 | } 46 | 47 | } 48 | 49 | extension FiniteDemoViewController: KSICCoordinatorDelegate { 50 | 51 | func carouselDidTappedImage(at index: Int, coordinator: KSICCoordinator) { 52 | let alert = UIAlertController(title: "KSImageCarousel", message: "You tapped image at index \(index)", preferredStyle: .alert) 53 | let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil) 54 | alert.addAction(okAction) 55 | present(alert, animated: true, completion: nil) 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/KSICFakeScrollerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KSICFakeScrollerViewController.swift 3 | // KSImageCarousel 4 | // 5 | // Created by Lee Kah Seng on 23/07/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import KSImageCarousel 11 | 12 | class KSICFakeScrollerViewController: KSICScrollerViewController { 13 | 14 | var scrollToCenterSubviewCalled: Bool 15 | var scrollToFirstSubviewCalled: Bool 16 | var scrollToLastSubviewCalled: Bool 17 | 18 | init(withViewModel vm: [KSImageCarouselDisplayable]) { 19 | 20 | scrollToCenterSubviewCalled = false 21 | scrollToFirstSubviewCalled = false 22 | scrollToLastSubviewCalled = false 23 | 24 | super.init(withViewModel: vm, placeholderImage: nil, delegate: KSICFakeScrollerViewControllerDelegate()) 25 | 26 | } 27 | 28 | required init?(coder aDecoder: NSCoder) { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | override func scrollToCenterSubview(_ animated: Bool) { 33 | scrollToCenterSubviewCalled = true 34 | } 35 | 36 | override func scrollToFirstSubview(_ animated: Bool) { 37 | scrollToFirstSubviewCalled = true 38 | } 39 | 40 | override func scrollToLastSubview(_ animated: Bool) { 41 | scrollToLastSubviewCalled = true 42 | } 43 | 44 | func resetStatus() { 45 | scrollToCenterSubviewCalled = false 46 | scrollToFirstSubviewCalled = false 47 | scrollToLastSubviewCalled = false 48 | } 49 | } 50 | 51 | class KSICFakeScrollerViewControllerDelegate: KSICScrollerViewControllerDelegate { 52 | func scrollerViewControllerDidGotoNextPage(_ viewController: KSICScrollerViewController) { } 53 | func scrollerViewControllerDidGotoPreviousPage(_ viewController: KSICScrollerViewController) { } 54 | func scrollerViewControllerDidFinishLayoutSubviews(_ viewController: KSICScrollerViewController) { } 55 | func scrollerViewControllerDidTappedImageView(at index: Int, viewController: KSICScrollerViewController) { } 56 | func scrollerViewControllerShouldShowActivityIndicator() -> Bool { return true } 57 | func scrollerViewControllerShowActivityIndicatorStyle() -> UIActivityIndicatorViewStyle { return .gray } 58 | } 59 | -------------------------------------------------------------------------------- /KSImageCarouselExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // KSImageCarouselExample 4 | // 5 | // Created by Lee Kah Seng on 24/05/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import KSImageCarousel 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 19 | // Override point for customization after application launch. 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(_ application: UIApplication) { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel/Sources/KSICImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KSICImageView.swift 3 | // KSImageCarousel 4 | // 5 | // Created by Lee Kah Seng on 22/07/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. (https://github.com/LeeKahSeng/KSImageCarousel) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | // 26 | // 27 | 28 | import UIKit 29 | 30 | 31 | /// UIImageView subclass that have UIActivityIndicatorView as subview. This allow activity indicator to be shown at the center of the imageView while waiting for image to be downloaded 32 | class KSICImageView: UIImageView { 33 | 34 | lazy var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView() 35 | 36 | required init(coder aDecoder: NSCoder) { 37 | super.init(coder: aDecoder)! 38 | 39 | commonInit() 40 | } 41 | 42 | override init(image: UIImage?) { 43 | super.init(image: image) 44 | 45 | commonInit() 46 | } 47 | 48 | private func commonInit() { 49 | activityIndicator.style = .gray 50 | activityIndicator.hidesWhenStopped = true 51 | activityIndicator.startAnimating() 52 | addSubview(activityIndicator) 53 | 54 | // Make activity indicator center 55 | activityIndicator.translatesAutoresizingMaskIntoConstraints = false 56 | 57 | let centerX = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: activityIndicator, attribute: .centerX, multiplier: 1, constant:0) 58 | let centerY = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: activityIndicator, attribute: .centerY, multiplier: 1, constant:0) 59 | addConstraints([centerX , centerY]) 60 | } 61 | 62 | /// Start animating activity indicator 63 | func startLoading() { 64 | activityIndicator.startAnimating() 65 | } 66 | 67 | /// Stop animating activity indicator 68 | func stopLoading() { 69 | activityIndicator.stopAnimating() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel/Sources/KSImageCarouselDisplayable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KSImageCarouselDisplayable.swift 3 | // KSImageCarousel 4 | // 5 | // Created by Lee Kah Seng on 30/05/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. (https://github.com/LeeKahSeng/KSImageCarousel) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | // 26 | 27 | import Foundation 28 | import UIKit 29 | 30 | /// Conform to this protocol to make your custom object able to show the images on KSImageCarousel 31 | public protocol KSImageCarouselDisplayable { 32 | 33 | 34 | /// Create / generate / download image that needs to be show on carousell 35 | /// 36 | /// - Parameter completion: Completion handler when image is ready. This closure should accept the image and the KSImageCarouselDisplayable that provide the image 37 | func createCarouselImage(completion: @escaping (UIImage?, KSImageCarouselDisplayable) -> Void) 38 | 39 | 40 | /// Act as equatable checker to compare 2 KSImageCarouselDisplayable. This is needed to make sure the image will show in the correct image view of the carousel when createCarouselImage completion handler trigger . However if the KSImageCarouselDisplayable able to create the image instantly, then just return true in this function 41 | /// 42 | /// - Parameter displayable: KSImageCarouselDisplayable to compare 43 | /// - Returns: true -> both is the same; false -> both is not the same 44 | func isEqual(to displayable: KSImageCarouselDisplayable) -> Bool 45 | } 46 | 47 | extension UIImage: KSImageCarouselDisplayable { 48 | 49 | public func createCarouselImage(completion: @escaping (UIImage?, KSImageCarouselDisplayable) -> Void) { 50 | completion(self, self) 51 | } 52 | 53 | public func isEqual(to displayable: KSImageCarouselDisplayable) -> Bool { 54 | return true 55 | } 56 | } 57 | 58 | extension URL: KSImageCarouselDisplayable { 59 | 60 | public func createCarouselImage(completion: @escaping (UIImage?, KSImageCarouselDisplayable) -> ()) { 61 | KSICImageCache.shared.fetchImage(from: self) { (image) in 62 | completion(image, self) 63 | } 64 | } 65 | 66 | public func isEqual(to displayable: KSImageCarouselDisplayable) -> Bool { 67 | if let url = displayable as? URL { 68 | return url == self 69 | } else { 70 | return false 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel/Sources/KSICImageCache.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KSICImageCache.swift 3 | // KSImageCarousel 4 | // 5 | // Created by Lee Kah Seng on 20/07/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. (https://github.com/LeeKahSeng/KSImageCarousel) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | // 26 | // 27 | 28 | import Foundation 29 | 30 | 31 | /// Simple singleton class to download and cache images downloaded from URL 32 | class KSICImageCache { 33 | 34 | /// Singleton object of KSICImageCache 35 | static let shared = KSICImageCache() 36 | 37 | private let cache = NSCache() 38 | private init() { 39 | cache.countLimit = 20 // Max cache 20 image 40 | cache.totalCostLimit = 20 * 1024 * 1024 // 20M 41 | } 42 | 43 | /// Fetch image from url. If already in cache, will use image in cache instead 44 | /// 45 | /// - Parameters: 46 | /// - url: url of image 47 | /// - completion: completion handler when image found in cache / download completed 48 | func fetchImage(from url: URL, completion: @escaping (UIImage?) -> Void) { 49 | // Check for cache before downloading image 50 | if let cachedImage = fetchFromCache(url) { 51 | // Image found in cache 52 | completion(cachedImage) 53 | } else { 54 | 55 | // Download image 56 | let task = URLSession.shared.dataTask(with: url) { [unowned self] (data, response, error) in 57 | if error == nil { 58 | if let imageData = data, let image = UIImage(data: imageData) { 59 | 60 | self.cache.setObject(image, forKey: url.absoluteString as AnyObject, cost: imageData.count) 61 | 62 | DispatchQueue.main.async() { 63 | completion(image) 64 | } 65 | } 66 | } else { 67 | DispatchQueue.main.async() { 68 | completion(nil) 69 | } 70 | } 71 | } 72 | 73 | // Start download 74 | task.resume() 75 | } 76 | } 77 | 78 | 79 | /// Find image in cache base on URL string 80 | /// 81 | /// - Parameter url: url of image 82 | /// - Returns: image found in cache 83 | private func fetchFromCache(_ url: URL) -> UIImage? { 84 | return cache.object(forKey: url.absoluteString as AnyObject) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel.xcodeproj/xcshareddata/xcschemes/KSImageCarousel.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KSImageCarousel 2 | 3 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 4 | [![CocoaPods](https://img.shields.io/cocoapods/v/KSFacebookButton.svg)](http://cocoapods.org/pods/KSFacebookButton) 5 | [![Platform](https://img.shields.io/cocoapods/p/KSFacebookButton.svg?)](http://cocoadocs.org/docsets/KSFacebookButton) 6 | [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/Carthage/Carthage/master/LICENSE.md) 7 | 8 | KSImageCarousel is a lightweight image carousel which can easily adapt to different type of image source. KSImageCarousel support both finite and infinite scrolling mode. Just instantiate KSImageCarousel with your desire UIImages or image URLs and it will do all the work for you. 9 | 10 | 11 | ## Requirements 12 | * Xcode 10 or later 13 | * iOS 10.3 or later 14 | * Swift 4.2 or later 15 | 16 | 17 | ## Features 18 | * Infinite scrolling mode 19 | * Finite scrolling mode 20 | * Auto scroll 21 | * Support ```UIImage``` and ```URL``` as image source 22 | * Can easily adapt to other image source 23 | 24 | 25 | ## Example 26 | Clone or download the source code and launch the project with ```KSImageCarouselExample.xcworkspace```. The example include sample code of using both infinite and finite scrolling mode. 27 | 28 | ![](https://thumbs.gfycat.com/UnhappyVariableIberianmole-size_restricted.gif) 29 | 30 | 31 | ## Installation 32 | ### CocoaPods 33 | ``` ruby 34 | pod 'KSImageCarousel' 35 | ``` 36 | 37 | ### Carthage 38 | 1. Create and update Cartfile 39 | ``` ruby 40 | github "LeeKahSeng/KSImageCarousel" 41 | ``` 42 | 2. Build the framework using terminal 43 | ``` 44 | carthage update 45 | ``` 46 | 3. After finish building the framework using Carthage, open XCode and select you project in the project navigator. 47 | 4. At ```Build Phases``` tab, add ```KSImageCarousel.framework``` to ```Link Binary with Libraries```. 48 | 5. At ```General``` tab, add ```KSImageCarousel.framework``` to ```Embedded Binaries```. 49 | 50 | ### Manually 51 | 1. Download the project. 52 | 2. Drag the ```Sources``` folder in ```\KSImageCarousel\KSImageCarousel``` into your Xcode project. 53 | 3. Add ```import UIKit``` to all the source code that causing compile error. 54 | 4. Build & run. 55 | 56 | 57 | ## How to use 58 | KSImageCarousel is developed using coordinator pattern. Thus to start using KSImageCarousel, create a ```KSICInfiniteCoordinator``` or ```KSICFiniteCoordinator``` and use it to show the carousel in the desire container view. 59 | 60 | Make sure you import KSImageCarousel if you are using CocoaPods or Carthage. 61 | ```swift 62 | import KSImageCarousel 63 | ``` 64 | 65 | To use KSImageCarousel with finite scrolling mode. 66 | ```swift 67 | // Create container view 68 | let carouselContainer = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 150)) 69 | view.addSubview(carouselContainer) 70 | 71 | // Create array of image source 72 | let model = [ 73 | URL(string: "https://via.placeholder.com/375x281/403ADA/FFFFFF?text=Image-0")!, 74 | URL(string: "https://via.placeholder.com/375x281/5D0F25/FFFFFF?text=Image-1")!, 75 | URL(string: "https://via.placeholder.com/375x281/83B002/FFFFFF?text=Image-2")!, 76 | URL(string: "https://via.placeholder.com/375x281/1B485D/FFFFFF?text=Image-3")!, 77 | URL(string: "https://via.placeholder.com/375x281/E6581C/FFFFFF?text=Image-4")!, 78 | ] 79 | 80 | // Use coordinator to show the carousel 81 | if let coordinator = try? KSICFiniteCoordinator(with: model, placeholderImage: nil, initialPage: 0) { 82 | coordinator.activityIndicatorStyle = .white 83 | coordinator.showCarousel(inside: carouselContainer, of: self) 84 | } 85 | ``` 86 | 87 | To use KSImageCarousel with infinite scrolling mode 88 | ```swift 89 | // Create container view 90 | let carouselContainer = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 150)) 91 | view.addSubview(carouselContainer) 92 | 93 | // Create array of image source 94 | let model = [ 95 | URL(string: "https://via.placeholder.com/375x281/403ADA/FFFFFF?text=Image-0")!, 96 | URL(string: "https://via.placeholder.com/375x281/5D0F25/FFFFFF?text=Image-1")!, 97 | URL(string: "https://via.placeholder.com/375x281/83B002/FFFFFF?text=Image-2")!, 98 | URL(string: "https://via.placeholder.com/375x281/1B485D/FFFFFF?text=Image-3")!, 99 | URL(string: "https://via.placeholder.com/375x281/E6581C/FFFFFF?text=Image-4")!, 100 | ] 101 | 102 | // Use coordinator to show the carousel 103 | if let coordinator = try? KSICInfiniteCoordinator(with: model, placeholderImage: nil, initialPage: 0) { 104 | coordinator.activityIndicatorStyle = .white 105 | coordinator.showCarousel(inside: carouselContainer, of: self) 106 | } 107 | ``` 108 | 109 | To enable auto scrolling for KSICInfiniteCoordinator 110 | ```swift 111 | coordinator.startAutoScroll(withDirection: .left, interval: 1) 112 | ``` 113 | 114 | To hide / show the activity indicator view when image being downloaded 115 | ```swift 116 | coordinator.shouldShowActivityIndicator = true 117 | ``` 118 | 119 | To change the activity indicator view style 120 | ```swift 121 | coordinator.activityIndicatorStyle = .white 122 | ``` 123 | 124 | Please note that both ```shouldShowActivityIndicator``` and ```activityIndicatorStyle``` needs to be set before calling ```showCarousel(inside: UIView, of: UIViewController)``` 125 | 126 | To detect tap event on the carousel, just conform to ```KSICCoordinatorDelegate``` and implement ```carouselDidTappedImage(at index: Int, coordinator: KSICCoordinator)``` 127 | 128 | Feel free to clone or download the source code and checkout the KSImageCarouselExample project which highlights how to use KSImageCarousel correctly. 129 | 130 | ### Adapt to use other type of image source 131 | Currently KSImageCarousel support both ```UIImage``` and ```URL``` as image source. However, there might be situation where you need KSImageCarousel to support other image source such as Base64, byte array, etc., or you would prefer to use your project's own image downloader instead of the build-in downloader of KSImageCarousel. What you need to do is just create a custom image provider class and conform to ```KSImageCarouselDisplayable``` protocol. 132 | 133 | Sample class to support Base64 image source: 134 | ```swift 135 | class Base64ImageProvider { 136 | 137 | let base64: String 138 | 139 | init(_ base64: String) { 140 | self.base64 = base64 141 | } 142 | 143 | func covertToImage(completion: @escaping (UIImage?) -> Void) { 144 | 145 | /*** Code to covert Base64 to UIImage here ***/ 146 | let convsionResult = UIImage() 147 | /*********************************************/ 148 | 149 | // Call completion handler with the conversion result 150 | completion(convsionResult) 151 | } 152 | } 153 | 154 | 155 | extension Base64ImageProvider: KSImageCarouselDisplayable { 156 | 157 | func createCarouselImage(completion: @escaping (UIImage?, KSImageCarouselDisplayable) -> Void) { 158 | covertToImage { (image) in 159 | completion(image, self) 160 | } 161 | } 162 | 163 | func isEqual(to displayable: KSImageCarouselDisplayable) -> Bool { 164 | if let provider = displayable as? Base64ImageProvider { 165 | return base64 == provider.base64 166 | } else { 167 | return false 168 | } 169 | } 170 | } 171 | ``` 172 | Sample class to use other image downloader: 173 | ```swift 174 | class ImageProvider { 175 | 176 | let url: URL 177 | 178 | init(_ url: URL) { 179 | self.url = url 180 | } 181 | 182 | func downloadImage(completion: @escaping (UIImage?) -> Void) { 183 | 184 | /*** Code to download image using you own downloader class here ***/ 185 | let downloadResult = UIImage() 186 | /******************************************************************/ 187 | 188 | // Call completion handler with the conversion result 189 | completion(downloadResult) 190 | } 191 | } 192 | 193 | 194 | extension ImageProvider: KSImageCarouselDisplayable { 195 | 196 | func createCarouselImage(completion: @escaping (UIImage?, KSImageCarouselDisplayable) -> Void) { 197 | downloadImage { (downloadedImage) in 198 | completion(downloadedImage, self) 199 | } 200 | } 201 | 202 | func isEqual(to displayable: KSImageCarouselDisplayable) -> Bool { 203 | if let provider = displayable as? ImageProvider { 204 | return url == provider.url 205 | } else { 206 | return false 207 | } 208 | } 209 | } 210 | ``` 211 | 212 | 213 | ## License 214 | 215 | This code is distributed under the terms and conditions of the [MIT license](LICENSE). 216 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel/Sources/KSICScrollerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KSICScrollerViewController.swift 3 | // KSImageCarousel 4 | // 5 | // Created by Lee Kah Seng on 01/06/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. (https://github.com/LeeKahSeng/KSImageCarousel) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | // 26 | // 27 | 28 | import UIKit 29 | 30 | public protocol KSICScrollerViewControllerDelegate { 31 | func scrollerViewControllerDidGotoNextPage(_ viewController: KSICScrollerViewController) 32 | func scrollerViewControllerDidGotoPreviousPage(_ viewController: KSICScrollerViewController) 33 | func scrollerViewControllerDidFinishLayoutSubviews(_ viewController: KSICScrollerViewController) 34 | func scrollerViewControllerDidTappedImageView(at index: Int, viewController: KSICScrollerViewController) 35 | 36 | func scrollerViewControllerShouldShowActivityIndicator() -> Bool 37 | func scrollerViewControllerShowActivityIndicatorStyle() -> UIActivityIndicatorView.Style 38 | } 39 | 40 | public class KSICScrollerViewController: UIViewController { 41 | 42 | var delegate: KSICScrollerViewControllerDelegate 43 | 44 | lazy fileprivate var scrollView: UIScrollView = UIScrollView() 45 | fileprivate var imageViews: [KSICImageView] = [] 46 | fileprivate var tapGestureRecognizers: [UITapGestureRecognizer?] = [] 47 | private let placeholderImage: UIImage? 48 | 49 | /// This will be use to determine wether scroll view had scrolled to next page or previous page after scroll ended 50 | fileprivate var contentOffsetX: CGFloat = 0 51 | 52 | private var imageViewCount: Int { 53 | return viewModel.count 54 | } 55 | 56 | var viewModel: [KSImageCarouselDisplayable] { 57 | didSet { 58 | // Update scroll view with new view model 59 | setViewModelToScrollView() 60 | } 61 | } 62 | 63 | init(withViewModel vm: [KSImageCarouselDisplayable], placeholderImage: UIImage?, delegate: KSICScrollerViewControllerDelegate) { 64 | 65 | viewModel = vm 66 | self.placeholderImage = placeholderImage 67 | self.delegate = delegate 68 | 69 | // Image views that later will be added to scroll view as subviews 70 | // The number of subviews needed will be same as number of view models provided 71 | for _ in vm.enumerated() { 72 | // Set placeholder image to image view and keep in array 73 | let imgView = KSICImageView(image: placeholderImage) 74 | imgView.activityIndicator.alpha = delegate.scrollerViewControllerShouldShowActivityIndicator() ? 1.0 : 0.0 75 | imgView.activityIndicator.style = delegate.scrollerViewControllerShowActivityIndicatorStyle() 76 | imageViews.append(imgView) 77 | } 78 | 79 | super.init(nibName: nil, bundle: nil) 80 | } 81 | 82 | required public init?(coder aDecoder: NSCoder) { 83 | fatalError("KSICScrollerViewController should not be use at interface builder. Please use KSICFiniteCoordinator / KSICInfiniteCoordinator") 84 | } 85 | 86 | override public func viewDidLoad() { 87 | super.viewDidLoad() 88 | 89 | configureScrollView() 90 | setViewModelToScrollView() 91 | } 92 | 93 | override public func viewDidLayoutSubviews() { 94 | 95 | // Set scroll view content size 96 | let scrollViewWidth = scrollView.frame.width 97 | let scrollViewHeight = scrollView.frame.height 98 | scrollView.contentSize = CGSize(width: scrollViewWidth * CGFloat(imageViewCount), height: scrollViewHeight) 99 | 100 | // Layout image view in scroll view 101 | for i in 0.. oldX { 203 | // Next page 204 | // Trigger delegate and let coordinator decide what to do 205 | delegate.scrollerViewControllerDidGotoNextPage(self) 206 | } else if newX < oldX { 207 | // Previous page 208 | // Trigger delegate and let coordinator decide what to do 209 | delegate.scrollerViewControllerDidGotoPreviousPage(self) 210 | } 211 | 212 | contentOffsetX = newX 213 | } 214 | 215 | @objc fileprivate func imageViewDidTapped(regconizer: UITapGestureRecognizer) { 216 | 217 | let index = tapGestureRecognizers.index { (reg) -> Bool in 218 | return (reg == regconizer) 219 | } 220 | 221 | if let i = index { 222 | delegate.scrollerViewControllerDidTappedImageView(at: i, viewController: self) 223 | } 224 | } 225 | } 226 | 227 | extension KSICScrollerViewController: UIScrollViewDelegate { 228 | 229 | public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 230 | // Track the scroll view content offset when begin scrolling 231 | // This will be use to determine wether scroll view had scrolled to next page or previous page after scroll ended 232 | contentOffsetX = scrollView.contentOffset.x 233 | } 234 | 235 | public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 236 | // This delegate will trigger after scroll view finish scrolling due to user interaction 237 | scrollViewDidEndScrolling(withNewContentOffsetX: scrollView.contentOffset.x, oldContentOffsetX: contentOffsetX) 238 | } 239 | 240 | public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { 241 | // This delegate will trigger after scroll view is scrolled programatically using scrollRectToVisible() 242 | scrollViewDidEndScrolling(withNewContentOffsetX: scrollView.contentOffset.x, oldContentOffsetX: contentOffsetX) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /KSImageCarouselExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F91944721F1F56BA00C4140F /* KSImageCarousel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F91944711F1F56BA00C4140F /* KSImageCarousel.framework */; }; 11 | F91944731F1F56BA00C4140F /* KSImageCarousel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F91944711F1F56BA00C4140F /* KSImageCarousel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | F9D158531ED5B94100F4EAD7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D158521ED5B94100F4EAD7 /* AppDelegate.swift */; }; 13 | F9D158581ED5B94100F4EAD7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F9D158561ED5B94100F4EAD7 /* Main.storyboard */; }; 14 | F9D1585A1ED5B94100F4EAD7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F9D158591ED5B94100F4EAD7 /* Assets.xcassets */; }; 15 | F9D1585D1ED5B94100F4EAD7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F9D1585B1ED5B94100F4EAD7 /* LaunchScreen.storyboard */; }; 16 | F9D4A6441F20864500CBEE07 /* placeholder.jpg in Resources */ = {isa = PBXBuildFile; fileRef = F9D4A6431F20864500CBEE07 /* placeholder.jpg */; }; 17 | F9F4813A1F24AB9F00F647CF /* InfinityDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9F481391F24AB9F00F647CF /* InfinityDemoViewController.swift */; }; 18 | F9F4813C1F24ABB600F647CF /* FiniteDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9F4813B1F24ABB600F647CF /* FiniteDemoViewController.swift */; }; 19 | F9F481441F24AE7900F647CF /* Image-0.jpg in Resources */ = {isa = PBXBuildFile; fileRef = F9F4813E1F24AE7900F647CF /* Image-0.jpg */; }; 20 | F9F481451F24AE7900F647CF /* Image-1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = F9F4813F1F24AE7900F647CF /* Image-1.jpg */; }; 21 | F9F481461F24AE7900F647CF /* Image-2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = F9F481401F24AE7900F647CF /* Image-2.jpg */; }; 22 | F9F481471F24AE7900F647CF /* Image-3.jpg in Resources */ = {isa = PBXBuildFile; fileRef = F9F481411F24AE7900F647CF /* Image-3.jpg */; }; 23 | F9F481481F24AE7900F647CF /* Image-4.jpg in Resources */ = {isa = PBXBuildFile; fileRef = F9F481421F24AE7900F647CF /* Image-4.jpg */; }; 24 | F9F481491F24AE7900F647CF /* Image-5.jpg in Resources */ = {isa = PBXBuildFile; fileRef = F9F481431F24AE7900F647CF /* Image-5.jpg */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXCopyFilesBuildPhase section */ 28 | F91944741F1F56BA00C4140F /* Embed Frameworks */ = { 29 | isa = PBXCopyFilesBuildPhase; 30 | buildActionMask = 2147483647; 31 | dstPath = ""; 32 | dstSubfolderSpec = 10; 33 | files = ( 34 | F91944731F1F56BA00C4140F /* KSImageCarousel.framework in Embed Frameworks */, 35 | ); 36 | name = "Embed Frameworks"; 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXCopyFilesBuildPhase section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | F91944711F1F56BA00C4140F /* KSImageCarousel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KSImageCarousel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | F9D1584F1ED5B94100F4EAD7 /* KSImageCarouselExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KSImageCarouselExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | F9D158521ED5B94100F4EAD7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | F9D158571ED5B94100F4EAD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46 | F9D158591ED5B94100F4EAD7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | F9D1585C1ED5B94100F4EAD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 48 | F9D1585E1ED5B94100F4EAD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | F9D4A6431F20864500CBEE07 /* placeholder.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = placeholder.jpg; sourceTree = ""; }; 50 | F9F481391F24AB9F00F647CF /* InfinityDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfinityDemoViewController.swift; sourceTree = ""; }; 51 | F9F4813B1F24ABB600F647CF /* FiniteDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FiniteDemoViewController.swift; sourceTree = ""; }; 52 | F9F4813E1F24AE7900F647CF /* Image-0.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "Image-0.jpg"; sourceTree = ""; }; 53 | F9F4813F1F24AE7900F647CF /* Image-1.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "Image-1.jpg"; sourceTree = ""; }; 54 | F9F481401F24AE7900F647CF /* Image-2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "Image-2.jpg"; sourceTree = ""; }; 55 | F9F481411F24AE7900F647CF /* Image-3.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "Image-3.jpg"; sourceTree = ""; }; 56 | F9F481421F24AE7900F647CF /* Image-4.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "Image-4.jpg"; sourceTree = ""; }; 57 | F9F481431F24AE7900F647CF /* Image-5.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "Image-5.jpg"; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | F9D1584C1ED5B94100F4EAD7 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | F91944721F1F56BA00C4140F /* KSImageCarousel.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 3AEDB88392DE9A467EA9C030 /* Frameworks */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | F91944711F1F56BA00C4140F /* KSImageCarousel.framework */, 76 | ); 77 | name = Frameworks; 78 | sourceTree = ""; 79 | }; 80 | F9D158461ED5B94100F4EAD7 = { 81 | isa = PBXGroup; 82 | children = ( 83 | F9D158511ED5B94100F4EAD7 /* KSImageCarouselExample */, 84 | F9D158501ED5B94100F4EAD7 /* Products */, 85 | 3AEDB88392DE9A467EA9C030 /* Frameworks */, 86 | ); 87 | sourceTree = ""; 88 | }; 89 | F9D158501ED5B94100F4EAD7 /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | F9D1584F1ED5B94100F4EAD7 /* KSImageCarouselExample.app */, 93 | ); 94 | name = Products; 95 | sourceTree = ""; 96 | }; 97 | F9D158511ED5B94100F4EAD7 /* KSImageCarouselExample */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | F9D158521ED5B94100F4EAD7 /* AppDelegate.swift */, 101 | F9F481391F24AB9F00F647CF /* InfinityDemoViewController.swift */, 102 | F9F4813B1F24ABB600F647CF /* FiniteDemoViewController.swift */, 103 | F9D158561ED5B94100F4EAD7 /* Main.storyboard */, 104 | F9D158591ED5B94100F4EAD7 /* Assets.xcassets */, 105 | F9D1585B1ED5B94100F4EAD7 /* LaunchScreen.storyboard */, 106 | F9D1585E1ED5B94100F4EAD7 /* Info.plist */, 107 | F9D4A6431F20864500CBEE07 /* placeholder.jpg */, 108 | F9F4813D1F24AE7900F647CF /* Sample-Photos */, 109 | ); 110 | path = KSImageCarouselExample; 111 | sourceTree = ""; 112 | }; 113 | F9F4813D1F24AE7900F647CF /* Sample-Photos */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | F9F4813E1F24AE7900F647CF /* Image-0.jpg */, 117 | F9F4813F1F24AE7900F647CF /* Image-1.jpg */, 118 | F9F481401F24AE7900F647CF /* Image-2.jpg */, 119 | F9F481411F24AE7900F647CF /* Image-3.jpg */, 120 | F9F481421F24AE7900F647CF /* Image-4.jpg */, 121 | F9F481431F24AE7900F647CF /* Image-5.jpg */, 122 | ); 123 | path = "Sample-Photos"; 124 | sourceTree = ""; 125 | }; 126 | /* End PBXGroup section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | F9D1584E1ED5B94100F4EAD7 /* KSImageCarouselExample */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = F9D158611ED5B94100F4EAD7 /* Build configuration list for PBXNativeTarget "KSImageCarouselExample" */; 132 | buildPhases = ( 133 | F9D1584B1ED5B94100F4EAD7 /* Sources */, 134 | F9D1584C1ED5B94100F4EAD7 /* Frameworks */, 135 | F9D1584D1ED5B94100F4EAD7 /* Resources */, 136 | F91944741F1F56BA00C4140F /* Embed Frameworks */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = KSImageCarouselExample; 143 | productName = KSImageCarouselExample; 144 | productReference = F9D1584F1ED5B94100F4EAD7 /* KSImageCarouselExample.app */; 145 | productType = "com.apple.product-type.application"; 146 | }; 147 | /* End PBXNativeTarget section */ 148 | 149 | /* Begin PBXProject section */ 150 | F9D158471ED5B94100F4EAD7 /* Project object */ = { 151 | isa = PBXProject; 152 | attributes = { 153 | LastSwiftUpdateCheck = 0830; 154 | LastUpgradeCheck = 1010; 155 | ORGANIZATIONNAME = "Lee Kah Seng"; 156 | TargetAttributes = { 157 | F9D1584E1ED5B94100F4EAD7 = { 158 | CreatedOnToolsVersion = 8.3.2; 159 | DevelopmentTeam = SM42V25455; 160 | LastSwiftMigration = 0900; 161 | ProvisioningStyle = Automatic; 162 | }; 163 | }; 164 | }; 165 | buildConfigurationList = F9D1584A1ED5B94100F4EAD7 /* Build configuration list for PBXProject "KSImageCarouselExample" */; 166 | compatibilityVersion = "Xcode 3.2"; 167 | developmentRegion = English; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | Base, 172 | ); 173 | mainGroup = F9D158461ED5B94100F4EAD7; 174 | productRefGroup = F9D158501ED5B94100F4EAD7 /* Products */; 175 | projectDirPath = ""; 176 | projectRoot = ""; 177 | targets = ( 178 | F9D1584E1ED5B94100F4EAD7 /* KSImageCarouselExample */, 179 | ); 180 | }; 181 | /* End PBXProject section */ 182 | 183 | /* Begin PBXResourcesBuildPhase section */ 184 | F9D1584D1ED5B94100F4EAD7 /* Resources */ = { 185 | isa = PBXResourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | F9D1585D1ED5B94100F4EAD7 /* LaunchScreen.storyboard in Resources */, 189 | F9F481491F24AE7900F647CF /* Image-5.jpg in Resources */, 190 | F9F481451F24AE7900F647CF /* Image-1.jpg in Resources */, 191 | F9F481441F24AE7900F647CF /* Image-0.jpg in Resources */, 192 | F9F481461F24AE7900F647CF /* Image-2.jpg in Resources */, 193 | F9D4A6441F20864500CBEE07 /* placeholder.jpg in Resources */, 194 | F9F481471F24AE7900F647CF /* Image-3.jpg in Resources */, 195 | F9D1585A1ED5B94100F4EAD7 /* Assets.xcassets in Resources */, 196 | F9F481481F24AE7900F647CF /* Image-4.jpg in Resources */, 197 | F9D158581ED5B94100F4EAD7 /* Main.storyboard in Resources */, 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | }; 201 | /* End PBXResourcesBuildPhase section */ 202 | 203 | /* Begin PBXSourcesBuildPhase section */ 204 | F9D1584B1ED5B94100F4EAD7 /* Sources */ = { 205 | isa = PBXSourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | F9F4813C1F24ABB600F647CF /* FiniteDemoViewController.swift in Sources */, 209 | F9D158531ED5B94100F4EAD7 /* AppDelegate.swift in Sources */, 210 | F9F4813A1F24AB9F00F647CF /* InfinityDemoViewController.swift in Sources */, 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | /* End PBXSourcesBuildPhase section */ 215 | 216 | /* Begin PBXVariantGroup section */ 217 | F9D158561ED5B94100F4EAD7 /* Main.storyboard */ = { 218 | isa = PBXVariantGroup; 219 | children = ( 220 | F9D158571ED5B94100F4EAD7 /* Base */, 221 | ); 222 | name = Main.storyboard; 223 | sourceTree = ""; 224 | }; 225 | F9D1585B1ED5B94100F4EAD7 /* LaunchScreen.storyboard */ = { 226 | isa = PBXVariantGroup; 227 | children = ( 228 | F9D1585C1ED5B94100F4EAD7 /* Base */, 229 | ); 230 | name = LaunchScreen.storyboard; 231 | sourceTree = ""; 232 | }; 233 | /* End PBXVariantGroup section */ 234 | 235 | /* Begin XCBuildConfiguration section */ 236 | F9D1585F1ED5B94100F4EAD7 /* Debug */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | CLANG_ANALYZER_NONNULL = YES; 241 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 243 | CLANG_CXX_LIBRARY = "libc++"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_COMMA = YES; 249 | CLANG_WARN_CONSTANT_CONVERSION = YES; 250 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 252 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 253 | CLANG_WARN_EMPTY_BODY = YES; 254 | CLANG_WARN_ENUM_CONVERSION = YES; 255 | CLANG_WARN_INFINITE_RECURSION = YES; 256 | CLANG_WARN_INT_CONVERSION = YES; 257 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 259 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 260 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 261 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 262 | CLANG_WARN_STRICT_PROTOTYPES = YES; 263 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 264 | CLANG_WARN_UNREACHABLE_CODE = YES; 265 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 266 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 267 | COPY_PHASE_STRIP = NO; 268 | DEBUG_INFORMATION_FORMAT = dwarf; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | ENABLE_TESTABILITY = YES; 271 | GCC_C_LANGUAGE_STANDARD = gnu99; 272 | GCC_DYNAMIC_NO_PIC = NO; 273 | GCC_NO_COMMON_BLOCKS = YES; 274 | GCC_OPTIMIZATION_LEVEL = 0; 275 | GCC_PREPROCESSOR_DEFINITIONS = ( 276 | "DEBUG=1", 277 | "$(inherited)", 278 | ); 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 286 | MTL_ENABLE_DEBUG_INFO = YES; 287 | ONLY_ACTIVE_ARCH = YES; 288 | SDKROOT = iphoneos; 289 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 290 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 291 | }; 292 | name = Debug; 293 | }; 294 | F9D158601ED5B94100F4EAD7 /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ALWAYS_SEARCH_USER_PATHS = NO; 298 | CLANG_ANALYZER_NONNULL = YES; 299 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 305 | CLANG_WARN_BOOL_CONVERSION = YES; 306 | CLANG_WARN_COMMA = YES; 307 | CLANG_WARN_CONSTANT_CONVERSION = YES; 308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 310 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 311 | CLANG_WARN_EMPTY_BODY = YES; 312 | CLANG_WARN_ENUM_CONVERSION = YES; 313 | CLANG_WARN_INFINITE_RECURSION = YES; 314 | CLANG_WARN_INT_CONVERSION = YES; 315 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 316 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 317 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 319 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 320 | CLANG_WARN_STRICT_PROTOTYPES = YES; 321 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 322 | CLANG_WARN_UNREACHABLE_CODE = YES; 323 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 324 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 325 | COPY_PHASE_STRIP = NO; 326 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 327 | ENABLE_NS_ASSERTIONS = NO; 328 | ENABLE_STRICT_OBJC_MSGSEND = YES; 329 | GCC_C_LANGUAGE_STANDARD = gnu99; 330 | GCC_NO_COMMON_BLOCKS = YES; 331 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 332 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 333 | GCC_WARN_UNDECLARED_SELECTOR = YES; 334 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 335 | GCC_WARN_UNUSED_FUNCTION = YES; 336 | GCC_WARN_UNUSED_VARIABLE = YES; 337 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 338 | MTL_ENABLE_DEBUG_INFO = NO; 339 | SDKROOT = iphoneos; 340 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 341 | VALIDATE_PRODUCT = YES; 342 | }; 343 | name = Release; 344 | }; 345 | F9D158621ED5B94100F4EAD7 /* Debug */ = { 346 | isa = XCBuildConfiguration; 347 | buildSettings = { 348 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 349 | DEVELOPMENT_TEAM = SM42V25455; 350 | INFOPLIST_FILE = KSImageCarouselExample/Info.plist; 351 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 352 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 353 | PRODUCT_BUNDLE_IDENTIFIER = com.LeeKahSeng.KSImageCarouselExample; 354 | PRODUCT_NAME = "$(TARGET_NAME)"; 355 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 356 | SWIFT_VERSION = 4.2; 357 | }; 358 | name = Debug; 359 | }; 360 | F9D158631ED5B94100F4EAD7 /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 364 | DEVELOPMENT_TEAM = SM42V25455; 365 | INFOPLIST_FILE = KSImageCarouselExample/Info.plist; 366 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 367 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 368 | PRODUCT_BUNDLE_IDENTIFIER = com.LeeKahSeng.KSImageCarouselExample; 369 | PRODUCT_NAME = "$(TARGET_NAME)"; 370 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 371 | SWIFT_VERSION = 4.2; 372 | }; 373 | name = Release; 374 | }; 375 | /* End XCBuildConfiguration section */ 376 | 377 | /* Begin XCConfigurationList section */ 378 | F9D1584A1ED5B94100F4EAD7 /* Build configuration list for PBXProject "KSImageCarouselExample" */ = { 379 | isa = XCConfigurationList; 380 | buildConfigurations = ( 381 | F9D1585F1ED5B94100F4EAD7 /* Debug */, 382 | F9D158601ED5B94100F4EAD7 /* Release */, 383 | ); 384 | defaultConfigurationIsVisible = 0; 385 | defaultConfigurationName = Release; 386 | }; 387 | F9D158611ED5B94100F4EAD7 /* Build configuration list for PBXNativeTarget "KSImageCarouselExample" */ = { 388 | isa = XCConfigurationList; 389 | buildConfigurations = ( 390 | F9D158621ED5B94100F4EAD7 /* Debug */, 391 | F9D158631ED5B94100F4EAD7 /* Release */, 392 | ); 393 | defaultConfigurationIsVisible = 0; 394 | defaultConfigurationName = Release; 395 | }; 396 | /* End XCConfigurationList section */ 397 | }; 398 | rootObject = F9D158471ED5B94100F4EAD7 /* Project object */; 399 | } 400 | -------------------------------------------------------------------------------- /KSImageCarouselExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 114 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 185 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarouselTests/KSImageCarouselTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KSImageCarouselTests.swift 3 | // KSImageCarouselTests 4 | // 5 | // Created by Lee Kah Seng on 24/05/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KSImageCarousel 11 | 12 | class KSImageCarouselTests: XCTestCase { 13 | 14 | enum TestImage { 15 | case black 16 | case white 17 | case green 18 | case red 19 | case blue 20 | case gray 21 | 22 | var name: String { 23 | switch self { 24 | case .black: 25 | return "black" 26 | case .white: 27 | return "white" 28 | case .green: 29 | return "green" 30 | case .red: 31 | return "red" 32 | case .blue: 33 | return "blue" 34 | case .gray: 35 | return "gray" 36 | } 37 | } 38 | } 39 | 40 | override func setUp() { 41 | super.setUp() 42 | // Put setup code here. This method is called before the invocation of each test method in the class. 43 | } 44 | 45 | override func tearDown() { 46 | // Put teardown code here. This method is called after the invocation of each test method in the class. 47 | super.tearDown() 48 | } 49 | 50 | func testCoordinatorInitSuccessful() { 51 | 52 | // Create test data 53 | let dummyModel = [createTestImage(.black), 54 | createTestImage(.white), 55 | createTestImage(.blue)] 56 | let dummyInitialPage = 1 57 | 58 | // Test KSICFiniteCoordinator 59 | let coordinator = try? KSICFiniteCoordinator(with: dummyModel, initialPage: dummyInitialPage) 60 | XCTAssertNotNil(coordinator) 61 | 62 | // Test KSICInfiniteCoordinator 63 | let coordinator2 = try? KSICInfiniteCoordinator(with: dummyModel, initialPage: dummyInitialPage) 64 | XCTAssertNotNil(coordinator2) 65 | } 66 | 67 | func testCoordinatorInitFail() { 68 | 69 | // Create test data 70 | let dummyModel = [createTestImage(.black), 71 | createTestImage(.white), 72 | createTestImage(.blue)] 73 | let dummyInitialPage = 10 74 | 75 | // Test KSICFiniteCoordinator 76 | // Out of range 77 | XCTAssertThrowsError(try KSICFiniteCoordinator(with: dummyModel, initialPage: dummyInitialPage)) { error in 78 | XCTAssertEqual(error as? CoordinatorError, CoordinatorError.pageOutOfRange) 79 | } 80 | 81 | // Empty model 82 | XCTAssertThrowsError(try KSICFiniteCoordinator(with: [], initialPage: 0)) { error in 83 | XCTAssertEqual(error as? CoordinatorError, CoordinatorError.emptyModel) 84 | } 85 | 86 | // Test KSICInfiniteCoordinator 87 | // Out of range 88 | XCTAssertThrowsError(try KSICInfiniteCoordinator(with: dummyModel, initialPage: dummyInitialPage)) { error in 89 | XCTAssertEqual(error as? CoordinatorError, CoordinatorError.pageOutOfRange) 90 | } 91 | 92 | // Empty model 93 | XCTAssertThrowsError(try KSICInfiniteCoordinator(with: [], initialPage: 0)) { error in 94 | XCTAssertEqual(error as? CoordinatorError, CoordinatorError.emptyModel) 95 | } 96 | } 97 | 98 | func testFiniteCoordonatorPageNavigation() { 99 | 100 | // Create test data 101 | let dummyModel = [createTestImage(.black), 102 | createTestImage(.white), 103 | createTestImage(.gray), 104 | createTestImage(.red), 105 | createTestImage(.green), 106 | createTestImage(.blue)] 107 | 108 | let dummyInitialPage = 0 109 | 110 | let coordinator = try! KSICFiniteCoordinator(with: dummyModel, initialPage: dummyInitialPage) 111 | 112 | // Test initial view model value 113 | var currentPage = dummyInitialPage 114 | var expectedViewModel = [dummyModel[currentPage], dummyModel[currentPage + 1], dummyModel[currentPage + 2]] 115 | var actualViewModel = coordinator.carouselViewModel 116 | XCTAssertTrue(compare(viewModel1: expectedViewModel, viewModel2: actualViewModel)) 117 | 118 | // Go to next page 3 times 119 | var nextPageCount = 3 120 | for _ in 0.. Bool { 464 | 465 | for i in 0.. Bool { 478 | 479 | let data1 = UIImagePNGRepresentation(img1)! 480 | let data2 = UIImagePNGRepresentation(img2)! 481 | 482 | return data1 == data2 483 | } 484 | 485 | func createTestImage(_ img: TestImage) -> UIImage { 486 | return UIImage(named: img.name, in: Bundle(for: KSImageCarouselTests.self), compatibleWith: nil)! 487 | } 488 | } 489 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel/Sources/KSICCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KSICCoordinator.swift 3 | // KSImageCarousel 4 | // 5 | // Created by Lee Kah Seng on 28/05/2017. 6 | // Copyright © 2017 Lee Kah Seng. All rights reserved. (https://github.com/LeeKahSeng/KSImageCarousel) 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | // 26 | 27 | import Foundation 28 | 29 | public protocol KSICCoordinatorDelegate { 30 | 31 | /// Method triggered when image of carousel being tapped 32 | /// 33 | /// - Parameter index: model's index of the tapped image 34 | func carouselDidTappedImage(at index: Int, coordinator: KSICCoordinator) 35 | } 36 | 37 | extension KSICCoordinatorDelegate { 38 | // Optional delegte method 39 | public func carouselDidTappedImage(at index: Int, coordinator: KSICCoordinator) {} 40 | } 41 | 42 | public protocol KSICCoordinator: class, KSICScrollerViewControllerDelegate { 43 | 44 | /// The carousel that being show on screen 45 | var carousel: KSICScrollerViewController? { get } 46 | 47 | /// The model (images that need to be show on carousel) 48 | var model: [KSImageCarouselDisplayable] { get } 49 | 50 | /// The placeholder image to show while image being download 51 | var placeholderImage: UIImage? { get } 52 | 53 | /// A Boolean value that determines whether the activity indicator is visible when loading image to carousel. Default value is true. This value should be set before calling showCarousel(inside: of:) 54 | var shouldShowActivityIndicator: Bool { get } 55 | 56 | /// Style of the activity indicator that shown when loading image to carousel. Default value is gray. This value needs should be set before calling showCarousel(inside: of:) 57 | var activityIndicatorStyle: UIActivityIndicatorView.Style { get } 58 | 59 | /// KSICCoordinator optional delegte 60 | var delegate: KSICCoordinatorDelegate? { get } 61 | 62 | /// View model for the carousel. 63 | /// The 3 elements consists of [prev page element, current page element, next page element] 64 | /// If model only have less than 3 element, view model will have less than 3 element as well 65 | var carouselViewModel: [KSImageCarouselDisplayable] { get } 66 | 67 | /// Page (index) of model that currently visible to user 68 | var currentPage: Int { get } 69 | 70 | /// Add the carousel to it's container 71 | func showCarousel(inside container: UIView, of parentViewController: UIViewController) 72 | 73 | /// Scroll carousel to next page (just like user swipe to left) 74 | func scrollToNextPage() 75 | 76 | /// Scroll carousel to previous page (just like user swipe to right) 77 | func scrollToPreviousPage() 78 | } 79 | 80 | extension KSICCoordinator { 81 | 82 | fileprivate var firstPage: Int { 83 | return 0 84 | } 85 | 86 | fileprivate var lastPage: Int { 87 | return model.count - 1 88 | } 89 | 90 | fileprivate var isFirstPage: Bool { 91 | return currentPage == firstPage 92 | } 93 | 94 | fileprivate var isLastPage: Bool { 95 | return currentPage == lastPage 96 | } 97 | 98 | /// Check to make sure the page number is in range (between first page & last page) 99 | /// 100 | /// - Parameter page: page number to check 101 | /// - Returns: True -> In range | False -> out of range 102 | fileprivate func isPageInRange(_ page: Int) -> Bool { 103 | return (page >= firstPage && page <= lastPage) 104 | } 105 | 106 | /// Add carousel as child view controller and follow the size of the container view 107 | /// 108 | /// - Parameters: 109 | /// - carousel: carousel to be added as child view controller 110 | /// - container: container that contain the carousel 111 | /// - parentViewController: parent view controller of the carousel 112 | fileprivate func add(_ carousel: KSICScrollerViewController, to container: UIView, of parentViewController: UIViewController) { 113 | 114 | // Note: automaticallyAdjustsScrollViewInsets of the parent view controller must be set to false so that content size of UIScrollView in KSICScrollerViewController is correct 115 | parentViewController.automaticallyAdjustsScrollViewInsets = false 116 | 117 | // Add KSICScrollerViewController as child view controller 118 | parentViewController.addChild(carousel) 119 | carousel.didMove(toParent: parentViewController) 120 | 121 | // Carousel to follow container size 122 | container.addSameSizeSubview(carousel.view) 123 | } 124 | } 125 | 126 | // MARK: - 127 | 128 | /// Carousel can only scroll until last page or first page when using this coordinator 129 | public class KSICFiniteCoordinator: KSICCoordinator { 130 | 131 | public var delegate: KSICCoordinatorDelegate? 132 | public let model: [KSImageCarouselDisplayable] 133 | public let placeholderImage: UIImage? 134 | public var shouldShowActivityIndicator = true 135 | public var activityIndicatorStyle: UIActivityIndicatorView.Style = .gray 136 | public internal(set) var carousel: KSICScrollerViewController? 137 | 138 | public private(set) var currentPage: Int { 139 | didSet { 140 | // Note: Everytime current page being set, we will update carousel's viewModel (which will update images in carousel) and scroll carousel to subview that user should see 141 | 142 | // Update view model of carousel 143 | carousel?.viewModel = carouselViewModel 144 | 145 | // Scroll carousel (without animation) to subview that user should see 146 | swapCarouselSubview() 147 | } 148 | } 149 | 150 | public var carouselViewModel: [KSImageCarouselDisplayable] { 151 | 152 | if model.count == 1 { 153 | // When model have only 1 element 154 | return [model[0]] 155 | 156 | } else if model.count == 2 { 157 | // When model have only 2 elements 158 | return [model[0], model[1]] 159 | 160 | } else { 161 | // When model have only 3 or more elements 162 | if isFirstPage { 163 | 164 | return [model[currentPage], 165 | model[currentPage + 1], 166 | model[currentPage + 2]] 167 | 168 | } else if isLastPage { 169 | 170 | return [model[currentPage - 2], 171 | model[currentPage - 1], 172 | model[currentPage]] 173 | 174 | } else { 175 | 176 | return [model[currentPage - 1], 177 | model[currentPage], 178 | model[currentPage + 1]] 179 | } 180 | } 181 | } 182 | 183 | /// Initializer 184 | /// 185 | /// - Parameters: 186 | /// - model: Model for carousel 187 | /// - placeholderImage: Placeholder image to show when image being download 188 | /// - initialPage: Page to display when carousel first shown 189 | /// - Throws: emptyModel, pageOutOfRange 190 | public init(with model: [KSImageCarouselDisplayable], placeholderImage: UIImage?, initialPage: Int) throws { 191 | 192 | // Make sure model is not empty 193 | guard model.count > 0 else { 194 | throw CoordinatorError.emptyModel 195 | } 196 | 197 | self.model = model 198 | self.placeholderImage = placeholderImage 199 | self.currentPage = initialPage 200 | 201 | // Make sure initial page is in range 202 | guard isPageInRange(initialPage) else { 203 | throw CoordinatorError.pageOutOfRange 204 | } 205 | } 206 | 207 | /// Initializer 208 | /// 209 | /// - Parameters: 210 | /// - model: Model for carousel 211 | /// - initialPage: Page to display when carousel first shown 212 | /// - Throws: emptyModel, pageOutOfRange 213 | public convenience init(with model: [KSImageCarouselDisplayable], initialPage: Int) throws { 214 | try self.init(with: model, placeholderImage: nil, initialPage: initialPage) 215 | } 216 | 217 | // MARK: KSICCoordinator conformation 218 | public func showCarousel(inside container: UIView, of parentViewController: UIViewController) { 219 | carousel = KSICScrollerViewController(withViewModel: carouselViewModel, placeholderImage: placeholderImage, delegate: self) 220 | add(carousel!, to: container, of: parentViewController) 221 | } 222 | 223 | public func scrollToNextPage() { 224 | 225 | // Simulate action when user scroll carousel with finger. This will trigger scrollerViewControllerDidGotoNextPage and everything will run just the same like a real user interaction 226 | 227 | if isFirstPage { 228 | carousel?.scrollToCenterSubview(true) 229 | } else { 230 | carousel?.scrollToLastSubview(true) 231 | } 232 | } 233 | 234 | public func scrollToPreviousPage() { 235 | 236 | // Simulate action when user scroll carousel with finger. This will trigger scrollerViewControllerDidGotoPreviousPage and everything will run just the same like a real user interaction 237 | 238 | if isLastPage { 239 | carousel?.scrollToCenterSubview(true) 240 | } else { 241 | carousel?.scrollToFirstSubview(true) 242 | } 243 | } 244 | 245 | // MARK: Utilities functions 246 | 247 | /// Set current page number 248 | /// 249 | /// - Parameter p: page number 250 | /// - Throws: pageOutOfRange 251 | private func setPage(_ p: Int) throws { 252 | 253 | // Make sure page is between first page and last page 254 | guard isPageInRange(p) else { 255 | // throw exception 256 | throw CoordinatorError.pageOutOfRange 257 | } 258 | 259 | currentPage = p 260 | } 261 | 262 | /// +1 to page number - calling this will update currentPage -> update caoursel.viewModel -> update images in carousel -> scroll carousel to desire subview 263 | func increasePageByOne() { 264 | if currentPage == lastPage { 265 | return 266 | } else { 267 | let newPage = currentPage + 1 268 | try! setPage(newPage) 269 | } 270 | } 271 | 272 | /// -1 to page number - calling this will update currentPage -> update caoursel.viewModel -> update images in carousel -> scroll carousel to desire subview 273 | func reducePageByOne() { 274 | if currentPage == firstPage { 275 | return 276 | } else { 277 | let newPage = currentPage - 1 278 | try! setPage(newPage) 279 | } 280 | } 281 | 282 | /// Base on current page, scroll carousel (without animation) to subview that should be visible to user 283 | fileprivate func swapCarouselSubview() { 284 | if isFirstPage { 285 | // Scroll to first image view 286 | carousel?.scrollToFirstSubview(false) 287 | } else if isLastPage { 288 | // Scroll to last image view 289 | carousel?.scrollToLastSubview(false) 290 | } else { 291 | // Scroll to center image view 292 | carousel?.scrollToCenterSubview(false) 293 | } 294 | } 295 | } 296 | 297 | // MARK: KSICScrollerViewControllerDelegate 298 | extension KSICCoordinator where Self: KSICFiniteCoordinator { 299 | public func scrollerViewControllerDidFinishLayoutSubviews(_ viewController: KSICScrollerViewController) { 300 | // Scroll carousel (without animation) to subview that user should see 301 | swapCarouselSubview() 302 | } 303 | 304 | public func scrollerViewControllerDidGotoNextPage(_ viewController: KSICScrollerViewController) { 305 | // Calling increasePageByOne() will update currentPage -> update caoursel.viewModel -> update images in carousel -> scroll carousel to desire subview 306 | increasePageByOne() 307 | } 308 | 309 | public func scrollerViewControllerDidGotoPreviousPage(_ viewController: KSICScrollerViewController) { 310 | // Calling reducePageByOne() will update currentPage -> update caoursel.viewModel -> update images in carousel -> scroll carousel to desire subview 311 | reducePageByOne() 312 | } 313 | 314 | public func scrollerViewControllerDidTappedImageView(at index: Int, viewController: KSICScrollerViewController) { 315 | delegate?.carouselDidTappedImage(at: currentPage, coordinator: self) 316 | } 317 | 318 | public func scrollerViewControllerShouldShowActivityIndicator() -> Bool { 319 | return shouldShowActivityIndicator 320 | } 321 | 322 | public func scrollerViewControllerShowActivityIndicatorStyle() -> UIActivityIndicatorView.Style { 323 | return activityIndicatorStyle 324 | } 325 | } 326 | 327 | // MARK: - 328 | 329 | /// Carousel will be able to scroll infinitely when using this coordinator 330 | public class KSICInfiniteCoordinator: KSICCoordinator { 331 | 332 | public enum KSICAutoScrollDirection { 333 | case left 334 | case right 335 | } 336 | 337 | public var delegate: KSICCoordinatorDelegate? 338 | public let model: [KSImageCarouselDisplayable] 339 | public let placeholderImage: UIImage? 340 | public var shouldShowActivityIndicator = true 341 | public var activityIndicatorStyle: UIActivityIndicatorView.Style = .gray 342 | public internal(set) var carousel: KSICScrollerViewController? 343 | 344 | public private(set) var currentPage: Int { 345 | didSet { 346 | 347 | // Note: Everytime current page being set, we will update carousel's viewModel (which will update images in carousel) and scroll carousel to subview that user should see 348 | 349 | // Update view model of carousel 350 | carousel?.viewModel = carouselViewModel 351 | 352 | // Scroll carousel (without animation) to subview that user should see 353 | swapCarouselSubview() 354 | } 355 | } 356 | 357 | public var carouselViewModel: [KSImageCarouselDisplayable] { 358 | if model.count == 1 { 359 | // When model only have 1 element, next page & previous page is same as current page 360 | return [model[currentPage], 361 | model[currentPage], 362 | model[currentPage]] 363 | } else { 364 | 365 | if isFirstPage { 366 | // When at first page, previous page should be last page 367 | return [model[lastPage], 368 | model[currentPage], 369 | model[currentPage + 1]] 370 | } else if isLastPage { 371 | // When at last page, next page should be first page 372 | return [model[currentPage - 1], 373 | model[currentPage], 374 | model[firstPage]] 375 | } else { 376 | return [model[currentPage - 1], 377 | model[currentPage], 378 | model[currentPage + 1]] 379 | } 380 | } 381 | } 382 | 383 | 384 | /// Timer object needed for auto scrolling 385 | lazy private var autoScrollTimer: Timer = Timer() 386 | 387 | /// Initializer 388 | /// 389 | /// - Parameters: 390 | /// - model: Model for carousel 391 | /// - placeholderImage: Placeholder image to show when image being download 392 | /// - initialPage: Page to display when carousel first shown 393 | /// - Throws: emptyModel, pageOutOfRange 394 | public init(with model: [KSImageCarouselDisplayable], placeholderImage: UIImage?, initialPage: Int) throws { 395 | 396 | // Make sure model is not empty 397 | guard model.count > 0 else { 398 | throw CoordinatorError.emptyModel 399 | } 400 | 401 | self.model = model 402 | self.placeholderImage = placeholderImage 403 | self.currentPage = initialPage 404 | 405 | // Make sure initial page is in range 406 | guard isPageInRange(initialPage) else { 407 | throw CoordinatorError.pageOutOfRange 408 | } 409 | } 410 | 411 | /// Initializer 412 | /// 413 | /// - Parameters: 414 | /// - model: Model for carousel 415 | /// - initialPage: Page to display when carousel first shown 416 | /// - Throws: emptyModel, pageOutOfRange 417 | public convenience init(with model: [KSImageCarouselDisplayable], initialPage: Int) throws { 418 | try self.init(with: model, placeholderImage: nil, initialPage: initialPage) 419 | } 420 | 421 | // MARK: KSICCoordinator conformation 422 | public func showCarousel(inside container: UIView, of parentViewController: UIViewController) { 423 | carousel = KSICScrollerViewController(withViewModel: carouselViewModel, placeholderImage: placeholderImage, delegate: self) 424 | add(carousel!, to: container, of: parentViewController) 425 | } 426 | 427 | public func scrollToNextPage() { 428 | // Simulate action when user scroll carousel with finger. This will trigger scrollerViewControllerDidGotoNextPage and everything will run just the same like a real user interaction 429 | carousel?.scrollToLastSubview(true) 430 | } 431 | 432 | public func scrollToPreviousPage() { 433 | // Simulate action when user scroll carousel with finger. This will trigger scrollerViewControllerDidGotoPreviousPage and everything will run just the same like a real user interaction 434 | carousel?.scrollToFirstSubview(true) 435 | } 436 | 437 | // MARK: Public functions 438 | public func startAutoScroll(withDirection direction: KSICAutoScrollDirection, interval: TimeInterval) { 439 | switch direction { 440 | case .left: 441 | autoScrollTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: { [unowned self] (timer) in 442 | self.scrollToNextPage() 443 | }) 444 | case .right: 445 | autoScrollTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: { [unowned self] (timer) in 446 | self.scrollToPreviousPage() 447 | }) 448 | } 449 | } 450 | 451 | public func stopAutoScroll() { 452 | autoScrollTimer.invalidate() 453 | } 454 | 455 | 456 | // MARK: Utilities functions 457 | 458 | /// +1 to page number - calling this will update currentPage -> update caoursel.viewModel -> update images in carousel -> scroll carousel to desire subview 459 | func increasePageByOne() { 460 | if currentPage == lastPage { 461 | try! setPage(firstPage) 462 | } else { 463 | let newPage = currentPage + 1 464 | try! setPage(newPage) 465 | } 466 | } 467 | 468 | /// -1 to page number - calling this will update currentPage -> update caoursel.viewModel -> update images in carousel -> scroll carousel to desire subview 469 | func reducePageByOne() { 470 | if currentPage == firstPage { 471 | try! setPage(lastPage) 472 | } else { 473 | let newPage = currentPage - 1 474 | try! setPage(newPage) 475 | } 476 | } 477 | 478 | /// Set current page number 479 | /// 480 | /// - Parameter p: page number 481 | /// - Throws: pageOutOfRange 482 | private func setPage(_ p: Int) throws { 483 | 484 | // Make sure page is between first page and last page 485 | guard isPageInRange(p) else { 486 | // throw exception 487 | throw CoordinatorError.pageOutOfRange 488 | } 489 | 490 | currentPage = p 491 | } 492 | 493 | /// Base on current page, scroll carousel (without animation) to subview that should be visible to user 494 | fileprivate func swapCarouselSubview() { 495 | // Center page should always be the current visible page 496 | carousel?.scrollToCenterSubview(false) 497 | } 498 | } 499 | 500 | // MARK: KSICScrollerViewControllerDelegate 501 | extension KSICCoordinator where Self: KSICInfiniteCoordinator { 502 | public func scrollerViewControllerDidFinishLayoutSubviews(_ viewController: KSICScrollerViewController) { 503 | // Scroll carousel (without animation) to subview that user should see 504 | swapCarouselSubview() 505 | } 506 | 507 | public func scrollerViewControllerDidGotoNextPage(_ viewController: KSICScrollerViewController) { 508 | // Calling increasePageByOne() will update currentPage -> update caoursel.viewModel -> update images in carousel -> scroll carousel to desire subview 509 | increasePageByOne() 510 | } 511 | 512 | public func scrollerViewControllerDidGotoPreviousPage(_ viewController: KSICScrollerViewController) { 513 | // Calling reducePageByOne() will update currentPage -> update caoursel.viewModel -> update images in carousel -> scroll carousel to desire subview 514 | reducePageByOne() 515 | } 516 | 517 | public func scrollerViewControllerDidTappedImageView(at index: Int, viewController: KSICScrollerViewController) { 518 | delegate?.carouselDidTappedImage(at: currentPage, coordinator: self) 519 | } 520 | 521 | public func scrollerViewControllerShouldShowActivityIndicator() -> Bool { 522 | return shouldShowActivityIndicator 523 | } 524 | 525 | public func scrollerViewControllerShowActivityIndicatorStyle() -> UIActivityIndicatorView.Style { 526 | return activityIndicatorStyle 527 | } 528 | } 529 | 530 | -------------------------------------------------------------------------------- /KSImageCarousel/KSImageCarousel.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F96BF5AF1EDFF81C00E0E9E6 /* KSICScrollerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96BF5AE1EDFF81C00E0E9E6 /* KSICScrollerViewController.swift */; }; 11 | F96BF5B11EE026ED00E0E9E6 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96BF5B01EE026ED00E0E9E6 /* Extensions.swift */; }; 12 | F99C319D1EDD7AEE00019996 /* KSImageCarouselDisplayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F99C319C1EDD7AEE00019996 /* KSImageCarouselDisplayable.swift */; }; 13 | F99C31A01EDD7EC400019996 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F99C319F1EDD7EC400019996 /* Assets.xcassets */; }; 14 | F99C31A11EDD7EC400019996 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F99C319F1EDD7EC400019996 /* Assets.xcassets */; }; 15 | F99DC4D71F2311DD00F2093B /* KSICImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F99DC4D61F2311DD00F2093B /* KSICImageView.swift */; }; 16 | F9A678DC1EDAD46D00AE2E0A /* KSICCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A678DB1EDAD46D00AE2E0A /* KSICCoordinator.swift */; }; 17 | F9A678E01EDAD96600AE2E0A /* KSICError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A678DF1EDAD96600AE2E0A /* KSICError.swift */; }; 18 | F9D158361ED5B8D200F4EAD7 /* KSImageCarousel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9D1582C1ED5B8D200F4EAD7 /* KSImageCarousel.framework */; }; 19 | F9D1583B1ED5B8D200F4EAD7 /* KSImageCarouselTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D1583A1ED5B8D200F4EAD7 /* KSImageCarouselTests.swift */; }; 20 | F9D1583D1ED5B8D200F4EAD7 /* KSImageCarousel.h in Headers */ = {isa = PBXBuildFile; fileRef = F9D1582F1ED5B8D200F4EAD7 /* KSImageCarousel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 21 | F9D4A6421F20656100CBEE07 /* KSICImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D4A6411F20656100CBEE07 /* KSICImageCache.swift */; }; 22 | F9F4812B1F248E2500F647CF /* KSICFakeScrollerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9F4812A1F248E2500F647CF /* KSICFakeScrollerViewController.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | F9D158371ED5B8D200F4EAD7 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = F9D158231ED5B8D200F4EAD7 /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = F9D1582B1ED5B8D200F4EAD7; 31 | remoteInfo = KSImageCarousel; 32 | }; 33 | /* End PBXContainerItemProxy section */ 34 | 35 | /* Begin PBXFileReference section */ 36 | F96BF5AE1EDFF81C00E0E9E6 /* KSICScrollerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSICScrollerViewController.swift; sourceTree = ""; }; 37 | F96BF5B01EE026ED00E0E9E6 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 38 | F99C319C1EDD7AEE00019996 /* KSImageCarouselDisplayable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSImageCarouselDisplayable.swift; sourceTree = ""; }; 39 | F99C319F1EDD7EC400019996 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | F99DC4D61F2311DD00F2093B /* KSICImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSICImageView.swift; sourceTree = ""; }; 41 | F9A678DB1EDAD46D00AE2E0A /* KSICCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSICCoordinator.swift; sourceTree = ""; }; 42 | F9A678DF1EDAD96600AE2E0A /* KSICError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSICError.swift; sourceTree = ""; }; 43 | F9D1582C1ED5B8D200F4EAD7 /* KSImageCarousel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KSImageCarousel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | F9D1582F1ED5B8D200F4EAD7 /* KSImageCarousel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KSImageCarousel.h; sourceTree = ""; }; 45 | F9D158301ED5B8D200F4EAD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | F9D158351ED5B8D200F4EAD7 /* KSImageCarouselTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KSImageCarouselTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | F9D1583A1ED5B8D200F4EAD7 /* KSImageCarouselTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSImageCarouselTests.swift; sourceTree = ""; }; 48 | F9D1583C1ED5B8D200F4EAD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | F9D4A6411F20656100CBEE07 /* KSICImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSICImageCache.swift; sourceTree = ""; }; 50 | F9F4812A1F248E2500F647CF /* KSICFakeScrollerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSICFakeScrollerViewController.swift; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | F9D158281ED5B8D200F4EAD7 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | F9D158321ED5B8D200F4EAD7 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | F9D158361ED5B8D200F4EAD7 /* KSImageCarousel.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | A6F57FCBD9D383AB52CD2933 /* Frameworks */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | ); 76 | name = Frameworks; 77 | sourceTree = ""; 78 | }; 79 | F9D158221ED5B8D200F4EAD7 = { 80 | isa = PBXGroup; 81 | children = ( 82 | F9D1582E1ED5B8D200F4EAD7 /* KSImageCarousel */, 83 | F9D158391ED5B8D200F4EAD7 /* KSImageCarouselTests */, 84 | F9D1582D1ED5B8D200F4EAD7 /* Products */, 85 | A6F57FCBD9D383AB52CD2933 /* Frameworks */, 86 | ); 87 | sourceTree = ""; 88 | }; 89 | F9D1582D1ED5B8D200F4EAD7 /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | F9D1582C1ED5B8D200F4EAD7 /* KSImageCarousel.framework */, 93 | F9D158351ED5B8D200F4EAD7 /* KSImageCarouselTests.xctest */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | F9D1582E1ED5B8D200F4EAD7 /* KSImageCarousel */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | F9D158771ED5BEDD00F4EAD7 /* Sources */, 102 | F9D1582F1ED5B8D200F4EAD7 /* KSImageCarousel.h */, 103 | F9D158301ED5B8D200F4EAD7 /* Info.plist */, 104 | ); 105 | path = KSImageCarousel; 106 | sourceTree = ""; 107 | }; 108 | F9D158391ED5B8D200F4EAD7 /* KSImageCarouselTests */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | F99C319F1EDD7EC400019996 /* Assets.xcassets */, 112 | F9D1583A1ED5B8D200F4EAD7 /* KSImageCarouselTests.swift */, 113 | F9F4812A1F248E2500F647CF /* KSICFakeScrollerViewController.swift */, 114 | F9D1583C1ED5B8D200F4EAD7 /* Info.plist */, 115 | ); 116 | path = KSImageCarouselTests; 117 | sourceTree = ""; 118 | }; 119 | F9D158771ED5BEDD00F4EAD7 /* Sources */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | F9A678DF1EDAD96600AE2E0A /* KSICError.swift */, 123 | F9A678DB1EDAD46D00AE2E0A /* KSICCoordinator.swift */, 124 | F99C319C1EDD7AEE00019996 /* KSImageCarouselDisplayable.swift */, 125 | F96BF5AE1EDFF81C00E0E9E6 /* KSICScrollerViewController.swift */, 126 | F96BF5B01EE026ED00E0E9E6 /* Extensions.swift */, 127 | F9D4A6411F20656100CBEE07 /* KSICImageCache.swift */, 128 | F99DC4D61F2311DD00F2093B /* KSICImageView.swift */, 129 | ); 130 | path = Sources; 131 | sourceTree = ""; 132 | }; 133 | /* End PBXGroup section */ 134 | 135 | /* Begin PBXHeadersBuildPhase section */ 136 | F9D158291ED5B8D200F4EAD7 /* Headers */ = { 137 | isa = PBXHeadersBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | F9D1583D1ED5B8D200F4EAD7 /* KSImageCarousel.h in Headers */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXHeadersBuildPhase section */ 145 | 146 | /* Begin PBXNativeTarget section */ 147 | F9D1582B1ED5B8D200F4EAD7 /* KSImageCarousel */ = { 148 | isa = PBXNativeTarget; 149 | buildConfigurationList = F9D158401ED5B8D200F4EAD7 /* Build configuration list for PBXNativeTarget "KSImageCarousel" */; 150 | buildPhases = ( 151 | F9D158271ED5B8D200F4EAD7 /* Sources */, 152 | F9D158281ED5B8D200F4EAD7 /* Frameworks */, 153 | F9D158291ED5B8D200F4EAD7 /* Headers */, 154 | F9D1582A1ED5B8D200F4EAD7 /* Resources */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | ); 160 | name = KSImageCarousel; 161 | productName = KSImageCarousel; 162 | productReference = F9D1582C1ED5B8D200F4EAD7 /* KSImageCarousel.framework */; 163 | productType = "com.apple.product-type.framework"; 164 | }; 165 | F9D158341ED5B8D200F4EAD7 /* KSImageCarouselTests */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = F9D158431ED5B8D200F4EAD7 /* Build configuration list for PBXNativeTarget "KSImageCarouselTests" */; 168 | buildPhases = ( 169 | F9D158311ED5B8D200F4EAD7 /* Sources */, 170 | F9D158321ED5B8D200F4EAD7 /* Frameworks */, 171 | F9D158331ED5B8D200F4EAD7 /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | F9D158381ED5B8D200F4EAD7 /* PBXTargetDependency */, 177 | ); 178 | name = KSImageCarouselTests; 179 | productName = KSImageCarouselTests; 180 | productReference = F9D158351ED5B8D200F4EAD7 /* KSImageCarouselTests.xctest */; 181 | productType = "com.apple.product-type.bundle.unit-test"; 182 | }; 183 | /* End PBXNativeTarget section */ 184 | 185 | /* Begin PBXProject section */ 186 | F9D158231ED5B8D200F4EAD7 /* Project object */ = { 187 | isa = PBXProject; 188 | attributes = { 189 | LastSwiftUpdateCheck = 0830; 190 | LastUpgradeCheck = 1010; 191 | ORGANIZATIONNAME = "Lee Kah Seng"; 192 | TargetAttributes = { 193 | F9D1582B1ED5B8D200F4EAD7 = { 194 | CreatedOnToolsVersion = 8.3.2; 195 | DevelopmentTeam = SM42V25455; 196 | LastSwiftMigration = 0900; 197 | ProvisioningStyle = Automatic; 198 | }; 199 | F9D158341ED5B8D200F4EAD7 = { 200 | CreatedOnToolsVersion = 8.3.2; 201 | DevelopmentTeam = SM42V25455; 202 | ProvisioningStyle = Automatic; 203 | }; 204 | }; 205 | }; 206 | buildConfigurationList = F9D158261ED5B8D200F4EAD7 /* Build configuration list for PBXProject "KSImageCarousel" */; 207 | compatibilityVersion = "Xcode 3.2"; 208 | developmentRegion = English; 209 | hasScannedForEncodings = 0; 210 | knownRegions = ( 211 | en, 212 | ); 213 | mainGroup = F9D158221ED5B8D200F4EAD7; 214 | productRefGroup = F9D1582D1ED5B8D200F4EAD7 /* Products */; 215 | projectDirPath = ""; 216 | projectRoot = ""; 217 | targets = ( 218 | F9D1582B1ED5B8D200F4EAD7 /* KSImageCarousel */, 219 | F9D158341ED5B8D200F4EAD7 /* KSImageCarouselTests */, 220 | ); 221 | }; 222 | /* End PBXProject section */ 223 | 224 | /* Begin PBXResourcesBuildPhase section */ 225 | F9D1582A1ED5B8D200F4EAD7 /* Resources */ = { 226 | isa = PBXResourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | F99C31A01EDD7EC400019996 /* Assets.xcassets in Resources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | F9D158331ED5B8D200F4EAD7 /* Resources */ = { 234 | isa = PBXResourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | F99C31A11EDD7EC400019996 /* Assets.xcassets in Resources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | /* End PBXResourcesBuildPhase section */ 242 | 243 | /* Begin PBXSourcesBuildPhase section */ 244 | F9D158271ED5B8D200F4EAD7 /* Sources */ = { 245 | isa = PBXSourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | F9D4A6421F20656100CBEE07 /* KSICImageCache.swift in Sources */, 249 | F96BF5B11EE026ED00E0E9E6 /* Extensions.swift in Sources */, 250 | F99C319D1EDD7AEE00019996 /* KSImageCarouselDisplayable.swift in Sources */, 251 | F96BF5AF1EDFF81C00E0E9E6 /* KSICScrollerViewController.swift in Sources */, 252 | F99DC4D71F2311DD00F2093B /* KSICImageView.swift in Sources */, 253 | F9A678DC1EDAD46D00AE2E0A /* KSICCoordinator.swift in Sources */, 254 | F9A678E01EDAD96600AE2E0A /* KSICError.swift in Sources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | F9D158311ED5B8D200F4EAD7 /* Sources */ = { 259 | isa = PBXSourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | F9D1583B1ED5B8D200F4EAD7 /* KSImageCarouselTests.swift in Sources */, 263 | F9F4812B1F248E2500F647CF /* KSICFakeScrollerViewController.swift in Sources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | /* End PBXSourcesBuildPhase section */ 268 | 269 | /* Begin PBXTargetDependency section */ 270 | F9D158381ED5B8D200F4EAD7 /* PBXTargetDependency */ = { 271 | isa = PBXTargetDependency; 272 | target = F9D1582B1ED5B8D200F4EAD7 /* KSImageCarousel */; 273 | targetProxy = F9D158371ED5B8D200F4EAD7 /* PBXContainerItemProxy */; 274 | }; 275 | /* End PBXTargetDependency section */ 276 | 277 | /* Begin XCBuildConfiguration section */ 278 | F9D1583E1ED5B8D200F4EAD7 /* Debug */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ALWAYS_SEARCH_USER_PATHS = NO; 282 | CLANG_ANALYZER_NONNULL = YES; 283 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 289 | CLANG_WARN_BOOL_CONVERSION = YES; 290 | CLANG_WARN_COMMA = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 293 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 294 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 295 | CLANG_WARN_EMPTY_BODY = YES; 296 | CLANG_WARN_ENUM_CONVERSION = YES; 297 | CLANG_WARN_INFINITE_RECURSION = YES; 298 | CLANG_WARN_INT_CONVERSION = YES; 299 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 300 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 301 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 302 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 303 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 304 | CLANG_WARN_STRICT_PROTOTYPES = YES; 305 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 306 | CLANG_WARN_UNREACHABLE_CODE = YES; 307 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 308 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 309 | COPY_PHASE_STRIP = NO; 310 | CURRENT_PROJECT_VERSION = 1; 311 | DEBUG_INFORMATION_FORMAT = dwarf; 312 | ENABLE_STRICT_OBJC_MSGSEND = YES; 313 | ENABLE_TESTABILITY = YES; 314 | GCC_C_LANGUAGE_STANDARD = gnu99; 315 | GCC_DYNAMIC_NO_PIC = NO; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_OPTIMIZATION_LEVEL = 0; 318 | GCC_PREPROCESSOR_DEFINITIONS = ( 319 | "DEBUG=1", 320 | "$(inherited)", 321 | ); 322 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 323 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 324 | GCC_WARN_UNDECLARED_SELECTOR = YES; 325 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 326 | GCC_WARN_UNUSED_FUNCTION = YES; 327 | GCC_WARN_UNUSED_VARIABLE = YES; 328 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 329 | MTL_ENABLE_DEBUG_INFO = YES; 330 | ONLY_ACTIVE_ARCH = YES; 331 | SDKROOT = iphoneos; 332 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 333 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 334 | TARGETED_DEVICE_FAMILY = "1,2"; 335 | VERSIONING_SYSTEM = "apple-generic"; 336 | VERSION_INFO_PREFIX = ""; 337 | }; 338 | name = Debug; 339 | }; 340 | F9D1583F1ED5B8D200F4EAD7 /* Release */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ALWAYS_SEARCH_USER_PATHS = NO; 344 | CLANG_ANALYZER_NONNULL = YES; 345 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 346 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 347 | CLANG_CXX_LIBRARY = "libc++"; 348 | CLANG_ENABLE_MODULES = YES; 349 | CLANG_ENABLE_OBJC_ARC = YES; 350 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 351 | CLANG_WARN_BOOL_CONVERSION = YES; 352 | CLANG_WARN_COMMA = YES; 353 | CLANG_WARN_CONSTANT_CONVERSION = YES; 354 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 355 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 356 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 357 | CLANG_WARN_EMPTY_BODY = YES; 358 | CLANG_WARN_ENUM_CONVERSION = YES; 359 | CLANG_WARN_INFINITE_RECURSION = YES; 360 | CLANG_WARN_INT_CONVERSION = YES; 361 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 362 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 363 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 364 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 365 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 366 | CLANG_WARN_STRICT_PROTOTYPES = YES; 367 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 368 | CLANG_WARN_UNREACHABLE_CODE = YES; 369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 370 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 371 | COPY_PHASE_STRIP = NO; 372 | CURRENT_PROJECT_VERSION = 1; 373 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 374 | ENABLE_NS_ASSERTIONS = NO; 375 | ENABLE_STRICT_OBJC_MSGSEND = YES; 376 | GCC_C_LANGUAGE_STANDARD = gnu99; 377 | GCC_NO_COMMON_BLOCKS = YES; 378 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 379 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 380 | GCC_WARN_UNDECLARED_SELECTOR = YES; 381 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 382 | GCC_WARN_UNUSED_FUNCTION = YES; 383 | GCC_WARN_UNUSED_VARIABLE = YES; 384 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 385 | MTL_ENABLE_DEBUG_INFO = NO; 386 | SDKROOT = iphoneos; 387 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | VALIDATE_PRODUCT = YES; 390 | VERSIONING_SYSTEM = "apple-generic"; 391 | VERSION_INFO_PREFIX = ""; 392 | }; 393 | name = Release; 394 | }; 395 | F9D158411ED5B8D200F4EAD7 /* Debug */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | CLANG_ENABLE_MODULES = YES; 399 | CODE_SIGN_IDENTITY = ""; 400 | DEFINES_MODULE = YES; 401 | DEVELOPMENT_TEAM = SM42V25455; 402 | DYLIB_COMPATIBILITY_VERSION = 1; 403 | DYLIB_CURRENT_VERSION = 1; 404 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 405 | INFOPLIST_FILE = KSImageCarousel/Info.plist; 406 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 407 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 408 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 409 | PRODUCT_BUNDLE_IDENTIFIER = com.LeeKahSeng.KSImageCarousel; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | SKIP_INSTALL = YES; 412 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 413 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 414 | SWIFT_VERSION = 4.2; 415 | }; 416 | name = Debug; 417 | }; 418 | F9D158421ED5B8D200F4EAD7 /* Release */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | CLANG_ENABLE_MODULES = YES; 422 | CODE_SIGN_IDENTITY = ""; 423 | DEFINES_MODULE = YES; 424 | DEVELOPMENT_TEAM = SM42V25455; 425 | DYLIB_COMPATIBILITY_VERSION = 1; 426 | DYLIB_CURRENT_VERSION = 1; 427 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 428 | INFOPLIST_FILE = KSImageCarousel/Info.plist; 429 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 430 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 431 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 432 | PRODUCT_BUNDLE_IDENTIFIER = com.LeeKahSeng.KSImageCarousel; 433 | PRODUCT_NAME = "$(TARGET_NAME)"; 434 | SKIP_INSTALL = YES; 435 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 436 | SWIFT_VERSION = 4.2; 437 | }; 438 | name = Release; 439 | }; 440 | F9D158441ED5B8D200F4EAD7 /* Debug */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 444 | DEVELOPMENT_TEAM = SM42V25455; 445 | INFOPLIST_FILE = KSImageCarouselTests/Info.plist; 446 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 447 | PRODUCT_BUNDLE_IDENTIFIER = com.LeeKahSeng.KSImageCarouselTests; 448 | PRODUCT_NAME = "$(TARGET_NAME)"; 449 | SWIFT_VERSION = 3.0; 450 | }; 451 | name = Debug; 452 | }; 453 | F9D158451ED5B8D200F4EAD7 /* Release */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 457 | DEVELOPMENT_TEAM = SM42V25455; 458 | INFOPLIST_FILE = KSImageCarouselTests/Info.plist; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 460 | PRODUCT_BUNDLE_IDENTIFIER = com.LeeKahSeng.KSImageCarouselTests; 461 | PRODUCT_NAME = "$(TARGET_NAME)"; 462 | SWIFT_VERSION = 3.0; 463 | }; 464 | name = Release; 465 | }; 466 | /* End XCBuildConfiguration section */ 467 | 468 | /* Begin XCConfigurationList section */ 469 | F9D158261ED5B8D200F4EAD7 /* Build configuration list for PBXProject "KSImageCarousel" */ = { 470 | isa = XCConfigurationList; 471 | buildConfigurations = ( 472 | F9D1583E1ED5B8D200F4EAD7 /* Debug */, 473 | F9D1583F1ED5B8D200F4EAD7 /* Release */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | F9D158401ED5B8D200F4EAD7 /* Build configuration list for PBXNativeTarget "KSImageCarousel" */ = { 479 | isa = XCConfigurationList; 480 | buildConfigurations = ( 481 | F9D158411ED5B8D200F4EAD7 /* Debug */, 482 | F9D158421ED5B8D200F4EAD7 /* Release */, 483 | ); 484 | defaultConfigurationIsVisible = 0; 485 | defaultConfigurationName = Release; 486 | }; 487 | F9D158431ED5B8D200F4EAD7 /* Build configuration list for PBXNativeTarget "KSImageCarouselTests" */ = { 488 | isa = XCConfigurationList; 489 | buildConfigurations = ( 490 | F9D158441ED5B8D200F4EAD7 /* Debug */, 491 | F9D158451ED5B8D200F4EAD7 /* Release */, 492 | ); 493 | defaultConfigurationIsVisible = 0; 494 | defaultConfigurationName = Release; 495 | }; 496 | /* End XCConfigurationList section */ 497 | }; 498 | rootObject = F9D158231ED5B8D200F4EAD7 /* Project object */; 499 | } 500 | --------------------------------------------------------------------------------