├── .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 | [](https://github.com/Carthage/Carthage)
4 | [](http://cocoapods.org/pods/KSFacebookButton)
5 | [](http://cocoadocs.org/docsets/KSFacebookButton)
6 | [](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 | 
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 |
--------------------------------------------------------------------------------