├── .swift-version ├── ImageSlideshow ├── Assets │ ├── .gitkeep │ ├── ic_cross_white@2x.png │ └── ic_cross_white@3x.png └── Classes │ ├── .gitkeep │ ├── Core │ ├── Bundle+Module.swift │ ├── SwiftSupport.swift │ ├── UIImage+AspectFit.swift │ ├── UIImageView+Tools.swift │ ├── ActivityIndicator.swift │ ├── PageIndicator.swift │ ├── InputSource.swift │ ├── PageIndicatorPosition.swift │ ├── FullScreenSlideshowViewController.swift │ ├── ImageSlideshowItem.swift │ ├── ZoomAnimatedTransitioning.swift │ └── ImageSlideshow.swift │ └── InputSources │ ├── ParseSource.swift │ ├── SDWebImageSource.swift │ ├── AFURLSource.swift │ ├── AlamofireSource.swift │ ├── AlamofireLegacySource.swift │ └── KingfisherSource.swift ├── _Pods.xcodeproj ├── Example ├── ImageSlideshow │ ├── Images.xcassets │ │ ├── Contents.json │ │ ├── img1.imageset │ │ │ ├── img1.png │ │ │ └── Contents.json │ │ ├── img2.imageset │ │ │ ├── img2.png │ │ │ └── Contents.json │ │ ├── img3.imageset │ │ │ ├── img3.png │ │ │ └── Contents.json │ │ ├── img4.imageset │ │ │ ├── img4.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── AppDelegate.swift │ ├── TableViewController.swift │ ├── ViewController.swift │ └── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard ├── ImageSlideshow.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ ├── ImageSlideshow_framework.xcscheme │ │ │ └── ImageSlideshow-Example.xcscheme │ └── project.pbxproj ├── ImageSlideshow.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Podfile ├── ImageSlideshow_framework │ ├── ImageSlideshow_framework.h │ └── Info.plist ├── Tests │ ├── Info.plist │ └── Tests.swift └── Podfile.lock ├── .travis.yml ├── .gitignore ├── LICENSE ├── Package.resolved ├── Package.swift ├── ImageSlideshow.podspec ├── CHANGELOG.md └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5.2 2 | -------------------------------------------------------------------------------- /ImageSlideshow/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ImageSlideshow/Assets/ic_cross_white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zvonicek/ImageSlideshow/HEAD/ImageSlideshow/Assets/ic_cross_white@2x.png -------------------------------------------------------------------------------- /ImageSlideshow/Assets/ic_cross_white@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zvonicek/ImageSlideshow/HEAD/ImageSlideshow/Assets/ic_cross_white@3x.png -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.xcassets/img1.imageset/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zvonicek/ImageSlideshow/HEAD/Example/ImageSlideshow/Images.xcassets/img1.imageset/img1.png -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.xcassets/img2.imageset/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zvonicek/ImageSlideshow/HEAD/Example/ImageSlideshow/Images.xcassets/img2.imageset/img2.png -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.xcassets/img3.imageset/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zvonicek/ImageSlideshow/HEAD/Example/ImageSlideshow/Images.xcassets/img3.imageset/img3.png -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.xcassets/img4.imageset/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zvonicek/ImageSlideshow/HEAD/Example/ImageSlideshow/Images.xcassets/img4.imageset/img4.png -------------------------------------------------------------------------------- /Example/ImageSlideshow.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/ImageSlideshow.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/ImageSlideshow.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/Bundle+Module.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+Module.swift 3 | // ImageSlideshow 4 | // 5 | // Created by woxtu on 20/11/21. 6 | // 7 | 8 | import Foundation 9 | 10 | #if !SWIFT_PACKAGE 11 | extension Bundle { 12 | static var module: Bundle = { 13 | return Bundle(for: ImageSlideshow.self) 14 | }() 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | use_frameworks! 3 | 4 | target 'ImageSlideshow_Example' do 5 | pod "ImageSlideshow", :path => "../" 6 | pod "ImageSlideshow/AFURL", :path => "../" 7 | pod "ImageSlideshow/Alamofire", :path => "../" 8 | pod "ImageSlideshow/SDWebImage", :path => "../" 9 | pod "ImageSlideshow/Kingfisher", :path => "../" 10 | end 11 | -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.xcassets/img1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "img1.png" 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 | } -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.xcassets/img2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "img2.png" 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 | } -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.xcassets/img3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "img3.png" 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 | } -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.xcassets/img4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "img4.png" 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 | } -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/SwiftSupport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftSupport.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Pierluigi Cifani on 30/07/2018. 6 | // 7 | 8 | import UIKit 9 | 10 | #if swift(>=4.2) 11 | public typealias UIViewContentMode = UIView.ContentMode 12 | public typealias UIActivityIndicatorViewStyle = UIActivityIndicatorView.Style 13 | typealias UIControlState = UIControl.State 14 | typealias UIViewAnimationOptions = UIView.AnimationOptions 15 | typealias UIControlEvents = UIControl.Event 16 | typealias UIViewAutoresizing = UIView.AutoresizingMask 17 | #else 18 | #endif 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | cache: cocoapods 8 | podfile: Example/Podfile 9 | before_install: 10 | - gem install cocoapods # Since Travis is not always on latest version 11 | - pod install --project-directory=Example 12 | install: 13 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 14 | script: 15 | - set -e && xcodebuild -workspace -workspace Example/ImageSlideshow.xcworkspace -scheme ImageSlideshow-Example -sdk iphonesimulator8.1 test | xcpretty -c 16 | - pod lib lint --quick 17 | -------------------------------------------------------------------------------- /Example/ImageSlideshow_framework/ImageSlideshow_framework.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageSlideshow_framework.h 3 | // ImageSlideshow_framework 4 | // 5 | // Created by Petr Zvoníček on 25.09.16. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ImageSlideshow_framework. 12 | FOUNDATION_EXPORT double ImageSlideshow_frameworkVersionNumber; 13 | 14 | //! Project version string for ImageSlideshow_framework. 15 | FOUNDATION_EXPORT const unsigned char ImageSlideshow_frameworkVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | 33 | Pods/ 34 | 35 | # Swift Package Manager 36 | .build/ 37 | -------------------------------------------------------------------------------- /Example/Tests/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import XCTest 3 | 4 | class Tests: XCTestCase { 5 | 6 | override func setUp() { 7 | super.setUp() 8 | // Put setup code here. This method is called before the invocation of each test method in the class. 9 | } 10 | 11 | override func tearDown() { 12 | // Put teardown code here. This method is called after the invocation of each test method in the class. 13 | super.tearDown() 14 | } 15 | 16 | func testExample() { 17 | // This is an example of a functional test case. 18 | XCTAssert(true, "Pass") 19 | } 20 | 21 | func testPerformanceExample() { 22 | // This is an example of a performance test case. 23 | self.measure { 24 | // Put the code you want to measure the time of here. 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Example/ImageSlideshow_framework/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 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/UIImage+AspectFit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+AspectFit.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 31.08.15. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | 13 | func tgr_aspectFitRectForSize(_ size: CGSize) -> CGRect { 14 | let targetAspect: CGFloat = size.width / size.height 15 | let sourceAspect: CGFloat = self.size.width / self.size.height 16 | var rect: CGRect = CGRect.zero 17 | 18 | if targetAspect > sourceAspect { 19 | rect.size.height = size.height 20 | rect.size.width = ceil(rect.size.height * sourceAspect) 21 | rect.origin.x = ceil((size.width - rect.size.width) * 0.5) 22 | } else { 23 | rect.size.width = size.width 24 | rect.size.height = ceil(rect.size.width / sourceAspect) 25 | rect.origin.y = ceil((size.height - rect.size.height) * 0.5) 26 | } 27 | 28 | return rect 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Example/ImageSlideshow/Images.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 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Petr Zvoníček 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/InputSources/ParseSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseImageSource.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Jaime Agudo Lopez on 14/01/2017. 6 | // 7 | import Parse 8 | 9 | /// Input Source to image using Parse 10 | public class ParseSource: NSObject, InputSource { 11 | var file: PFFileObject 12 | var placeholder: UIImage? 13 | 14 | /// Initializes a new source with URL and optionally a placeholder 15 | /// - parameter url: a url to be loaded 16 | /// - parameter placeholder: a placeholder used before image is loaded 17 | public init(file: PFFileObject, placeholder: UIImage? = nil) { 18 | self.file = file 19 | self.placeholder = placeholder 20 | super.init() 21 | } 22 | 23 | @objc public func load(to imageView: UIImageView, with callback: @escaping (UIImage?) -> Void) { 24 | imageView.image = self.placeholder 25 | 26 | self.file.getDataInBackground {(data: Data?, _: Error?) in 27 | if let data = data, let image = UIImage(data: data) { 28 | imageView.image = image 29 | callback(image) 30 | } else { 31 | callback(nil) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/UIImageView+Tools.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+Tools.swift 3 | // Pods 4 | // 5 | // Created by Aleš Kocur on 20/04/16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImageView { 12 | 13 | func aspectToFitFrame() -> CGRect { 14 | 15 | guard let image = image else { 16 | assertionFailure("No image found!") 17 | return CGRect.zero 18 | } 19 | 20 | let imageRatio: CGFloat = image.size.width / image.size.height 21 | let viewRatio: CGFloat = frame.size.width / frame.size.height 22 | 23 | if imageRatio < viewRatio { 24 | let scale: CGFloat = frame.size.height / image.size.height 25 | let width: CGFloat = scale * image.size.width 26 | let topLeftX: CGFloat = (frame.size.width - width) * 0.5 27 | return CGRect(x: topLeftX, y: 0, width: width, height: frame.size.height) 28 | } else { 29 | let scale: CGFloat = frame.size.width / image.size.width 30 | let height: CGFloat = scale * image.size.height 31 | let topLeftY: CGFloat = (frame.size.height - height) * 0.5 32 | return CGRect(x: 0, y: topLeftY, width: frame.size.width, height: height) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Alamofire", 6 | "repositoryURL": "https://github.com/Alamofire/Alamofire.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "fca036f7aeca07124067cb6e0c12b0ad6359e3d4", 10 | "version": "5.1.0" 11 | } 12 | }, 13 | { 14 | "package": "AlamofireImage", 15 | "repositoryURL": "https://github.com/Alamofire/AlamofireImage.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "3e8edbeb75227f8542aa87f90240cf0424d6362f", 19 | "version": "4.1.0" 20 | } 21 | }, 22 | { 23 | "package": "Kingfisher", 24 | "repositoryURL": "https://github.com/onevcat/Kingfisher.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "349ed06467a6f8a4939bcb83db301542bc84eac9", 28 | "version": "5.13.4" 29 | } 30 | }, 31 | { 32 | "package": "SDWebImage", 33 | "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "74ae21337c415bb542eb901803f3e84799265c34", 37 | "version": "5.7.2" 38 | } 39 | } 40 | ] 41 | }, 42 | "version": 1 43 | } 44 | -------------------------------------------------------------------------------- /Example/ImageSlideshow/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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/InputSources/SDWebImageSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SDWebImageSource.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Nik Kov on 06.07.16. 6 | // 7 | // 8 | 9 | import UIKit 10 | #if SWIFT_PACKAGE 11 | import ImageSlideshow 12 | #endif 13 | import SDWebImage 14 | 15 | /// Input Source to image using SDWebImage 16 | @objcMembers 17 | public class SDWebImageSource: NSObject, InputSource { 18 | /// url to load 19 | public var url: URL 20 | 21 | /// placeholder used before image is loaded 22 | public var placeholder: UIImage? 23 | 24 | /// Initializes a new source with a URL 25 | /// - parameter url: a url to be loaded 26 | /// - parameter placeholder: a placeholder used before image is loaded 27 | public init(url: URL, placeholder: UIImage? = nil) { 28 | self.url = url 29 | self.placeholder = placeholder 30 | super.init() 31 | } 32 | 33 | /// Initializes a new source with a URL string 34 | /// - parameter urlString: a string url to load 35 | /// - parameter placeholder: a placeholder used before image is loaded 36 | public init?(urlString: String, placeholder: UIImage? = nil) { 37 | if let validUrl = URL(string: urlString) { 38 | self.url = validUrl 39 | self.placeholder = placeholder 40 | super.init() 41 | } else { 42 | return nil 43 | } 44 | } 45 | 46 | public func load(to imageView: UIImageView, with callback: @escaping (UIImage?) -> Void) { 47 | imageView.sd_setImage(with: self.url, placeholderImage: self.placeholder, options: [], completed: { (image, _, _, _) in 48 | callback(image) 49 | }) 50 | } 51 | 52 | public func cancelLoad(on imageView: UIImageView) { 53 | imageView.sd_cancelCurrentImageLoad() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/InputSources/AFURLSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AFURLSource.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 30.07.15. 6 | // 7 | 8 | import UIKit 9 | import AFNetworking 10 | 11 | /// Input Source to image using AFNetworking 12 | @objcMembers 13 | public class AFURLSource: NSObject, InputSource { 14 | /// url to load 15 | public var url: URL 16 | 17 | /// placeholder used before image is loaded 18 | public var placeholder: UIImage? 19 | 20 | /// Initializes a new source with URL and placeholder 21 | /// - parameter url: a url to load 22 | /// - parameter placeholder: a placeholder used before image is loaded 23 | public init(url: URL, placeholder: UIImage? = nil) { 24 | self.url = url 25 | self.placeholder = placeholder 26 | super.init() 27 | } 28 | 29 | /// Initializes a new source with a URL string 30 | /// - parameter urlString: a string url to load 31 | /// - parameter placeholder: a placeholder used before image is loaded 32 | public init?(urlString: String, placeholder: UIImage? = nil) { 33 | if let validUrl = URL(string: urlString) { 34 | self.placeholder = placeholder 35 | self.url = validUrl 36 | super.init() 37 | } else { 38 | return nil 39 | } 40 | } 41 | 42 | public func load(to imageView: UIImageView, with callback: @escaping (UIImage?) -> Void) { 43 | imageView.setImageWith(URLRequest(url: url), placeholderImage: self.placeholder, success: { (_, _, image: UIImage) in 44 | callback(image) 45 | }, failure: {[placeholder = self.placeholder] _, _, _ in 46 | callback(placeholder) 47 | }) 48 | } 49 | 50 | public func cancelLoad(on imageView: UIImageView) { 51 | imageView.cancelImageDownloadTask() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/InputSources/AlamofireSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlamofireSource.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 14.01.16. 6 | // 7 | // 8 | 9 | import UIKit 10 | #if SWIFT_PACKAGE 11 | import ImageSlideshow 12 | #endif 13 | import Alamofire 14 | import AlamofireImage 15 | 16 | /// Input Source to image using Alamofire 17 | @objcMembers 18 | public class AlamofireSource: NSObject, InputSource { 19 | /// url to load 20 | public var url: URL 21 | 22 | /// placeholder used before image is loaded 23 | public var placeholder: UIImage? 24 | 25 | /// Initializes a new source with a URL 26 | /// - parameter url: a url to load 27 | /// - parameter placeholder: a placeholder used before image is loaded 28 | public init(url: URL, placeholder: UIImage? = nil) { 29 | self.url = url 30 | self.placeholder = placeholder 31 | super.init() 32 | } 33 | 34 | /// Initializes a new source with a URL string 35 | /// - parameter urlString: a string url to load 36 | /// - parameter placeholder: a placeholder used before image is loaded 37 | public init?(urlString: String, placeholder: UIImage? = nil) { 38 | if let validUrl = URL(string: urlString) { 39 | self.url = validUrl 40 | self.placeholder = placeholder 41 | super.init() 42 | } else { 43 | return nil 44 | } 45 | } 46 | 47 | public func load(to imageView: UIImageView, with callback: @escaping (UIImage?) -> Void) { 48 | imageView.af.setImage(withURL: self.url, placeholderImage: placeholder, filter: nil, progress: nil) { [weak self] (response) in 49 | switch response.result { 50 | case .success(let image): 51 | callback(image) 52 | case .failure: 53 | callback(self?.placeholder) 54 | } 55 | } 56 | } 57 | 58 | public func cancelLoad(on imageView: UIImageView) { 59 | imageView.af.cancelImageRequest() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/InputSources/AlamofireLegacySource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlamofireLegacySource.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček 6 | // 7 | // 8 | 9 | import UIKit 10 | #if SWIFT_PACKAGE 11 | import ImageSlideshow 12 | #endif 13 | import Alamofire 14 | import AlamofireImage 15 | 16 | /// Input Source to image using Alamofire 3 17 | @objcMembers 18 | public class AlamofireSource: NSObject, InputSource { 19 | /// url to load 20 | public var url: URL 21 | 22 | /// placeholder used before image is loaded 23 | public var placeholder: UIImage? 24 | 25 | /// Initializes a new source with a URL 26 | /// - parameter url: a url to load 27 | /// - parameter placeholder: a placeholder used before image is loaded 28 | public init(url: URL, placeholder: UIImage? = nil) { 29 | self.url = url 30 | self.placeholder = placeholder 31 | super.init() 32 | } 33 | 34 | /// Initializes a new source with a URL string 35 | /// - parameter urlString: a string url to load 36 | /// - parameter placeholder: a placeholder used before image is loaded 37 | public init?(urlString: String, placeholder: UIImage? = nil) { 38 | if let validUrl = URL(string: urlString) { 39 | self.url = validUrl 40 | self.placeholder = placeholder 41 | super.init() 42 | } else { 43 | return nil 44 | } 45 | } 46 | 47 | public func load(to imageView: UIImageView, with callback: @escaping (UIImage?) -> Void) { 48 | imageView.af_setImage(withURL: self.url, placeholderImage: placeholder, filter: nil, progress: nil) { [weak self] (response) in 49 | switch response.result { 50 | case .success(let image): 51 | callback(image) 52 | case .failure: 53 | if let strongSelf = self { 54 | callback(strongSelf.placeholder) 55 | } else { 56 | callback(nil) 57 | } 58 | } 59 | } 60 | } 61 | 62 | public func cancelLoad(on imageView: UIImageView) { 63 | imageView.af_cancelImageRequest() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/ActivityIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivityIndicator.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 01.05.17. 6 | // 7 | 8 | import UIKit 9 | 10 | /// Cusotm Activity Indicator can be used by implementing this protocol 11 | public protocol ActivityIndicatorView { 12 | /// View of the activity indicator 13 | var view: UIView { get } 14 | 15 | /// Show activity indicator 16 | func show() 17 | 18 | /// Hide activity indicator 19 | func hide() 20 | } 21 | 22 | /// Factory protocol to create new ActivityIndicatorViews. Meant to be implemented when creating custom activity indicator. 23 | public protocol ActivityIndicatorFactory { 24 | func create() -> ActivityIndicatorView 25 | } 26 | 27 | /// Default ActivityIndicatorView implementation for UIActivityIndicatorView 28 | extension UIActivityIndicatorView: ActivityIndicatorView { 29 | public var view: UIView { 30 | return self 31 | } 32 | 33 | public func show() { 34 | startAnimating() 35 | } 36 | 37 | public func hide() { 38 | stopAnimating() 39 | } 40 | } 41 | 42 | /// Default activity indicator factory creating UIActivityIndicatorView instances 43 | @objcMembers 44 | open class DefaultActivityIndicator: ActivityIndicatorFactory { 45 | /// activity indicator style 46 | open var style: UIActivityIndicatorViewStyle 47 | 48 | /// activity indicator color 49 | open var color: UIColor? 50 | 51 | /// Create a new ActivityIndicator for UIActivityIndicatorView 52 | /// 53 | /// - style: activity indicator style 54 | /// - color: activity indicator color 55 | public init(style: UIActivityIndicatorViewStyle = .gray, color: UIColor? = nil) { 56 | self.style = style 57 | self.color = color 58 | } 59 | 60 | /// create ActivityIndicatorView instance 61 | open func create() -> ActivityIndicatorView { 62 | #if swift(>=4.2) 63 | let activityIndicator = UIActivityIndicatorView(style: style) 64 | #else 65 | let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: style) 66 | #endif 67 | activityIndicator.color = color 68 | activityIndicator.hidesWhenStopped = true 69 | 70 | return activityIndicator 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Example/ImageSlideshow/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 09/01/2015. 6 | // Copyright (c) 2015 Petr Zvoníček. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | private func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // 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. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // 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. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // 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. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (3.2.1): 3 | - AFNetworking/NSURLSession (= 3.2.1) 4 | - AFNetworking/Reachability (= 3.2.1) 5 | - AFNetworking/Security (= 3.2.1) 6 | - AFNetworking/Serialization (= 3.2.1) 7 | - AFNetworking/UIKit (= 3.2.1) 8 | - AFNetworking/NSURLSession (3.2.1): 9 | - AFNetworking/Reachability 10 | - AFNetworking/Security 11 | - AFNetworking/Serialization 12 | - AFNetworking/Reachability (3.2.1) 13 | - AFNetworking/Security (3.2.1) 14 | - AFNetworking/Serialization (3.2.1) 15 | - AFNetworking/UIKit (3.2.1): 16 | - AFNetworking/NSURLSession 17 | - Alamofire (5.2.1) 18 | - AlamofireImage (4.1.0): 19 | - Alamofire (~> 5.1) 20 | - ImageSlideshow (1.9.0): 21 | - ImageSlideshow/Core (= 1.9.0) 22 | - ImageSlideshow/AFURL (1.9.0): 23 | - AFNetworking (~> 3.0) 24 | - ImageSlideshow/Core 25 | - ImageSlideshow/Alamofire (1.9.0): 26 | - AlamofireImage (~> 4.0) 27 | - ImageSlideshow/Core 28 | - ImageSlideshow/Core (1.9.0) 29 | - ImageSlideshow/Kingfisher (1.9.0): 30 | - ImageSlideshow/Core 31 | - Kingfisher (> 3.0) 32 | - ImageSlideshow/SDWebImage (1.9.0): 33 | - ImageSlideshow/Core 34 | - SDWebImage (>= 3.7) 35 | - Kingfisher (5.14.0): 36 | - Kingfisher/Core (= 5.14.0) 37 | - Kingfisher/Core (5.14.0) 38 | - SDWebImage (5.8.1): 39 | - SDWebImage/Core (= 5.8.1) 40 | - SDWebImage/Core (5.8.1) 41 | 42 | DEPENDENCIES: 43 | - ImageSlideshow (from `../`) 44 | - ImageSlideshow/AFURL (from `../`) 45 | - ImageSlideshow/Alamofire (from `../`) 46 | - ImageSlideshow/Kingfisher (from `../`) 47 | - ImageSlideshow/SDWebImage (from `../`) 48 | 49 | SPEC REPOS: 50 | trunk: 51 | - AFNetworking 52 | - Alamofire 53 | - AlamofireImage 54 | - Kingfisher 55 | - SDWebImage 56 | 57 | EXTERNAL SOURCES: 58 | ImageSlideshow: 59 | :path: "../" 60 | 61 | SPEC CHECKSUMS: 62 | AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 63 | Alamofire: e911732990610fe89af59ac0077f923d72dc3dfd 64 | AlamofireImage: c4a2ba349885fb3064feb74d2e547bd42ce9be10 65 | ImageSlideshow: a90cc3568a325cdf1bd543732a2cfcf6361d0dc1 66 | Kingfisher: 7b64389a43139c903ec434788344c288217c792d 67 | SDWebImage: e3eae2eda88578db0685a0c88597fdadd9433f05 68 | 69 | PODFILE CHECKSUM: 46015634277c8c8bfd78d1b363e41ea2dfd179d9 70 | 71 | COCOAPODS: 1.9.3 72 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/InputSources/KingfisherSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KingfisherSource.swift 3 | // ImageSlideshow 4 | // 5 | // Created by feiin 6 | // 7 | // 8 | 9 | import UIKit 10 | #if SWIFT_PACKAGE 11 | import ImageSlideshow 12 | #endif 13 | import Kingfisher 14 | 15 | /// Input Source to image using Kingfisher 16 | public class KingfisherSource: NSObject, InputSource { 17 | /// url to load 18 | public var url: URL 19 | 20 | /// placeholder used before image is loaded 21 | public var placeholder: UIImage? 22 | 23 | /// options for displaying, ie. [.transition(.fade(0.2))] 24 | public var options: KingfisherOptionsInfo? 25 | 26 | /// Initializes a new source with a URL 27 | /// - parameter url: a url to be loaded 28 | /// - parameter placeholder: a placeholder used before image is loaded 29 | /// - parameter options: options for displaying 30 | public init(url: URL, placeholder: UIImage? = nil, options: KingfisherOptionsInfo? = nil) { 31 | self.url = url 32 | self.placeholder = placeholder 33 | self.options = options 34 | super.init() 35 | } 36 | 37 | /// Initializes a new source with a URL string 38 | /// - parameter urlString: a string url to load 39 | /// - parameter placeholder: a placeholder used before image is loaded 40 | /// - parameter options: options for displaying 41 | public init?(urlString: String, placeholder: UIImage? = nil, options: KingfisherOptionsInfo? = nil) { 42 | if let validUrl = URL(string: urlString) { 43 | self.url = validUrl 44 | self.placeholder = placeholder 45 | self.options = options 46 | super.init() 47 | } else { 48 | return nil 49 | } 50 | } 51 | 52 | /// Load an image to an UIImageView 53 | /// 54 | /// - Parameters: 55 | /// - imageView: UIImageView that receives the loaded image 56 | /// - callback: Completion callback with an optional image 57 | @objc 58 | public func load(to imageView: UIImageView, with callback: @escaping (UIImage?) -> Void) { 59 | imageView.kf.setImage(with: self.url, placeholder: self.placeholder, options: self.options, progressBlock: nil) { result in 60 | switch result { 61 | case .success(let image): 62 | callback(image.image) 63 | case .failure: 64 | callback(self.placeholder) 65 | } 66 | } 67 | } 68 | 69 | /// Cancel an image download task 70 | /// 71 | /// - Parameter imageView: UIImage view with the download task that should be canceled 72 | public func cancelLoad(on imageView: UIImageView) { 73 | imageView.kf.cancelDownloadTask() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Example/ImageSlideshow/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.swift 3 | // ImageSlideshow_Example 4 | // 5 | // Created by Petr Zvoníček on 25.02.18. 6 | // Copyright © 2018 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ImageSlideshow 11 | 12 | struct Model { 13 | let image: UIImage 14 | let title: String 15 | 16 | var inputSource: InputSource { 17 | return ImageSource(image: image) 18 | } 19 | } 20 | 21 | class TableViewController: UITableViewController { 22 | 23 | let models = [Model(image: UIImage(named: "img1")!, title: "First image"), Model(image: UIImage(named: "img2")!, title: "Second image"), Model(image: UIImage(named: "img3")!, title: "Third image"), Model(image: UIImage(named: "img4")!, title: "Fourth image")] 24 | 25 | var slideshowTransitioningDelegate: ZoomAnimatedTransitioningDelegate? = nil 26 | 27 | // MARK: - Table view data source 28 | 29 | override func numberOfSections(in tableView: UITableView) -> Int { 30 | return 1 31 | } 32 | 33 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 34 | return models.count 35 | } 36 | 37 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 38 | let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) 39 | let model = models[indexPath.row] 40 | cell.imageView?.image = model.image 41 | cell.textLabel?.text = model.title 42 | 43 | return cell 44 | } 45 | 46 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 47 | tableView.deselectRow(at: indexPath, animated: true) 48 | 49 | let fullScreenController = FullScreenSlideshowViewController() 50 | fullScreenController.inputs = models.map { $0.inputSource } 51 | fullScreenController.initialPage = indexPath.row 52 | 53 | if let cell = tableView.cellForRow(at: indexPath), let imageView = cell.imageView { 54 | slideshowTransitioningDelegate = ZoomAnimatedTransitioningDelegate(imageView: imageView, slideshowController: fullScreenController) 55 | fullScreenController.modalPresentationStyle = .custom 56 | fullScreenController.transitioningDelegate = slideshowTransitioningDelegate 57 | } 58 | 59 | fullScreenController.slideshow.currentPageChanged = { [weak self] page in 60 | if let cell = tableView.cellForRow(at: IndexPath(row: page, section: 0)), let imageView = cell.imageView { 61 | self?.slideshowTransitioningDelegate?.referenceImageView = imageView 62 | } 63 | } 64 | 65 | present(fullScreenController, animated: true, completion: nil) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "ImageSlideshow", 7 | platforms: [ 8 | .iOS(.v10), 9 | ], 10 | products: [ 11 | .library( 12 | name: "ImageSlideshow", 13 | targets: ["ImageSlideshow"]), 14 | .library( 15 | name: "ImageSlideshow/Alamofire", 16 | targets: ["ImageSlideshowAlamofire"]), 17 | .library( 18 | name: "ImageSlideshow/SDWebImage", 19 | targets: ["ImageSlideshowSDWebImage"]), 20 | .library( 21 | name: "ImageSlideshow/Kingfisher", 22 | targets: ["ImageSlideshowKingfisher"]) 23 | ], 24 | dependencies: [ 25 | .package(url: "https://github.com/onevcat/Kingfisher.git", from: "5.8.0"), 26 | .package(url: "https://github.com/Alamofire/AlamofireImage.git", from: "4.0.0"), 27 | .package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.1.0") 28 | ], 29 | targets: [ 30 | .target( 31 | name: "ImageSlideshow", 32 | path: "ImageSlideshow", 33 | sources: [ 34 | "Classes/Core/ActivityIndicator.swift", 35 | "Classes/Core/Bundle+Module.swift", 36 | "Classes/Core/FullScreenSlideshowViewController.swift", 37 | "Classes/Core/ImageSlideshow.swift", 38 | "Classes/Core/ImageSlideshowItem.swift", 39 | "Classes/Core/InputSource.swift", 40 | "Classes/Core/PageIndicator.swift", 41 | "Classes/Core/PageIndicatorPosition.swift", 42 | "Classes/Core/SwiftSupport.swift", 43 | "Classes/Core/UIImage+AspectFit.swift", 44 | "Classes/Core/UIImageView+Tools.swift", 45 | "Classes/Core/ZoomAnimatedTransitioning.swift", 46 | ], 47 | resources: [ 48 | .copy("Assets/ic_cross_white@2x.png"), 49 | .copy("Assets/ic_cross_white@3x.png"), 50 | ]), 51 | .target( 52 | name: "ImageSlideshowAlamofire", 53 | dependencies: ["ImageSlideshow", "AlamofireImage"], 54 | path: "ImageSlideshow/Classes/InputSources", 55 | sources: ["AlamofireSource.swift"]), 56 | .target( 57 | name: "ImageSlideshowSDWebImage", 58 | dependencies: ["ImageSlideshow", "SDWebImage"], 59 | path: "ImageSlideshow/Classes/InputSources", 60 | sources: ["SDWebImageSource.swift"]), 61 | .target( 62 | name: "ImageSlideshowKingfisher", 63 | dependencies: ["ImageSlideshow", "Kingfisher"], 64 | path: "ImageSlideshow/Classes/InputSources", 65 | sources: ["KingfisherSource.swift"]) 66 | ], 67 | swiftLanguageVersions: [.v4, .v4_2, .v5] 68 | ) 69 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/PageIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageIndicator.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 27.05.18. 6 | // 7 | 8 | import UIKit 9 | 10 | /// Cusotm Page Indicator can be used by implementing this protocol 11 | public protocol PageIndicatorView: class { 12 | /// View of the page indicator 13 | var view: UIView { get } 14 | 15 | /// Current page of the page indicator 16 | var page: Int { get set } 17 | 18 | /// Total number of pages of the page indicator 19 | var numberOfPages: Int { get set} 20 | } 21 | 22 | extension UIPageControl: PageIndicatorView { 23 | public var view: UIView { 24 | return self 25 | } 26 | 27 | public var page: Int { 28 | get { 29 | return currentPage 30 | } 31 | set { 32 | currentPage = newValue 33 | } 34 | } 35 | 36 | open override func sizeToFit() { 37 | var frame = self.frame 38 | frame.size = size(forNumberOfPages: numberOfPages) 39 | frame.size.height = 30 40 | self.frame = frame 41 | } 42 | 43 | public static func withSlideshowColors() -> UIPageControl { 44 | let pageControl = UIPageControl() 45 | 46 | if #available(iOS 13.0, *) { 47 | pageControl.currentPageIndicatorTintColor = UIColor { traits in 48 | traits.userInterfaceStyle == .dark ? .white : .lightGray 49 | } 50 | } else { 51 | pageControl.currentPageIndicatorTintColor = .lightGray 52 | } 53 | 54 | if #available(iOS 13.0, *) { 55 | pageControl.pageIndicatorTintColor = UIColor { traits in 56 | traits.userInterfaceStyle == .dark ? .systemGray : .black 57 | } 58 | } else { 59 | pageControl.pageIndicatorTintColor = .black 60 | } 61 | 62 | return pageControl 63 | } 64 | } 65 | 66 | /// Page indicator that shows page in numeric style, eg. "5/21" 67 | public class LabelPageIndicator: UILabel, PageIndicatorView { 68 | public var view: UIView { 69 | return self 70 | } 71 | 72 | public var numberOfPages: Int = 0 { 73 | didSet { 74 | updateLabel() 75 | } 76 | } 77 | 78 | public var page: Int = 0 { 79 | didSet { 80 | updateLabel() 81 | } 82 | } 83 | 84 | public override init(frame: CGRect) { 85 | super.init(frame: frame) 86 | initialize() 87 | } 88 | 89 | required public init?(coder aDecoder: NSCoder) { 90 | super.init(coder: aDecoder) 91 | initialize() 92 | } 93 | 94 | private func initialize() { 95 | self.textAlignment = .center 96 | } 97 | 98 | private func updateLabel() { 99 | text = "\(page+1)/\(numberOfPages)" 100 | } 101 | 102 | public override func sizeToFit() { 103 | let maximumString = String(repeating: "8", count: numberOfPages) as NSString 104 | self.frame.size = maximumString.size(withAttributes: [.font: font as Any]) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/InputSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputSource.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 14.01.16. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | /// A protocol that can be adapted by different Input Source providers 12 | @objc public protocol InputSource { 13 | /** 14 | Load image from the source to image view. 15 | - parameter imageView: Image view to load the image into. 16 | - parameter callback: Callback called after image was set to the image view. 17 | - parameter image: Image that was set to the image view. 18 | */ 19 | func load(to imageView: UIImageView, with callback: @escaping (_ image: UIImage?) -> Void) 20 | 21 | /** 22 | Cancel image load on the image view 23 | - parameter imageView: Image view that is loading the image 24 | */ 25 | @objc optional func cancelLoad(on imageView: UIImageView) 26 | } 27 | 28 | /// Input Source to load plain UIImage 29 | @objcMembers 30 | open class ImageSource: NSObject, InputSource { 31 | var image: UIImage 32 | 33 | /// Initializes a new Image Source with UIImage 34 | /// - parameter image: Image to be loaded 35 | public init(image: UIImage) { 36 | self.image = image 37 | } 38 | 39 | /// Initializes a new Image Source with an image name from the main bundle 40 | /// - parameter imageString: name of the file in the application's main bundle 41 | @available(*, deprecated, message: "Use `BundleImageSource` instead") 42 | public init?(imageString: String) { 43 | if let image = UIImage(named: imageString) { 44 | self.image = image 45 | super.init() 46 | } else { 47 | return nil 48 | } 49 | } 50 | 51 | public func load(to imageView: UIImageView, with callback: @escaping (UIImage?) -> Void) { 52 | imageView.image = image 53 | callback(image) 54 | } 55 | } 56 | 57 | /// Input Source to load an image from the main bundle 58 | @objcMembers 59 | open class BundleImageSource: NSObject, InputSource { 60 | var imageString: String 61 | 62 | /// Initializes a new Image Source with an image name from the main bundle 63 | /// - parameter imageString: name of the file in the application's main bundle 64 | public init(imageString: String) { 65 | self.imageString = imageString 66 | super.init() 67 | } 68 | 69 | public func load(to imageView: UIImageView, with callback: @escaping (UIImage?) -> Void) { 70 | let image = UIImage(named: imageString) 71 | imageView.image = image 72 | callback(image) 73 | } 74 | } 75 | 76 | /// Input Source to load an image from a local file path 77 | @objcMembers 78 | open class FileImageSource: NSObject, InputSource { 79 | var path: String 80 | 81 | /// Initializes a new Image Source with an image name from the main bundle 82 | /// - parameter imageString: name of the file in the application's main bundle 83 | public init(path: String) { 84 | self.path = path 85 | super.init() 86 | } 87 | 88 | public func load(to imageView: UIImageView, with callback: @escaping (UIImage?) -> Void) { 89 | let image = UIImage(contentsOfFile: path) 90 | imageView.image = image 91 | callback(image) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Example/ImageSlideshow.xcodeproj/xcshareddata/xcschemes/ImageSlideshow_framework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/PageIndicatorPosition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PageIndicator.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 04.02.18. 6 | // 7 | 8 | import UIKit 9 | 10 | /// Describes the configuration of the page indicator position 11 | public struct PageIndicatorPosition { 12 | public enum Horizontal { 13 | case left(padding: CGFloat), center, right(padding: CGFloat) 14 | } 15 | 16 | public enum Vertical { 17 | case top, bottom, under, customTop(padding: CGFloat), customBottom(padding: CGFloat), customUnder(padding: CGFloat) 18 | } 19 | 20 | /// Horizontal position of the page indicator 21 | var horizontal: Horizontal 22 | 23 | /// Vertical position of the page indicator 24 | var vertical: Vertical 25 | 26 | /// Creates a new PageIndicatorPosition struct 27 | /// 28 | /// - Parameters: 29 | /// - horizontal: horizontal position of the page indicator 30 | /// - vertical: vertical position of the page indicator 31 | public init(horizontal: Horizontal = .center, vertical: Vertical = .bottom) { 32 | self.horizontal = horizontal 33 | self.vertical = vertical 34 | } 35 | 36 | /// Computes the additional padding needed for the page indicator under the ImageSlideshow 37 | /// 38 | /// - Parameter indicatorSize: size of the page indicator 39 | /// - Returns: padding needed under the ImageSlideshow 40 | func underPadding(for indicatorSize: CGSize) -> CGFloat { 41 | switch vertical { 42 | case .under: 43 | return indicatorSize.height 44 | case .customUnder(let padding): 45 | return indicatorSize.height + padding 46 | default: 47 | return 0 48 | } 49 | } 50 | 51 | /// Computes the page indicator frame 52 | /// 53 | /// - Parameters: 54 | /// - parentFrame: frame of the parent view – ImageSlideshow 55 | /// - indicatorSize: size of the page indicator 56 | /// - edgeInsets: edge insets of the parent view – ImageSlideshow (used for SafeAreaInsets adjustment) 57 | /// - Returns: frame of the indicator by computing the origin and using `indicatorSize` as size 58 | func indicatorFrame(for parentFrame: CGRect, indicatorSize: CGSize, edgeInsets: UIEdgeInsets) -> CGRect { 59 | var xSize: CGFloat = 0 60 | var ySize: CGFloat = 0 61 | 62 | switch horizontal { 63 | case .center: 64 | xSize = parentFrame.size.width / 2 - indicatorSize.width / 2 65 | case .left(let padding): 66 | xSize = padding + edgeInsets.left 67 | case .right(let padding): 68 | xSize = parentFrame.size.width - indicatorSize.width - padding - edgeInsets.right 69 | } 70 | 71 | switch vertical { 72 | case .bottom, .under, .customUnder: 73 | ySize = parentFrame.size.height - indicatorSize.height - edgeInsets.bottom 74 | case .customBottom(let padding): 75 | ySize = parentFrame.size.height - indicatorSize.height - padding - edgeInsets.bottom 76 | case .top: 77 | ySize = edgeInsets.top 78 | case .customTop(let padding): 79 | ySize = padding + edgeInsets.top 80 | } 81 | 82 | return CGRect(x: xSize, y: ySize, width: indicatorSize.width, height: indicatorSize.height) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ImageSlideshow.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint ImageSlideshow.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "ImageSlideshow" 11 | s.version = "1.9.2" 12 | s.summary = "Image slideshow written in Swift with circular scrolling, timer and full screen viewer" 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | s.description = <<-DESC 20 | Image slideshow is a Swift library providing customizable image slideshow with circular scrolling, timer and full screen viewer and extendable image source (AFNetworking image source available in AFURL subspec). 21 | DESC 22 | 23 | s.homepage = "https://github.com/zvonicek/ImageSlideshow" 24 | s.screenshots = "https://dzwonsemrish7.cloudfront.net/items/2R06283n040V3P3p0i42/ezgif.com-optimize.gif" 25 | s.license = 'MIT' 26 | s.author = { "Petr Zvonicek" => "zvonicek@gmail.com" } 27 | s.source = { :git => "https://github.com/zvonicek/ImageSlideshow.git", :tag => s.version.to_s } 28 | s.social_media_url = 'https://twitter.com/zvonicek' 29 | 30 | s.swift_versions = ['4.0', '4.1', '4.2', '5', '5.1', '5.2'] 31 | s.platform = :ios, '8.0' 32 | s.requires_arc = true 33 | 34 | s.subspec 'Core' do |core| 35 | core.source_files = 'ImageSlideshow/Classes/Core/**/*' 36 | core.resources = 'ImageSlideshow/Assets/*.png' 37 | end 38 | 39 | s.subspec 'AFURL' do |subspec| 40 | subspec.dependency 'ImageSlideshow/Core' 41 | subspec.dependency 'AFNetworking', '~> 3.0' 42 | subspec.source_files = 'ImageSlideshow/Classes/InputSources/AFURLSource.swift' 43 | end 44 | 45 | s.subspec 'Alamofire3' do |subspec| 46 | subspec.dependency 'ImageSlideshow/Core' 47 | subspec.dependency 'AlamofireImage', '~> 3.0' 48 | subspec.source_files = 'ImageSlideshow/Classes/InputSources/AlamofireLegacySource.swift' 49 | end 50 | 51 | s.subspec 'Alamofire' do |subspec| 52 | subspec.dependency 'ImageSlideshow/Core' 53 | subspec.dependency 'AlamofireImage', '~> 4.0' 54 | subspec.platform = :ios, '10.0' 55 | subspec.source_files = 'ImageSlideshow/Classes/InputSources/AlamofireSource.swift' 56 | end 57 | 58 | s.subspec 'SDWebImage' do |subspec| 59 | subspec.dependency 'ImageSlideshow/Core' 60 | subspec.dependency 'SDWebImage', '>= 3.7' 61 | subspec.source_files = 'ImageSlideshow/Classes/InputSources/SDWebImageSource.swift' 62 | end 63 | 64 | s.subspec 'Kingfisher' do |subspec| 65 | subspec.dependency 'ImageSlideshow/Core' 66 | subspec.dependency 'Kingfisher', '> 3.0' 67 | subspec.platform = :ios, '10.0' 68 | subspec.source_files = 'ImageSlideshow/Classes/InputSources/KingfisherSource.swift' 69 | end 70 | 71 | s.subspec 'Parse' do |subspec| 72 | subspec.dependency 'ImageSlideshow/Core' 73 | subspec.dependency 'Parse', '~> 1.14' 74 | subspec.source_files = 'ImageSlideshow/Classes/InputSources/ParseSource.swift' 75 | end 76 | 77 | s.default_subspec = 'Core' 78 | 79 | end 80 | -------------------------------------------------------------------------------- /Example/ImageSlideshow/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 30.07.15. 6 | // Copyright (c) 2015 Petr Zvonicek. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ImageSlideshow 11 | 12 | class ViewController: UIViewController { 13 | 14 | open override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 15 | return .portrait 16 | } 17 | 18 | @IBOutlet var slideshow: ImageSlideshow! 19 | 20 | let localSource = [BundleImageSource(imageString: "img1"), BundleImageSource(imageString: "img2"), BundleImageSource(imageString: "img3"), BundleImageSource(imageString: "img4")] 21 | let afNetworkingSource = [AFURLSource(urlString: "https://images.unsplash.com/photo-1432679963831-2dab49187847?w=1080")!, AFURLSource(urlString: "https://images.unsplash.com/photo-1447746249824-4be4e1b76d66?w=1080")!, AFURLSource(urlString: "https://images.unsplash.com/photo-1463595373836-6e0b0a8ee322?w=1080")!] 22 | let alamofireSource = [AlamofireSource(urlString: "https://images.unsplash.com/photo-1432679963831-2dab49187847?w=1080")!, AlamofireSource(urlString: "https://images.unsplash.com/photo-1447746249824-4be4e1b76d66?w=1080")!, AlamofireSource(urlString: "https://images.unsplash.com/photo-1463595373836-6e0b0a8ee322?w=1080")!] 23 | let sdWebImageSource = [SDWebImageSource(urlString: "https://images.unsplash.com/photo-1432679963831-2dab49187847?w=1080")!, SDWebImageSource(urlString: "https://images.unsplash.com/photo-1447746249824-4be4e1b76d66?w=1080")!, SDWebImageSource(urlString: "https://images.unsplash.com/photo-1463595373836-6e0b0a8ee322?w=1080")!] 24 | let kingfisherSource = [KingfisherSource(urlString: "https://images.unsplash.com/photo-1432679963831-2dab49187847?w=1080")!, KingfisherSource(urlString: "https://images.unsplash.com/photo-1447746249824-4be4e1b76d66?w=1080")!, KingfisherSource(urlString: "https://images.unsplash.com/photo-1463595373836-6e0b0a8ee322?w=1080")!] 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | slideshow.slideshowInterval = 5.0 30 | slideshow.pageIndicatorPosition = .init(horizontal: .center, vertical: .under) 31 | slideshow.contentScaleMode = UIViewContentMode.scaleAspectFill 32 | 33 | slideshow.pageIndicator = UIPageControl.withSlideshowColors() 34 | 35 | // optional way to show activity indicator during image load (skipping the line will show no activity indicator) 36 | slideshow.activityIndicator = DefaultActivityIndicator() 37 | slideshow.delegate = self 38 | 39 | // can be used with other sample sources as `afNetworkingSource`, `alamofireSource` or `sdWebImageSource` or `kingfisherSource` 40 | slideshow.setImageInputs(localSource) 41 | 42 | let recognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.didTap)) 43 | slideshow.addGestureRecognizer(recognizer) 44 | } 45 | 46 | @objc func didTap() { 47 | let fullScreenController = slideshow.presentFullScreenController(from: self) 48 | // set the activity indicator for full screen controller (skipping the line will show no activity indicator) 49 | fullScreenController.slideshow.activityIndicator = DefaultActivityIndicator(style: .white, color: nil) 50 | } 51 | } 52 | 53 | extension ViewController: ImageSlideshowDelegate { 54 | func imageSlideshow(_ imageSlideshow: ImageSlideshow, didChangeCurrentPageTo page: Int) { 55 | print("current page:", page) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Example/ImageSlideshow/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/FullScreenSlideshowViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FullScreenSlideshowViewController.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 31.08.15. 6 | // 7 | 8 | import UIKit 9 | 10 | @objcMembers 11 | open class FullScreenSlideshowViewController: UIViewController { 12 | 13 | open var slideshow: ImageSlideshow = { 14 | let slideshow = ImageSlideshow() 15 | slideshow.zoomEnabled = true 16 | slideshow.contentScaleMode = UIViewContentMode.scaleAspectFit 17 | slideshow.pageIndicatorPosition = PageIndicatorPosition(horizontal: .center, vertical: .bottom) 18 | // turns off the timer 19 | slideshow.slideshowInterval = 0 20 | slideshow.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight] 21 | 22 | return slideshow 23 | }() 24 | 25 | /// Close button 26 | open var closeButton = UIButton() 27 | 28 | /// Close button frame 29 | open var closeButtonFrame: CGRect? 30 | 31 | /// Closure called on page selection 32 | open var pageSelected: ((_ page: Int) -> Void)? 33 | 34 | /// Index of initial image 35 | open var initialPage: Int = 0 36 | 37 | /// Input sources to 38 | open var inputs: [InputSource]? 39 | 40 | /// Background color 41 | open var backgroundColor = UIColor.black 42 | 43 | /// Enables/disable zoom 44 | open var zoomEnabled = true { 45 | didSet { 46 | slideshow.zoomEnabled = zoomEnabled 47 | } 48 | } 49 | 50 | fileprivate var isInit = true 51 | 52 | convenience init() { 53 | self.init(nibName: nil, bundle: nil) 54 | 55 | self.modalPresentationStyle = .custom 56 | if #available(iOS 13.0, *) { 57 | // Use KVC to set the value to preserve backwards compatiblity with Xcode < 11 58 | self.setValue(true, forKey: "modalInPresentation") 59 | } 60 | } 61 | 62 | override open func viewDidLoad() { 63 | super.viewDidLoad() 64 | 65 | view.backgroundColor = backgroundColor 66 | slideshow.backgroundColor = backgroundColor 67 | 68 | if let inputs = inputs { 69 | slideshow.setImageInputs(inputs) 70 | } 71 | 72 | view.addSubview(slideshow) 73 | 74 | // close button configuration 75 | closeButton.setImage(UIImage(named: "ic_cross_white", in: .module, compatibleWith: nil), for: UIControlState()) 76 | closeButton.addTarget(self, action: #selector(FullScreenSlideshowViewController.close), for: UIControlEvents.touchUpInside) 77 | view.addSubview(closeButton) 78 | } 79 | 80 | override open var prefersStatusBarHidden: Bool { 81 | return true 82 | } 83 | 84 | override open func viewWillAppear(_ animated: Bool) { 85 | super.viewWillAppear(animated) 86 | 87 | if isInit { 88 | isInit = false 89 | slideshow.setCurrentPage(initialPage, animated: false) 90 | } 91 | } 92 | 93 | override open func viewWillDisappear(_ animated: Bool) { 94 | super.viewWillDisappear(animated) 95 | 96 | slideshow.slideshowItems.forEach { $0.cancelPendingLoad() } 97 | 98 | // Prevents broken dismiss transition when image is zoomed in 99 | slideshow.currentSlideshowItem?.zoomOut() 100 | } 101 | 102 | open override func viewDidLayoutSubviews() { 103 | if !isBeingDismissed { 104 | let safeAreaInsets: UIEdgeInsets 105 | if #available(iOS 11.0, *) { 106 | safeAreaInsets = view.safeAreaInsets 107 | } else { 108 | safeAreaInsets = UIEdgeInsets.zero 109 | } 110 | 111 | closeButton.frame = closeButtonFrame ?? CGRect(x: max(10, safeAreaInsets.left), y: max(10, safeAreaInsets.top), width: 40, height: 40) 112 | } 113 | 114 | slideshow.frame = view.frame 115 | } 116 | 117 | func close() { 118 | // if pageSelected closure set, send call it with current page 119 | if let pageSelected = pageSelected { 120 | pageSelected(slideshow.currentPage) 121 | } 122 | 123 | dismiss(animated: true, completion: nil) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Example/ImageSlideshow.xcodeproj/xcshareddata/xcschemes/ImageSlideshow-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## [1.6.1](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.6.1) (11/06/2018) 4 | 5 | ## Fixes 6 | 7 | - Fixed Carthage build (#258) 8 | - Fixed memory issues (#255) 9 | 10 | ## [1.6.0](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.6.0) (27/05/2018) 11 | 12 | ## New Features 13 | 14 | - Page Indicator customization (#251) 15 | 16 | ## Fixes 17 | 18 | - Fixed animation problem on orientation change (#234) 19 | - Fixed missing close button image in Carthage build (#247) 20 | 21 | ## [1.5.3](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.5.3) (17/04/2018) 22 | 23 | ## Fixes 24 | 25 | - Fixed Close broken button in Full screen controller (#242) 26 | - Fixed retain cycle (#241, @piercifani) 27 | 28 | ## [1.5.2](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.5.2) (16/04/2018) 29 | 30 | ## New Features 31 | 32 | - Example project demonstrating usage of Image Slideshow with parent UIImageView 33 | 34 | ## Fixes 35 | 36 | - Fix image shift on iPhone X landscape (#200) 37 | - Add closeButtonFrame property to Full Screen controller (#226) 38 | - Don't trigger interactive gesture recognizer on horizontal pans, fixes single image sliding issue (#224) 39 | - Fix interactive dismiss regression on iOS 11 40 | 41 | ## [1.5.1](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.5.1) (04/02/2018) 42 | 43 | ## Fixes 44 | - Fix division by zero error (#223) 45 | 46 | ## [1.5.0](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.5.0) (21/01/2018) 47 | 48 | ## New Features 49 | 50 | - Implement image load cancelling to optimize memory usage 51 | - Improve Kingfisher InputSource to take Options parameters 52 | - Update page control selected page during scrolling (#204) 53 | - Add possibility to change maximum zoom scale (#221) 54 | 55 | ## Fixes 56 | 57 | - SDWebImage dependency improvements (#205) 58 | - Fix possible division by zero crash (#187) 59 | - Adjust close button frame to respect SafeAreaInsets (#209) 60 | - Fix missing placeholder on AFURLSource (#218) 61 | - Fix incorrect currentPageChanged calls (#222) 62 | 63 | ## [1.4.1](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.4.1) (04/10/2017) 64 | 65 | ## Fixes 66 | 67 | - iPhone X fixes 68 | 69 | 70 | ## [1.4.0](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.4.0) (23/09/2017) 71 | 72 | ## New Features 73 | 74 | - Support for Swift 4 75 | 76 | ## [1.3.0](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.3.0) (08/05/2017) 77 | 78 | ## New Features 79 | 80 | - Possibility to show activity indicator during async loading 81 | - Hide UIPageControl when sllideshow has single item 82 | - UIScrollViewDelegate methods are now `open` instead of `public` 83 | 84 | ## Fixes 85 | 86 | - Fix zoom transition for when a slideshow has just a single item 87 | - Fix issue on `zoomEnabled` change 88 | 89 | 90 | ## [1.2.0](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.2.0) (20/03/2017) 91 | 92 | ## New Features 93 | 94 | - Improved placeholder handling on all remote input sources 95 | - Deprecated `pauseTimerIfNeeded` and `unpauseTimerIfNeeded` in favour of `pauseTimer` and `unpauseTimer` 96 | 97 | ## Fixes 98 | 99 | - Fix memory leak caused by incorrect timer invalidation 100 | - Partially fix an UI glitch happening when "in-call" status bar is on 101 | 102 | ## [1.1.0](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.1.0) (19/02/2017) 103 | 104 | ## New Features 105 | 106 | - Add `willBeginDragging` and `didEndDecelerating` callback closures (@El-Fitz) 107 | - Add Parse input source (@jaimeagudo) 108 | 109 | ## Fixes 110 | 111 | - Fix image preload issue when scrolling between edges (#115) 112 | - Fix issue caused by disabling `circular` after setting input sources (#104) 113 | - Improve example project 114 | - Style fixes (@dogo) 115 | 116 | ## [1.0.0](https://github.com/zvonicek/ImageSlideshow/releases/tag/1.0.0) (11/12/2016) 117 | 118 | Version 1.0 aims to improve stability and brings couple of new features. Also contains few backward complatibility breaking changes. 119 | 120 | ## New Features 121 | - New input source for Kingfisher (@feiin) 122 | - Add `currentPageChanged` closure to notify about page change (#54) 123 | - Add possibility to lazy load and release images (#42) 124 | - Easier way to present `FullScreenSlideshowViewController` (#72) 125 | - Documentation improvements 126 | 127 | ## Fixes 128 | - Fix the case when containing VC automatically adjusts scrollview insets (#71) 129 | - Fix crash during transition when no images set (#74) 130 | - Rounding error fix for page calculation (#90, @mjrehder) 131 | - Fix for black image when using fullscreen zoom (#92, @mjrehder) 132 | - Change page on UIPageControl page change (#80) 133 | - iOS 10 interactive transition interruption fix (5d5f22f) 134 | - Memory fixes 135 | 136 | ## API changes 137 | - `currentItemIndex` was renamed to `currentPage` 138 | - `set` function from `InputSource` protocol was renamed to `load` and have a new closure parameter called on image load 139 | 140 | 141 | ## [0.6](https://github.com/zvonicek/ImageSlideshow/releases/tag/0.6.0) (21/06/2016) 142 | 143 | Add support for Swift 2.3 and Carthage. Equivalent version *0.6-swift3* supports Swift 3. 144 | 145 | ## [0.5](https://github.com/zvonicek/ImageSlideshow/releases/tag/0.5.0) (09/06/2016) 146 | 147 | The version 0.5 cleans up the code, adds interactive fullscreen dismiss and fixes few minor issues (thanks again, @kafejo) 148 | This version also contains several background compatibility breaks, so please keep this in mind when upgrading to it. 149 | 150 | ### New Features 151 | - Interactive dismiss transition on full screen controller 152 | - A possibility to open full screen controller from plain UIImageView 153 | 154 | ### API changes 155 | - ImageSlideshow: `currentPage` renamed to `currentItemIndex` 156 | - FullScreenSlideshowViewController: `initialPage` renamed to `initialPageIndex` 157 | - ZoomAnimatedTransitioning: added second parameter `slideshowController: FullScreenSlideshowViewController` to constructor 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🖼 ImageSlideshow 2 | 3 | **Customizable Swift image slideshow with circular scrolling, timer and full screen viewer** 4 | 5 | [![Build Status](https://www.bitrise.io/app/9aaf3e552f3a575c.svg?token=AjiVckTN9ItQtJs873mYMw&branch=master)](https://www.bitrise.io/app/9aaf3e552f3a575c) 6 | [![Version](https://img.shields.io/cocoapods/v/ImageSlideshow.svg?style=flat)](http://cocoapods.org/pods/ImageSlideshow) 7 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | [![License](https://img.shields.io/cocoapods/l/ImageSlideshow.svg?style=flat)](http://cocoapods.org/pods/ImageSlideshow) 9 | [![Platform](https://img.shields.io/cocoapods/p/ImageSlideshow.svg?style=flat)](http://cocoapods.org/pods/ImageSlideshow) 10 | 11 | 12 | 13 | ![](https://dzwonsemrish7.cloudfront.net/items/2R06283n040V3P3p0i42/ezgif.com-optimize.gif) 14 | 15 | ## 📱 Example 16 | 17 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 18 | 19 | ## 🔧 Installation 20 | 21 | ### CocoaPods 22 | ImageSlideshow is available through [CocoaPods](http://cocoapods.org). To install 23 | it, simply add the following line to your Podfile: 24 | 25 | ```ruby 26 | pod 'ImageSlideshow', '~> 1.9.0' 27 | ``` 28 | 29 | ### Carthage 30 | To integrate ImageSlideshow into your Xcode project using Carthage, specify it in your Cartfile: 31 | 32 | ```ruby 33 | github "zvonicek/ImageSlideshow" ~> 1.9.0 34 | ``` 35 | 36 | Carthage does not include InputSources for external providers (due to dependency on those providers) so you need to grab the one you need from `ImageSlideshow/Classes/InputSources` manually. 37 | 38 | ### Manually 39 | One possibility is to download a built framework (ImageSlideshow.framework.zip) from [releases page](https://github.com/zvonicek/ImageSlideshow/releases/) and link it with your project (under`Linked Frameworks and Libraries` in your target). This is, however, currently problematic because of rapid Swift development -- the framework is built for a single Swift version and may not work on previous/future versions. 40 | 41 | Alternatively can also grab the whole `ImageSlideshow` directory and copy it to your project. Be sure to remove those external Input Sources you don't need. 42 | 43 | **Note on Swift 2.3, Swift 3 and Swift 4 support** 44 | 45 | Version 1.4 supports Swift 4. Swift 3 is supported from version 1.0, for Swift 2.2 and Swift 2.3 compatible code use version 0.6 or branch *swift-2.3*. 46 | 47 | 48 | ## 🔨 How to use 49 | 50 | Add ImageSlideshow view to your view hiearchy either in Interface Builder or in code. 51 | 52 | ### Loading images 53 | 54 | Set images by using ```setImageInputs``` method on ```ImageSlideshow``` instance with an array of *InputSource*s. By default you can use ```ImageSource``` which takes ```UIImage``` or few other *InputSource*s for most popular networking libraries. You can also create your own input source by implementing ```InputSource``` protocol. 55 | 56 | | Library | InputSource name | Pod | 57 | | ------------------------------------------------------------- |:----------------:| ---------------------------------:| 58 | | [AlamofireImage](https://github.com/Alamofire/AlamofireImage) | AlamofireSource | `pod "ImageSlideshow/Alamofire"` | 59 | | [AFNetworking](https://github.com/AFNetworking/AFNetworking) | AFURLSource | `pod "ImageSlideshow/AFURL"` | 60 | | [SDWebImage](https://github.com/rs/SDWebImage) | SDWebImageSource | `pod "ImageSlideshow/SDWebImage"` | 61 | | [Kingfisher](https://github.com/onevcat/Kingfisher) | KingfisherSource | `pod "ImageSlideshow/Kingfisher"` | 62 | | [Parse](https://github.com/ParsePlatform/Parse-SDK-iOS-OSX) | ParseSource | `pod "ImageSlideshow/Parse"` | 63 | 64 | 65 | ```swift 66 | slideshow.setImageInputs([ 67 | ImageSource(image: UIImage(named: "myImage"))!, 68 | ImageSource(image: UIImage(named: "myImage2"))!, 69 | AlamofireSource(urlString: "https://images.unsplash.com/photo-1432679963831-2dab49187847?w=1080"), 70 | KingfisherSource(urlString: "https://images.unsplash.com/photo-1432679963831-2dab49187847?w=1080"), 71 | ParseSource(file: PFFile(name:"image.jpg", data:data)) 72 | ]) 73 | ``` 74 | 75 | ### Configuration 76 | 77 | Behaviour is configurable by those properties: 78 | 79 | - ```slideshowInterval``` - slideshow interval in seconds (default `0` – disabled) 80 | - ```zoomEnabled``` - enables zooming (default `false`) 81 | - ```circular``` - enables circular scrolling (default `true`) 82 | - ```activityIndicator``` – allows to set custom activity indicator, see *Activity indicator* section 83 | - ```pageIndicator``` – allows to set custom page indicator, see *Page indicator* section; assign `nil` to hide page indicator 84 | - ```pageIndicatorPosition``` - configures position of the page indicator 85 | - ```contentScaleMode``` - configures the scaling (default `ScaleAspectFit`) 86 | - ```draggingEnabled``` - enables dragging (default `true`) 87 | - ```currentPageChanged``` - closure called on page change 88 | - ```willBeginDragging``` - closure called on scrollViewWillBeginDragging 89 | - ```didEndDecelerating``` - closure called on scrollViewDidEndDecelerating 90 | - ```preload``` - image preloading configuration (default `all` preloading, also `fixed`) 91 | 92 | ### Page Indicator 93 | 94 | Page indicator can be customized using the `pageIndicator` property on ImageSlideshow. By defualt, a plain UIPageControl is used. If needed, page control can be customized: 95 | 96 | ```swift 97 | let pageIndicator = UIPageControl() 98 | pageIndicator.currentPageIndicatorTintColor = UIColor.lightGray 99 | pageIndicator.pageIndicatorTintColor = UIColor.black 100 | slideshow.pageIndicator = pageIndicator 101 | ``` 102 | 103 | Also, a simple label page indicator that shows pages in style "5/21" (fifth page from twenty one) is provided: 104 | 105 | ```swift 106 | slideshow.pageIndicator = LabelPageIndicator() 107 | ``` 108 | 109 | You can also use your own page indicator by adopting the `PageIndicatorView` protocol. 110 | 111 | Position of the page indicator can be configured by assigning a `PageIndicatorPosition` value to the `pageIndicatorPosition` property on ImageSlideshow. You may specify the horizontal and vertical positioning separately. 112 | 113 | **Horizontal** positioning options are: `.left(padding: Int)`, `.center`, `.right(padding: Int)` 114 | 115 | **Vertical** positioning options are: `.top`, `.bottom`, `.under`, `customTop(padding: Int)`, `customBottom(padding: Int)`, `customUnder(padding: Int)` 116 | 117 | Example: 118 | ```swift 119 | slideshow.pageIndicatorPosition = PageIndicatorPosition(horizontal: .left(padding: 20), vertical: .bottom) 120 | ``` 121 | 122 | 123 | ### Activity Indicator 124 | 125 | By default activity indicator is not shown, but you can enable it by setting `DefaultActivityIndicator` instance to Image Slideshow: 126 | 127 | ```swift 128 | slideshow.activityIndicator = DefaultActivityIndicator() 129 | ``` 130 | 131 | You can customize style and color of the indicator: 132 | 133 | ```swift 134 | slideshow.activityIndicator = DefaultActivityIndicator(style: .white, color: nil) 135 | ``` 136 | 137 | There's also an option to use your own activity indicator. You just need to implement `ActivityIndicatorView` and `ActivityIndicatorFactory` protocols. See `ActivityIndicator.swift` for more information. 138 | 139 | ### Full Screen view 140 | 141 | There is also a possibility to open full-screen image view using attached `FullScreenSlideshowViewController`. The simplest way is to call: 142 | 143 | ```swift 144 | override func viewDidLoad() { 145 | let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.didTap)) 146 | slideshow.addGestureRecognizer(gestureRecognizer) 147 | } 148 | 149 | func didTap() { 150 | slideshow.presentFullScreenController(from: self) 151 | } 152 | ``` 153 | 154 | `FullScreenSlideshowViewController` can also be instantiated and configured manually if more advanced behavior is needed. 155 | 156 | ## 👤 Author 157 | 158 | Petr Zvoníček 159 | 160 | ## 📄 License 161 | 162 | ImageSlideshow is available under the MIT license. See the LICENSE file for more info. 163 | 164 | ## 👀 References 165 | 166 | Inspired by projects: 167 | - https://github.com/gonzalezreal/Vertigo 168 | - https://github.com/kimar/KIImagePager 169 | -------------------------------------------------------------------------------- /Example/ImageSlideshow/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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 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 | 109 | 110 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/ImageSlideshowItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZoomablePhotoView.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 30.07.15. 6 | // 7 | 8 | import UIKit 9 | 10 | /// Used to wrap a single slideshow item and allow zooming on it 11 | @objcMembers 12 | open class ImageSlideshowItem: UIScrollView, UIScrollViewDelegate { 13 | 14 | /// Image view to hold the image 15 | public let imageView = UIImageView() 16 | 17 | /// Activity indicator shown during image loading, when nil there won't be shown any 18 | public let activityIndicator: ActivityIndicatorView? 19 | 20 | /// Input Source for the item 21 | public let image: InputSource 22 | 23 | /// Guesture recognizer to detect double tap to zoom 24 | open var gestureRecognizer: UITapGestureRecognizer? 25 | 26 | /// Holds if the zoom feature is enabled 27 | public let zoomEnabled: Bool 28 | 29 | /// If set to true image is initially zoomed in 30 | open var zoomInInitially = false 31 | 32 | /// Maximum zoom scale 33 | open var maximumScale: CGFloat = 2.0 34 | 35 | fileprivate var lastFrame = CGRect.zero 36 | fileprivate var imageReleased = false 37 | fileprivate var isLoading = false 38 | fileprivate var singleTapGestureRecognizer: UITapGestureRecognizer? 39 | fileprivate var loadFailed = false { 40 | didSet { 41 | singleTapGestureRecognizer?.isEnabled = loadFailed 42 | gestureRecognizer?.isEnabled = !loadFailed 43 | } 44 | } 45 | 46 | /// Wraps around ImageView so RTL transformation on it doesn't interfere with UIScrollView zooming 47 | private let imageViewWrapper = UIView() 48 | 49 | // MARK: - Life cycle 50 | 51 | /** 52 | Initializes a new ImageSlideshowItem 53 | - parameter image: Input Source to load the image 54 | - parameter zoomEnabled: holds if it should be possible to zoom-in the image 55 | */ 56 | init(image: InputSource, zoomEnabled: Bool, activityIndicator: ActivityIndicatorView? = nil, maximumScale: CGFloat = 2.0) { 57 | self.zoomEnabled = zoomEnabled 58 | self.image = image 59 | self.activityIndicator = activityIndicator 60 | self.maximumScale = maximumScale 61 | 62 | super.init(frame: CGRect.null) 63 | 64 | imageViewWrapper.addSubview(imageView) 65 | imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 66 | imageView.isAccessibilityElement = true 67 | imageView.accessibilityTraits = .image 68 | if #available(iOS 11.0, *) { 69 | imageView.accessibilityIgnoresInvertColors = true 70 | } 71 | 72 | imageViewWrapper.clipsToBounds = true 73 | imageViewWrapper.isUserInteractionEnabled = true 74 | if UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft { 75 | imageView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi)) 76 | } 77 | 78 | setPictoCenter() 79 | 80 | // scroll view configuration 81 | delegate = self 82 | showsVerticalScrollIndicator = false 83 | showsHorizontalScrollIndicator = false 84 | addSubview(imageViewWrapper) 85 | minimumZoomScale = 1.0 86 | maximumZoomScale = calculateMaximumScale() 87 | 88 | if let activityIndicator = activityIndicator { 89 | addSubview(activityIndicator.view) 90 | } 91 | 92 | // tap gesture recognizer 93 | let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(ImageSlideshowItem.tapZoom)) 94 | tapRecognizer.numberOfTapsRequired = 2 95 | imageViewWrapper.addGestureRecognizer(tapRecognizer) 96 | gestureRecognizer = tapRecognizer 97 | 98 | singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(retryLoadImage)) 99 | singleTapGestureRecognizer!.numberOfTapsRequired = 1 100 | singleTapGestureRecognizer!.isEnabled = false 101 | imageViewWrapper.addGestureRecognizer(singleTapGestureRecognizer!) 102 | } 103 | 104 | required public init?(coder aDecoder: NSCoder) { 105 | fatalError("init(coder:) has not been implemented") 106 | } 107 | 108 | override open func layoutSubviews() { 109 | super.layoutSubviews() 110 | 111 | if !zoomEnabled { 112 | imageViewWrapper.frame.size = frame.size 113 | } else if !isZoomed() { 114 | imageViewWrapper.frame.size = calculatePictureSize() 115 | } 116 | 117 | if isFullScreen() { 118 | clearContentInsets() 119 | } else { 120 | setPictoCenter() 121 | } 122 | 123 | self.activityIndicator?.view.center = imageViewWrapper.center 124 | 125 | // if self.frame was changed and zoomInInitially enabled, zoom in 126 | if lastFrame != frame && zoomInInitially { 127 | setZoomScale(maximumZoomScale, animated: false) 128 | } 129 | 130 | lastFrame = self.frame 131 | 132 | contentSize = imageViewWrapper.frame.size 133 | maximumZoomScale = calculateMaximumScale() 134 | } 135 | 136 | /// Request to load Image Source to Image View 137 | public func loadImage() { 138 | if self.imageView.image == nil && !isLoading { 139 | isLoading = true 140 | imageReleased = false 141 | activityIndicator?.show() 142 | image.load(to: self.imageView) {[weak self] image in 143 | // set image to nil if there was a release request during the image load 144 | if let imageRelease = self?.imageReleased, imageRelease { 145 | self?.imageView.image = nil 146 | } else { 147 | self?.imageView.image = image 148 | } 149 | self?.activityIndicator?.hide() 150 | self?.loadFailed = image == nil 151 | self?.isLoading = false 152 | 153 | self?.setNeedsLayout() 154 | } 155 | } 156 | } 157 | 158 | func releaseImage() { 159 | imageReleased = true 160 | cancelPendingLoad() 161 | self.imageView.image = nil 162 | } 163 | 164 | public func cancelPendingLoad() { 165 | image.cancelLoad?(on: imageView) 166 | } 167 | 168 | func retryLoadImage() { 169 | self.loadImage() 170 | } 171 | 172 | // MARK: - Image zoom & size 173 | 174 | func isZoomed() -> Bool { 175 | return self.zoomScale != self.minimumZoomScale 176 | } 177 | 178 | func zoomOut() { 179 | self.setZoomScale(minimumZoomScale, animated: false) 180 | } 181 | 182 | func tapZoom() { 183 | if isZoomed() { 184 | self.setZoomScale(minimumZoomScale, animated: true) 185 | } else { 186 | self.setZoomScale(maximumZoomScale, animated: true) 187 | } 188 | } 189 | 190 | fileprivate func screenSize() -> CGSize { 191 | return CGSize(width: frame.width, height: frame.height) 192 | } 193 | 194 | fileprivate func calculatePictureSize() -> CGSize { 195 | if let image = imageView.image, imageView.contentMode == .scaleAspectFit { 196 | let picSize = image.size 197 | let picRatio = picSize.width / picSize.height 198 | let screenRatio = screenSize().width / screenSize().height 199 | 200 | if picRatio > screenRatio { 201 | return CGSize(width: screenSize().width, height: screenSize().width / picSize.width * picSize.height) 202 | } else { 203 | return CGSize(width: screenSize().height / picSize.height * picSize.width, height: screenSize().height) 204 | } 205 | } else { 206 | return CGSize(width: screenSize().width, height: screenSize().height) 207 | } 208 | } 209 | 210 | fileprivate func calculateMaximumScale() -> CGFloat { 211 | return maximumScale 212 | } 213 | 214 | fileprivate func setPictoCenter() { 215 | var intendHorizon = (screenSize().width - imageViewWrapper.frame.width ) / 2 216 | var intendVertical = (screenSize().height - imageViewWrapper.frame.height ) / 2 217 | intendHorizon = intendHorizon > 0 ? intendHorizon : 0 218 | intendVertical = intendVertical > 0 ? intendVertical : 0 219 | contentInset = UIEdgeInsets(top: intendVertical, left: intendHorizon, bottom: intendVertical, right: intendHorizon) 220 | } 221 | 222 | private func isFullScreen() -> Bool { 223 | return imageViewWrapper.frame.width >= screenSize().width && imageViewWrapper.frame.height >= screenSize().height 224 | } 225 | 226 | func clearContentInsets() { 227 | contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) 228 | } 229 | 230 | // MARK: UIScrollViewDelegate 231 | 232 | open func scrollViewDidZoom(_ scrollView: UIScrollView) { 233 | setPictoCenter() 234 | } 235 | 236 | open func viewForZooming(in scrollView: UIScrollView) -> UIView? { 237 | return zoomEnabled ? imageViewWrapper : nil 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/ZoomAnimatedTransitioning.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZoomAnimatedTransitioning.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 31.08.15. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | @objcMembers 12 | open class ZoomAnimatedTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { 13 | /// parent image view used for animated transition 14 | open var referenceImageView: UIImageView? 15 | /// parent slideshow view used for animated transition 16 | open weak var referenceSlideshowView: ImageSlideshow? 17 | 18 | // must be weak because FullScreenSlideshowViewController has strong reference to its transitioning delegate 19 | weak var referenceSlideshowController: FullScreenSlideshowViewController? 20 | 21 | var referenceSlideshowViewFrame: CGRect? 22 | var gestureRecognizer: UIPanGestureRecognizer! 23 | fileprivate var interactionController: UIPercentDrivenInteractiveTransition? 24 | 25 | /// Enables or disables swipe-to-dismiss interactive transition 26 | open var slideToDismissEnabled: Bool = true 27 | 28 | /** 29 | Init the transitioning delegate with a source ImageSlideshow 30 | - parameter slideshowView: ImageSlideshow instance to animate the transition from 31 | - parameter slideshowController: FullScreenViewController instance to animate the transition to 32 | */ 33 | public init(slideshowView: ImageSlideshow, slideshowController: FullScreenSlideshowViewController) { 34 | self.referenceSlideshowView = slideshowView 35 | self.referenceSlideshowController = slideshowController 36 | 37 | super.init() 38 | 39 | initialize() 40 | } 41 | 42 | /** 43 | Init the transitioning delegate with a source ImageView 44 | - parameter imageView: UIImageView instance to animate the transition from 45 | - parameter slideshowController: FullScreenViewController instance to animate the transition to 46 | */ 47 | public init(imageView: UIImageView, slideshowController: FullScreenSlideshowViewController) { 48 | self.referenceImageView = imageView 49 | self.referenceSlideshowController = slideshowController 50 | 51 | super.init() 52 | 53 | initialize() 54 | } 55 | 56 | func initialize() { 57 | // Pan gesture recognizer for interactive dismiss 58 | gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ZoomAnimatedTransitioningDelegate.handleSwipe(_:))) 59 | gestureRecognizer.delegate = self 60 | // Append it to a window otherwise it will be canceled during the transition 61 | UIApplication.shared.keyWindow?.addGestureRecognizer(gestureRecognizer) 62 | } 63 | 64 | func handleSwipe(_ gesture: UIPanGestureRecognizer) { 65 | guard let referenceSlideshowController = referenceSlideshowController else { 66 | return 67 | } 68 | 69 | let percent = min(max(abs(gesture.translation(in: gesture.view!).y) / 200.0, 0.0), 1.0) 70 | 71 | if gesture.state == .began { 72 | interactionController = UIPercentDrivenInteractiveTransition() 73 | referenceSlideshowController.dismiss(animated: true, completion: nil) 74 | } else if gesture.state == .changed { 75 | interactionController?.update(percent) 76 | } else if gesture.state == .ended || gesture.state == .cancelled || gesture.state == .failed { 77 | let velocity = gesture.velocity(in: referenceSlideshowView) 78 | 79 | if abs(velocity.y) > 500 { 80 | if let pageSelected = referenceSlideshowController.pageSelected { 81 | pageSelected(referenceSlideshowController.slideshow.currentPage) 82 | } 83 | 84 | interactionController?.finish() 85 | } else if percent > 0.5 { 86 | if let pageSelected = referenceSlideshowController.pageSelected { 87 | pageSelected(referenceSlideshowController.slideshow.currentPage) 88 | } 89 | 90 | interactionController?.finish() 91 | } else { 92 | interactionController?.cancel() 93 | } 94 | 95 | interactionController = nil 96 | } 97 | } 98 | 99 | open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 100 | if let reference = referenceSlideshowView { 101 | return ZoomInAnimator(referenceSlideshowView: reference, parent: self) 102 | } else if let reference = referenceImageView { 103 | return ZoomInAnimator(referenceImageView: reference, parent: self) 104 | } else { 105 | return nil 106 | } 107 | } 108 | 109 | open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 110 | if let reference = referenceSlideshowView { 111 | return ZoomOutAnimator(referenceSlideshowView: reference, parent: self) 112 | } else if let reference = referenceImageView { 113 | return ZoomOutAnimator(referenceImageView: reference, parent: self) 114 | } else { 115 | return nil 116 | } 117 | } 118 | 119 | open func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 120 | return interactionController 121 | } 122 | 123 | open func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 124 | return interactionController 125 | } 126 | 127 | public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { 128 | return PresentationController(presentedViewController: presented, presenting: presenting) 129 | } 130 | } 131 | 132 | private class PresentationController: UIPresentationController { 133 | // Needed for interactive dismiss to keep the presenter View Controller visible 134 | override var shouldRemovePresentersView: Bool { 135 | return false 136 | } 137 | } 138 | 139 | extension ZoomAnimatedTransitioningDelegate: UIGestureRecognizerDelegate { 140 | public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { 141 | guard let gestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else { 142 | return false 143 | } 144 | 145 | if !slideToDismissEnabled { 146 | return false 147 | } 148 | 149 | if let currentItem = referenceSlideshowController?.slideshow.currentSlideshowItem, currentItem.isZoomed() { 150 | return false 151 | } 152 | 153 | if let view = gestureRecognizer.view { 154 | let velocity = gestureRecognizer.velocity(in: view) 155 | return abs(velocity.x) < abs(velocity.y) 156 | } 157 | 158 | return true 159 | } 160 | } 161 | 162 | @objcMembers 163 | class ZoomAnimator: NSObject { 164 | 165 | var referenceImageView: UIImageView? 166 | var referenceSlideshowView: ImageSlideshow? 167 | var parent: ZoomAnimatedTransitioningDelegate 168 | 169 | init(referenceSlideshowView: ImageSlideshow, parent: ZoomAnimatedTransitioningDelegate) { 170 | self.referenceSlideshowView = referenceSlideshowView 171 | self.referenceImageView = referenceSlideshowView.currentSlideshowItem?.imageView 172 | self.parent = parent 173 | super.init() 174 | } 175 | 176 | init(referenceImageView: UIImageView, parent: ZoomAnimatedTransitioningDelegate) { 177 | self.referenceImageView = referenceImageView 178 | self.parent = parent 179 | super.init() 180 | } 181 | } 182 | 183 | @objcMembers 184 | class ZoomInAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { 185 | 186 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 187 | return 0.5 188 | } 189 | 190 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 191 | // Pauses slideshow 192 | self.referenceSlideshowView?.pauseTimer() 193 | 194 | let containerView = transitionContext.containerView 195 | let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! 196 | 197 | guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? FullScreenSlideshowViewController else { 198 | return 199 | } 200 | 201 | toViewController.view.frame = transitionContext.finalFrame(for: toViewController) 202 | 203 | let transitionBackgroundView = UIView(frame: containerView.frame) 204 | transitionBackgroundView.backgroundColor = toViewController.backgroundColor 205 | containerView.addSubview(transitionBackgroundView) 206 | 207 | #if swift(>=4.2) 208 | containerView.sendSubviewToBack(transitionBackgroundView) 209 | #else 210 | containerView.sendSubview(toBack: transitionBackgroundView) 211 | #endif 212 | 213 | let finalFrame = toViewController.view.frame 214 | 215 | var transitionView: UIImageView? 216 | var transitionViewFinalFrame = finalFrame 217 | if let referenceImageView = referenceImageView { 218 | transitionView = UIImageView(image: referenceImageView.image) 219 | transitionView!.contentMode = UIViewContentMode.scaleAspectFill 220 | transitionView!.clipsToBounds = true 221 | transitionView!.frame = containerView.convert(referenceImageView.bounds, from: referenceImageView) 222 | containerView.addSubview(transitionView!) 223 | self.parent.referenceSlideshowViewFrame = transitionView!.frame 224 | 225 | referenceImageView.alpha = 0 226 | 227 | if let image = referenceImageView.image { 228 | transitionViewFinalFrame = image.tgr_aspectFitRectForSize(finalFrame.size) 229 | } 230 | } 231 | 232 | if let item = toViewController.slideshow.currentSlideshowItem, item.zoomInInitially { 233 | transitionViewFinalFrame.size = CGSize(width: transitionViewFinalFrame.size.width * item.maximumZoomScale, height: transitionViewFinalFrame.size.height * item.maximumZoomScale) 234 | } 235 | 236 | let duration: TimeInterval = transitionDuration(using: transitionContext) 237 | 238 | UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0, options: UIViewAnimationOptions.curveLinear, animations: { 239 | fromViewController.view.alpha = 0 240 | transitionView?.frame = transitionViewFinalFrame 241 | transitionView?.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY) 242 | }, completion: {[ref = self.referenceImageView] _ in 243 | fromViewController.view.alpha = 1 244 | ref?.alpha = 1 245 | transitionView?.removeFromSuperview() 246 | transitionBackgroundView.removeFromSuperview() 247 | containerView.addSubview(toViewController.view) 248 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 249 | }) 250 | } 251 | } 252 | 253 | class ZoomOutAnimator: ZoomAnimator, UIViewControllerAnimatedTransitioning { 254 | 255 | private var animatorForCurrentTransition: UIViewImplicitlyAnimating? 256 | 257 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 258 | return 0.25 259 | } 260 | 261 | @available(iOS 10.0, *) 262 | func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating { 263 | // as per documentation, the same object should be returned for the ongoing transition 264 | if let animatorForCurrentSession = animatorForCurrentTransition { 265 | return animatorForCurrentSession 266 | } 267 | 268 | let params = animationParams(using: transitionContext) 269 | 270 | let animator = UIViewPropertyAnimator(duration: params.0, curve: .linear, animations: params.1) 271 | animator.addCompletion(params.2) 272 | animatorForCurrentTransition = animator 273 | 274 | return animator 275 | } 276 | 277 | private func animationParams(using transitionContext: UIViewControllerContextTransitioning) -> (TimeInterval, () -> Void, (Any) -> Void) { 278 | let toViewController: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! 279 | 280 | guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? FullScreenSlideshowViewController else { 281 | fatalError("Transition not used with FullScreenSlideshowViewController") 282 | } 283 | 284 | let containerView = transitionContext.containerView 285 | 286 | var transitionViewInitialFrame: CGRect 287 | if let currentSlideshowItem = fromViewController.slideshow.currentSlideshowItem { 288 | if let image = currentSlideshowItem.imageView.image { 289 | transitionViewInitialFrame = image.tgr_aspectFitRectForSize(currentSlideshowItem.imageView.frame.size) 290 | } else { 291 | transitionViewInitialFrame = currentSlideshowItem.imageView.frame 292 | } 293 | transitionViewInitialFrame = containerView.convert(transitionViewInitialFrame, from: currentSlideshowItem) 294 | } else { 295 | transitionViewInitialFrame = fromViewController.slideshow.frame 296 | } 297 | 298 | var transitionViewFinalFrame: CGRect 299 | if let referenceImageView = referenceImageView { 300 | referenceImageView.alpha = 0 301 | 302 | let referenceSlideshowViewFrame = containerView.convert(referenceImageView.bounds, from: referenceImageView) 303 | transitionViewFinalFrame = referenceSlideshowViewFrame 304 | 305 | // do a frame scaling when AspectFit content mode enabled 306 | if fromViewController.slideshow.currentSlideshowItem?.imageView.image != nil && referenceImageView.contentMode == UIViewContentMode.scaleAspectFit { 307 | transitionViewFinalFrame = containerView.convert(referenceImageView.aspectToFitFrame(), from: referenceImageView) 308 | } 309 | 310 | // fixes the problem when the referenceSlideshowViewFrame was shifted during change of the status bar hidden state 311 | if UIApplication.shared.isStatusBarHidden && !toViewController.prefersStatusBarHidden && referenceSlideshowViewFrame.origin.y != parent.referenceSlideshowViewFrame?.origin.y { 312 | transitionViewFinalFrame = transitionViewFinalFrame.offsetBy(dx: 0, dy: 20) 313 | } 314 | } else { 315 | transitionViewFinalFrame = referenceSlideshowView?.frame ?? CGRect.zero 316 | } 317 | 318 | let transitionBackgroundView = UIView(frame: containerView.frame) 319 | transitionBackgroundView.backgroundColor = fromViewController.backgroundColor 320 | containerView.addSubview(transitionBackgroundView) 321 | #if swift(>=4.2) 322 | containerView.sendSubviewToBack(transitionBackgroundView) 323 | #else 324 | containerView.sendSubview(toBack: transitionBackgroundView) 325 | #endif 326 | 327 | let transitionView = UIImageView(image: fromViewController.slideshow.currentSlideshowItem?.imageView.image) 328 | transitionView.contentMode = UIViewContentMode.scaleAspectFill 329 | transitionView.clipsToBounds = true 330 | transitionView.frame = transitionViewInitialFrame 331 | containerView.addSubview(transitionView) 332 | fromViewController.view.isHidden = true 333 | 334 | let duration = transitionDuration(using: transitionContext) 335 | let animations = { 336 | transitionBackgroundView.alpha = 0 337 | transitionView.frame = transitionViewFinalFrame 338 | } 339 | let completion = { (_: Any) in 340 | let completed = !transitionContext.transitionWasCancelled 341 | self.referenceImageView?.alpha = 1 342 | 343 | if completed { 344 | fromViewController.view.removeFromSuperview() 345 | UIApplication.shared.keyWindow?.removeGestureRecognizer(self.parent.gestureRecognizer) 346 | // Unpauses slideshow 347 | self.referenceSlideshowView?.unpauseTimer() 348 | } else { 349 | fromViewController.view.isHidden = false 350 | } 351 | 352 | transitionView.removeFromSuperview() 353 | transitionBackgroundView.removeFromSuperview() 354 | 355 | self.animatorForCurrentTransition = nil 356 | 357 | transitionContext.completeTransition(completed) 358 | } 359 | 360 | return (duration, animations, completion) 361 | } 362 | 363 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 364 | // Working around iOS 10+ breaking change requiring to use UIPropertyAnimator for proper interactive transition instead of UIView.animate 365 | if #available(iOS 10.0, *) { 366 | interruptibleAnimator(using: transitionContext).startAnimation() 367 | } else { 368 | let params = animationParams(using: transitionContext) 369 | UIView.animate(withDuration: params.0, delay: 0, options: UIViewAnimationOptions(), animations: params.1, completion: params.2) 370 | } 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /ImageSlideshow/Classes/Core/ImageSlideshow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageSlideshow.swift 3 | // ImageSlideshow 4 | // 5 | // Created by Petr Zvoníček on 30.07.15. 6 | // 7 | 8 | import UIKit 9 | 10 | @objc 11 | /// The delegate protocol informing about image slideshow state changes 12 | public protocol ImageSlideshowDelegate: class { 13 | /// Tells the delegate that the current page has changed 14 | /// 15 | /// - Parameters: 16 | /// - imageSlideshow: image slideshow instance 17 | /// - page: new page 18 | @objc optional func imageSlideshow(_ imageSlideshow: ImageSlideshow, didChangeCurrentPageTo page: Int) 19 | 20 | /// Tells the delegate that the slideshow will begin dragging 21 | /// 22 | /// - Parameter imageSlideshow: image slideshow instance 23 | @objc optional func imageSlideshowWillBeginDragging(_ imageSlideshow: ImageSlideshow) 24 | 25 | /// Tells the delegate that the slideshow did end decelerating 26 | /// 27 | /// - Parameter imageSlideshow: image slideshow instance 28 | @objc optional func imageSlideshowDidEndDecelerating(_ imageSlideshow: ImageSlideshow) 29 | } 30 | 31 | /** 32 | Used to represent position of the Page Control 33 | - hidden: Page Control is hidden 34 | - insideScrollView: Page Control is inside image slideshow 35 | - underScrollView: Page Control is under image slideshow 36 | - custom: Custom vertical padding, relative to "insideScrollView" position 37 | */ 38 | public enum PageControlPosition { 39 | case hidden 40 | case insideScrollView 41 | case underScrollView 42 | case custom(padding: CGFloat) 43 | } 44 | 45 | /// Used to represent image preload strategy 46 | /// 47 | /// - fixed: preload only fixed number of images before and after the current image 48 | /// - all: preload all images in the slideshow 49 | public enum ImagePreload { 50 | case fixed(offset: Int) 51 | case all 52 | } 53 | 54 | /// Main view containing the Image Slideshow 55 | @objcMembers 56 | open class ImageSlideshow: UIView { 57 | 58 | /// Scroll View to wrap the slideshow 59 | public let scrollView = UIScrollView() 60 | 61 | /// Page Control shown in the slideshow 62 | @available(*, deprecated, message: "Use pageIndicator.view instead") 63 | open var pageControl: UIPageControl { 64 | if let pageIndicator = pageIndicator as? UIPageControl { 65 | return pageIndicator 66 | } 67 | fatalError("pageIndicator is not an instance of UIPageControl") 68 | } 69 | 70 | /// Activity indicator shown when loading image 71 | open var activityIndicator: ActivityIndicatorFactory? { 72 | didSet { 73 | reloadScrollView() 74 | } 75 | } 76 | 77 | open var pageIndicator: PageIndicatorView? { 78 | didSet { 79 | oldValue?.view.removeFromSuperview() 80 | if let pageIndicator = pageIndicator { 81 | addSubview(pageIndicator.view) 82 | if let pageIndicator = pageIndicator as? UIControl { 83 | pageIndicator.addTarget(self, action: #selector(pageControlValueChanged), for: .valueChanged) 84 | } 85 | } 86 | setNeedsLayout() 87 | } 88 | } 89 | 90 | open var pageIndicatorPosition: PageIndicatorPosition = PageIndicatorPosition() { 91 | didSet { 92 | setNeedsLayout() 93 | } 94 | } 95 | 96 | // MARK: - State properties 97 | 98 | /// Page control position 99 | @available(*, deprecated, message: "Use pageIndicatorPosition instead") 100 | open var pageControlPosition = PageControlPosition.insideScrollView { 101 | didSet { 102 | pageIndicator = UIPageControl() 103 | switch pageControlPosition { 104 | case .hidden: 105 | pageIndicator = nil 106 | case .insideScrollView: 107 | pageIndicatorPosition = PageIndicatorPosition(vertical: .bottom) 108 | case .underScrollView: 109 | pageIndicatorPosition = PageIndicatorPosition(vertical: .under) 110 | case .custom(let padding): 111 | pageIndicatorPosition = PageIndicatorPosition(vertical: .customUnder(padding: padding-30)) 112 | } 113 | } 114 | } 115 | 116 | /// Current page 117 | open fileprivate(set) var currentPage: Int = 0 { 118 | didSet { 119 | if oldValue != currentPage { 120 | pageIndicator?.page = currentPage 121 | currentPageChanged?(currentPage) 122 | delegate?.imageSlideshow?(self, didChangeCurrentPageTo: currentPage) 123 | } 124 | } 125 | } 126 | 127 | /// Delegate called on image slideshow state change 128 | open weak var delegate: ImageSlideshowDelegate? 129 | 130 | /// Called on each currentPage change 131 | open var currentPageChanged: ((_ page: Int) -> Void)? 132 | 133 | /// Called on scrollViewWillBeginDragging 134 | open var willBeginDragging: (() -> Void)? 135 | 136 | /// Called on scrollViewDidEndDecelerating 137 | open var didEndDecelerating: (() -> Void)? 138 | 139 | /// Currenlty displayed slideshow item 140 | open var currentSlideshowItem: ImageSlideshowItem? { 141 | if slideshowItems.count > scrollViewPage { 142 | return slideshowItems[scrollViewPage] 143 | } else { 144 | return nil 145 | } 146 | } 147 | 148 | /// Current scroll view page. This may differ from `currentPage` as circular slider has two more dummy pages at indexes 0 and n-1 to provide fluent scrolling between first and last item. 149 | open fileprivate(set) var scrollViewPage: Int = 0 150 | 151 | /// Input Sources loaded to slideshow 152 | open fileprivate(set) var images = [InputSource]() 153 | 154 | /// Image Slideshow Items loaded to slideshow 155 | open fileprivate(set) var slideshowItems = [ImageSlideshowItem]() 156 | 157 | // MARK: - Preferences 158 | 159 | /// Enables/disables infinite scrolling between images 160 | open var circular = true { 161 | didSet { 162 | if images.count > 0 { 163 | setImageInputs(images) 164 | } 165 | } 166 | } 167 | 168 | /// Enables/disables user interactions 169 | open var draggingEnabled = true { 170 | didSet { 171 | scrollView.isUserInteractionEnabled = draggingEnabled 172 | } 173 | } 174 | 175 | /// Enables/disables zoom 176 | open var zoomEnabled = false { 177 | didSet { 178 | reloadScrollView() 179 | } 180 | } 181 | 182 | /// Maximum zoom scale 183 | open var maximumScale: CGFloat = 2.0 { 184 | didSet { 185 | reloadScrollView() 186 | } 187 | } 188 | 189 | /// Image change interval, zero stops the auto-scrolling 190 | open var slideshowInterval = 0.0 { 191 | didSet { 192 | slideshowTimer?.invalidate() 193 | slideshowTimer = nil 194 | setTimerIfNeeded() 195 | } 196 | } 197 | 198 | /// Image preload configuration, can be sed to .fixed to enable lazy load or .all 199 | open var preload = ImagePreload.all 200 | 201 | /// Content mode of each image in the slideshow 202 | open var contentScaleMode: UIViewContentMode = UIViewContentMode.scaleAspectFit { 203 | didSet { 204 | for view in slideshowItems { 205 | view.imageView.contentMode = contentScaleMode 206 | } 207 | } 208 | } 209 | 210 | fileprivate var slideshowTimer: Timer? 211 | fileprivate var scrollViewImages = [InputSource]() 212 | fileprivate var isAnimating: Bool = false 213 | 214 | /// Transitioning delegate to manage the transition to full screen controller 215 | open fileprivate(set) var slideshowTransitioningDelegate: ZoomAnimatedTransitioningDelegate? // swiftlint:disable:this weak_delegate 216 | 217 | private var primaryVisiblePage: Int { 218 | return scrollView.frame.size.width > 0 ? Int(scrollView.contentOffset.x + scrollView.frame.size.width / 2) / Int(scrollView.frame.size.width) : 0 219 | } 220 | 221 | // MARK: - Life cycle 222 | 223 | override public init(frame: CGRect) { 224 | super.init(frame: frame) 225 | initialize() 226 | } 227 | 228 | convenience init() { 229 | self.init(frame: CGRect.zero) 230 | } 231 | 232 | required public init?(coder aDecoder: NSCoder) { 233 | super.init(coder: aDecoder) 234 | initialize() 235 | } 236 | 237 | fileprivate func initialize() { 238 | autoresizesSubviews = true 239 | clipsToBounds = true 240 | if #available(iOS 13.0, *) { 241 | backgroundColor = .systemBackground 242 | } 243 | 244 | // scroll view configuration 245 | scrollView.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height - 50.0) 246 | scrollView.delegate = self 247 | scrollView.isPagingEnabled = true 248 | scrollView.bounces = true 249 | scrollView.showsHorizontalScrollIndicator = false 250 | scrollView.showsVerticalScrollIndicator = false 251 | scrollView.autoresizingMask = autoresizingMask 252 | if UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft { 253 | scrollView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi)) 254 | } 255 | 256 | if #available(iOS 11.0, *) { 257 | scrollView.contentInsetAdjustmentBehavior = .never 258 | } 259 | addSubview(scrollView) 260 | 261 | if pageIndicator == nil { 262 | pageIndicator = UIPageControl() 263 | } 264 | 265 | setTimerIfNeeded() 266 | layoutScrollView() 267 | } 268 | 269 | open override func removeFromSuperview() { 270 | super.removeFromSuperview() 271 | pauseTimer() 272 | } 273 | 274 | open override func layoutSubviews() { 275 | super.layoutSubviews() 276 | 277 | // fixes the case when automaticallyAdjustsScrollViewInsets on parenting view controller is set to true 278 | scrollView.contentInset = UIEdgeInsets.zero 279 | 280 | layoutPageControl() 281 | layoutScrollView() 282 | } 283 | 284 | open func layoutPageControl() { 285 | if let pageIndicatorView = pageIndicator?.view { 286 | pageIndicatorView.isHidden = images.count < 2 287 | 288 | var edgeInsets: UIEdgeInsets = UIEdgeInsets.zero 289 | if #available(iOS 11.0, *) { 290 | edgeInsets = safeAreaInsets 291 | } 292 | 293 | pageIndicatorView.sizeToFit() 294 | pageIndicatorView.frame = pageIndicatorPosition.indicatorFrame(for: frame, indicatorSize: pageIndicatorView.frame.size, edgeInsets: edgeInsets) 295 | } 296 | } 297 | 298 | /// updates frame of the scroll view and its inner items 299 | func layoutScrollView() { 300 | let pageIndicatorViewSize = pageIndicator?.view.frame.size 301 | let scrollViewBottomPadding = pageIndicatorViewSize.flatMap { pageIndicatorPosition.underPadding(for: $0) } ?? 0 302 | 303 | scrollView.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height - scrollViewBottomPadding) 304 | scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(scrollViewImages.count), height: scrollView.frame.size.height) 305 | 306 | for (index, view) in slideshowItems.enumerated() { 307 | if !view.zoomInInitially { 308 | view.zoomOut() 309 | } 310 | view.frame = CGRect(x: scrollView.frame.size.width * CGFloat(index), y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height) 311 | } 312 | 313 | setScrollViewPage(scrollViewPage, animated: false) 314 | } 315 | 316 | /// reloads scroll view with latest slideshow items 317 | func reloadScrollView() { 318 | // remove previous slideshow items 319 | for view in slideshowItems { 320 | view.removeFromSuperview() 321 | } 322 | slideshowItems = [] 323 | 324 | var i = 0 325 | for image in scrollViewImages { 326 | let item = ImageSlideshowItem(image: image, zoomEnabled: zoomEnabled, activityIndicator: activityIndicator?.create(), maximumScale: maximumScale) 327 | item.imageView.contentMode = contentScaleMode 328 | slideshowItems.append(item) 329 | scrollView.addSubview(item) 330 | i += 1 331 | } 332 | 333 | if circular && (scrollViewImages.count > 1) { 334 | scrollViewPage = 1 335 | scrollView.scrollRectToVisible(CGRect(x: scrollView.frame.size.width, y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height), animated: false) 336 | } else { 337 | scrollViewPage = 0 338 | } 339 | 340 | loadImages(for: scrollViewPage) 341 | } 342 | 343 | private func loadImages(for scrollViewPage: Int) { 344 | let totalCount = slideshowItems.count 345 | 346 | for i in 0.. totalCount-offset || circularEdgeLoad 357 | shouldLoad ? item.loadImage() : item.releaseImage() 358 | } 359 | } 360 | } 361 | 362 | // MARK: - Image setting 363 | 364 | /** 365 | Set image inputs into the image slideshow 366 | - parameter inputs: Array of InputSource instances. 367 | */ 368 | open func setImageInputs(_ inputs: [InputSource]) { 369 | images = inputs 370 | pageIndicator?.numberOfPages = inputs.count 371 | 372 | // in circular mode we add dummy first and last image to enable smooth scrolling 373 | if circular && images.count > 1 { 374 | var scImages = [InputSource]() 375 | 376 | if let last = images.last { 377 | scImages.append(last) 378 | } 379 | scImages += images 380 | if let first = images.first { 381 | scImages.append(first) 382 | } 383 | 384 | scrollViewImages = scImages 385 | } else { 386 | scrollViewImages = images 387 | } 388 | 389 | reloadScrollView() 390 | layoutScrollView() 391 | layoutPageControl() 392 | setTimerIfNeeded() 393 | } 394 | 395 | // MARK: paging methods 396 | 397 | /** 398 | Change the current page 399 | - parameter newPage: new page 400 | - parameter animated: true if animate the change 401 | */ 402 | open func setCurrentPage(_ newPage: Int, animated: Bool) { 403 | var pageOffset = newPage 404 | if circular && (scrollViewImages.count > 1) { 405 | pageOffset += 1 406 | } 407 | 408 | setScrollViewPage(pageOffset, animated: animated) 409 | } 410 | 411 | /** 412 | Change the scroll view page. This may differ from `setCurrentPage` as circular slider has two more dummy pages at indexes 0 and n-1 to provide fluent scrolling between first and last item. 413 | - parameter newScrollViewPage: new scroll view page 414 | - parameter animated: true if animate the change 415 | */ 416 | open func setScrollViewPage(_ newScrollViewPage: Int, animated: Bool) { 417 | if scrollViewPage < scrollViewImages.count { 418 | scrollView.scrollRectToVisible(CGRect(x: scrollView.frame.size.width * CGFloat(newScrollViewPage), y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height), animated: animated) 419 | setCurrentPageForScrollViewPage(newScrollViewPage) 420 | if animated { 421 | isAnimating = true 422 | } 423 | } 424 | } 425 | 426 | fileprivate func setTimerIfNeeded() { 427 | if slideshowInterval > 0 && scrollViewImages.count > 1 && slideshowTimer == nil { 428 | slideshowTimer = Timer.scheduledTimer(timeInterval: slideshowInterval, target: self, selector: #selector(ImageSlideshow.slideshowTick(_:)), userInfo: nil, repeats: true) 429 | } 430 | } 431 | 432 | func slideshowTick(_ timer: Timer) { 433 | let page = scrollView.frame.size.width > 0 ? Int(scrollView.contentOffset.x / scrollView.frame.size.width) : 0 434 | var nextPage = page + 1 435 | 436 | if !circular && page == scrollViewImages.count - 1 { 437 | nextPage = 0 438 | } 439 | 440 | setScrollViewPage(nextPage, animated: true) 441 | } 442 | 443 | fileprivate func setCurrentPageForScrollViewPage(_ page: Int) { 444 | if scrollViewPage != page { 445 | // current page has changed, zoom out this image 446 | if slideshowItems.count > scrollViewPage { 447 | slideshowItems[scrollViewPage].zoomOut() 448 | } 449 | } 450 | 451 | if page != scrollViewPage { 452 | loadImages(for: page) 453 | } 454 | scrollViewPage = page 455 | currentPage = currentPageForScrollViewPage(page) 456 | } 457 | 458 | fileprivate func currentPageForScrollViewPage(_ page: Int) -> Int { 459 | if circular { 460 | if page == 0 { 461 | // first page contains the last image 462 | return Int(images.count) - 1 463 | } else if page == scrollViewImages.count - 1 { 464 | // last page contains the first image 465 | return 0 466 | } else { 467 | return page - 1 468 | } 469 | } else { 470 | return page 471 | } 472 | } 473 | 474 | fileprivate func restartTimer() { 475 | if slideshowTimer?.isValid != nil { 476 | slideshowTimer?.invalidate() 477 | slideshowTimer = nil 478 | } 479 | 480 | setTimerIfNeeded() 481 | } 482 | 483 | /// Stops slideshow timer 484 | open func pauseTimer() { 485 | slideshowTimer?.invalidate() 486 | slideshowTimer = nil 487 | } 488 | 489 | /// Restarts slideshow timer 490 | open func unpauseTimer() { 491 | setTimerIfNeeded() 492 | } 493 | 494 | @available(*, deprecated, message: "use pauseTimer instead") 495 | open func pauseTimerIfNeeded() { 496 | pauseTimer() 497 | } 498 | 499 | @available(*, deprecated, message: "use unpauseTimer instead") 500 | open func unpauseTimerIfNeeded() { 501 | unpauseTimer() 502 | } 503 | 504 | /** 505 | Change the page to the next one 506 | - Parameter animated: true if animate the change 507 | */ 508 | open func nextPage(animated: Bool) { 509 | if !circular && currentPage == images.count - 1 { 510 | return 511 | } 512 | if isAnimating { 513 | return 514 | } 515 | 516 | setCurrentPage(currentPage + 1, animated: animated) 517 | restartTimer() 518 | } 519 | 520 | /** 521 | Change the page to the previous one 522 | - Parameter animated: true if animate the change 523 | */ 524 | open func previousPage(animated: Bool) { 525 | if !circular && currentPage == 0 { 526 | return 527 | } 528 | if isAnimating { 529 | return 530 | } 531 | 532 | let newPage = scrollViewPage > 0 ? scrollViewPage - 1 : scrollViewImages.count - 3 533 | setScrollViewPage(newPage, animated: animated) 534 | restartTimer() 535 | } 536 | 537 | /** 538 | Open full screen slideshow 539 | - parameter controller: Controller to present the full screen controller from 540 | - returns: FullScreenSlideshowViewController instance 541 | */ 542 | @discardableResult 543 | open func presentFullScreenController(from controller: UIViewController, completion: (() -> Void)? = nil) -> FullScreenSlideshowViewController { 544 | let fullscreen = FullScreenSlideshowViewController() 545 | fullscreen.pageSelected = {[weak self] (page: Int) in 546 | self?.setCurrentPage(page, animated: false) 547 | } 548 | 549 | fullscreen.initialPage = currentPage 550 | fullscreen.inputs = images 551 | slideshowTransitioningDelegate = ZoomAnimatedTransitioningDelegate(slideshowView: self, slideshowController: fullscreen) 552 | fullscreen.transitioningDelegate = slideshowTransitioningDelegate 553 | fullscreen.modalPresentationStyle = .custom 554 | controller.present(fullscreen, animated: true, completion: completion) 555 | 556 | return fullscreen 557 | } 558 | 559 | @objc private func pageControlValueChanged() { 560 | if let currentPage = pageIndicator?.page { 561 | setCurrentPage(currentPage, animated: true) 562 | } 563 | } 564 | } 565 | 566 | extension ImageSlideshow: UIScrollViewDelegate { 567 | 568 | open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 569 | restartTimer() 570 | willBeginDragging?() 571 | delegate?.imageSlideshowWillBeginDragging?(self) 572 | } 573 | 574 | open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 575 | setCurrentPageForScrollViewPage(primaryVisiblePage) 576 | didEndDecelerating?() 577 | delegate?.imageSlideshowDidEndDecelerating?(self) 578 | } 579 | 580 | open func scrollViewDidScroll(_ scrollView: UIScrollView) { 581 | if circular && (scrollViewImages.count > 1) { 582 | let regularContentOffset = scrollView.frame.size.width * CGFloat(images.count) 583 | 584 | if scrollView.contentOffset.x >= scrollView.frame.size.width * CGFloat(images.count + 1) { 585 | scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x - regularContentOffset, y: 0) 586 | } else if scrollView.contentOffset.x <= 0 { 587 | scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x + regularContentOffset, y: 0) 588 | } 589 | } 590 | 591 | // Updates the page indicator as the user scrolls (#204). Not called when not dragging to prevent flickers 592 | // when interacting with PageControl directly (#376). 593 | if scrollView.isDragging { 594 | pageIndicator?.page = currentPageForScrollViewPage(primaryVisiblePage) 595 | } 596 | } 597 | 598 | public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { 599 | isAnimating = false 600 | } 601 | } 602 | -------------------------------------------------------------------------------- /Example/ImageSlideshow.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 43F0FAD42568BAEB008FFD39 /* Bundle+Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F0FAD32568BAEB008FFD39 /* Bundle+Module.swift */; }; 11 | 48A9DE9233FBEA3CF63A8E54 /* Pods_ImageSlideshow_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AA07A6EDAC622F1407E6C80 /* Pods_ImageSlideshow_Example.framework */; }; 12 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 13 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 14 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 15 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 16 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 17 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; 18 | D007954F2043262C0053E25F /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007954E2043262C0053E25F /* TableViewController.swift */; }; 19 | D0083DCE1EB739E700126B21 /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0083DCD1EB739E700126B21 /* ActivityIndicator.swift */; }; 20 | D00C7A2720B4C0A100E5725B /* ic_cross_white@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D00C7A2520B4C0A000E5725B /* ic_cross_white@3x.png */; }; 21 | D00C7A2820B4C0A100E5725B /* ic_cross_white@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D00C7A2620B4C0A100E5725B /* ic_cross_white@2x.png */; }; 22 | D0B974B0202738F6006217CF /* PageIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B974AF202738F6006217CF /* PageIndicator.swift */; }; 23 | D0E8A9E61D97EB6D007EC517 /* ImageSlideshow_framework.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E8A9E41D97EB6D007EC517 /* ImageSlideshow_framework.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24 | D0E8A9F11D97EB94007EC517 /* FullScreenSlideshowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8A9EA1D97EB94007EC517 /* FullScreenSlideshowViewController.swift */; }; 25 | D0E8A9F21D97EB94007EC517 /* ImageSlideshow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8A9EB1D97EB94007EC517 /* ImageSlideshow.swift */; }; 26 | D0E8A9F31D97EB94007EC517 /* ImageSlideshowItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8A9EC1D97EB94007EC517 /* ImageSlideshowItem.swift */; }; 27 | D0E8A9F41D97EB94007EC517 /* InputSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8A9ED1D97EB94007EC517 /* InputSource.swift */; }; 28 | D0E8A9F51D97EB94007EC517 /* UIImage+AspectFit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8A9EE1D97EB94007EC517 /* UIImage+AspectFit.swift */; }; 29 | D0E8A9F61D97EB94007EC517 /* UIImageView+Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8A9EF1D97EB94007EC517 /* UIImageView+Tools.swift */; }; 30 | D0E8A9F71D97EB94007EC517 /* ZoomAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8A9F01D97EB94007EC517 /* ZoomAnimatedTransitioning.swift */; }; 31 | F539204C210F03610057EFB3 /* SwiftSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F539204B210F03600057EFB3 /* SwiftSupport.swift */; }; 32 | F802998F20CE9EA7009D64DD /* PageIndicatorPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = F802998E20CE9EA7009D64DD /* PageIndicatorPosition.swift */; }; 33 | F802999020CE9EA7009D64DD /* PageIndicatorPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = F802998E20CE9EA7009D64DD /* PageIndicatorPosition.swift */; }; 34 | F802999120CE9EA7009D64DD /* PageIndicatorPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = F802998E20CE9EA7009D64DD /* PageIndicatorPosition.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXContainerItemProxy section */ 38 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 43 | remoteInfo = ImageSlideshow; 44 | }; 45 | /* End PBXContainerItemProxy section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 09417F1351C21E0DCE8667BE /* Pods-ImageSlideshow_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageSlideshow_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-ImageSlideshow_Example/Pods-ImageSlideshow_Example.release.xcconfig"; sourceTree = ""; }; 49 | 1C4985EDB005E63A830176CE /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 50 | 43F0FAD32568BAEB008FFD39 /* Bundle+Module.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Bundle+Module.swift"; path = "../../ImageSlideshow/Classes/Core/Bundle+Module.swift"; sourceTree = ""; }; 51 | 5FD91AF3667236846934E8B9 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 52 | 607FACD01AFB9204008FA782 /* ImageSlideshow_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ImageSlideshow_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 55 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 56 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 58 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 59 | 607FACE51AFB9204008FA782 /* ImageSlideshow_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ImageSlideshow_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 62 | 749E212B852A3FE6757C1D36 /* ImageSlideshow.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = ImageSlideshow.podspec; path = ../ImageSlideshow.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 63 | 8AA07A6EDAC622F1407E6C80 /* Pods_ImageSlideshow_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ImageSlideshow_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | D007954E2043262C0053E25F /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 65 | D0083DCD1EB739E700126B21 /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ActivityIndicator.swift; path = ../../ImageSlideshow/Classes/Core/ActivityIndicator.swift; sourceTree = ""; }; 66 | D00C7A2520B4C0A000E5725B /* ic_cross_white@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "ic_cross_white@3x.png"; path = "../../../ImageSlideshow/Assets/ic_cross_white@3x.png"; sourceTree = ""; }; 67 | D00C7A2620B4C0A100E5725B /* ic_cross_white@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "ic_cross_white@2x.png"; path = "../../../ImageSlideshow/Assets/ic_cross_white@2x.png"; sourceTree = ""; }; 68 | D0B974AF202738F6006217CF /* PageIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PageIndicator.swift; path = ../../ImageSlideshow/Classes/Core/PageIndicator.swift; sourceTree = ""; }; 69 | D0E8A9E21D97EB6D007EC517 /* ImageSlideshow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ImageSlideshow.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | D0E8A9E41D97EB6D007EC517 /* ImageSlideshow_framework.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ImageSlideshow_framework.h; sourceTree = ""; }; 71 | D0E8A9E51D97EB6D007EC517 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 72 | D0E8A9EA1D97EB94007EC517 /* FullScreenSlideshowViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FullScreenSlideshowViewController.swift; path = ../../ImageSlideshow/Classes/Core/FullScreenSlideshowViewController.swift; sourceTree = ""; }; 73 | D0E8A9EB1D97EB94007EC517 /* ImageSlideshow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageSlideshow.swift; path = ../../ImageSlideshow/Classes/Core/ImageSlideshow.swift; sourceTree = ""; }; 74 | D0E8A9EC1D97EB94007EC517 /* ImageSlideshowItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageSlideshowItem.swift; path = ../../ImageSlideshow/Classes/Core/ImageSlideshowItem.swift; sourceTree = ""; }; 75 | D0E8A9ED1D97EB94007EC517 /* InputSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InputSource.swift; path = ../../ImageSlideshow/Classes/Core/InputSource.swift; sourceTree = ""; }; 76 | D0E8A9EE1D97EB94007EC517 /* UIImage+AspectFit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIImage+AspectFit.swift"; path = "../../ImageSlideshow/Classes/Core/UIImage+AspectFit.swift"; sourceTree = ""; }; 77 | D0E8A9EF1D97EB94007EC517 /* UIImageView+Tools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIImageView+Tools.swift"; path = "../../ImageSlideshow/Classes/Core/UIImageView+Tools.swift"; sourceTree = ""; }; 78 | D0E8A9F01D97EB94007EC517 /* ZoomAnimatedTransitioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ZoomAnimatedTransitioning.swift; path = ../../ImageSlideshow/Classes/Core/ZoomAnimatedTransitioning.swift; sourceTree = ""; }; 79 | F539204B210F03600057EFB3 /* SwiftSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftSupport.swift; path = ../../ImageSlideshow/Classes/Core/SwiftSupport.swift; sourceTree = ""; }; 80 | F802998E20CE9EA7009D64DD /* PageIndicatorPosition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PageIndicatorPosition.swift; path = ../../ImageSlideshow/Classes/Core/PageIndicatorPosition.swift; sourceTree = ""; }; 81 | FD45C56C18E7B8EC08371B86 /* Pods-ImageSlideshow_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageSlideshow_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ImageSlideshow_Example/Pods-ImageSlideshow_Example.debug.xcconfig"; sourceTree = ""; }; 82 | /* End PBXFileReference section */ 83 | 84 | /* Begin PBXFrameworksBuildPhase section */ 85 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | 48A9DE9233FBEA3CF63A8E54 /* Pods_ImageSlideshow_Example.framework in Frameworks */, 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | D0E8A9DE1D97EB6D007EC517 /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | ); 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | /* End PBXFrameworksBuildPhase section */ 108 | 109 | /* Begin PBXGroup section */ 110 | 607FACC71AFB9204008FA782 = { 111 | isa = PBXGroup; 112 | children = ( 113 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 114 | 607FACD21AFB9204008FA782 /* Example for ImageSlideshow */, 115 | 607FACE81AFB9204008FA782 /* Tests */, 116 | D0E8A9E31D97EB6D007EC517 /* ImageSlideshow_framework */, 117 | 607FACD11AFB9204008FA782 /* Products */, 118 | 67EF8AB7524DB4FF7F66A17E /* Pods */, 119 | 88C40B0495D05599E2889C22 /* Frameworks */, 120 | ); 121 | sourceTree = ""; 122 | }; 123 | 607FACD11AFB9204008FA782 /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 607FACD01AFB9204008FA782 /* ImageSlideshow_Example.app */, 127 | 607FACE51AFB9204008FA782 /* ImageSlideshow_Tests.xctest */, 128 | D0E8A9E21D97EB6D007EC517 /* ImageSlideshow.framework */, 129 | ); 130 | name = Products; 131 | sourceTree = ""; 132 | }; 133 | 607FACD21AFB9204008FA782 /* Example for ImageSlideshow */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 137 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 138 | D007954E2043262C0053E25F /* TableViewController.swift */, 139 | 607FACD91AFB9204008FA782 /* Main.storyboard */, 140 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 141 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 142 | 607FACD31AFB9204008FA782 /* Supporting Files */, 143 | ); 144 | name = "Example for ImageSlideshow"; 145 | path = ImageSlideshow; 146 | sourceTree = ""; 147 | }; 148 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 607FACD41AFB9204008FA782 /* Info.plist */, 152 | ); 153 | name = "Supporting Files"; 154 | sourceTree = ""; 155 | }; 156 | 607FACE81AFB9204008FA782 /* Tests */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 607FACEB1AFB9204008FA782 /* Tests.swift */, 160 | 607FACE91AFB9204008FA782 /* Supporting Files */, 161 | ); 162 | path = Tests; 163 | sourceTree = ""; 164 | }; 165 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 607FACEA1AFB9204008FA782 /* Info.plist */, 169 | ); 170 | name = "Supporting Files"; 171 | sourceTree = ""; 172 | }; 173 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 749E212B852A3FE6757C1D36 /* ImageSlideshow.podspec */, 177 | 1C4985EDB005E63A830176CE /* README.md */, 178 | 5FD91AF3667236846934E8B9 /* LICENSE */, 179 | ); 180 | name = "Podspec Metadata"; 181 | sourceTree = ""; 182 | }; 183 | 67EF8AB7524DB4FF7F66A17E /* Pods */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | FD45C56C18E7B8EC08371B86 /* Pods-ImageSlideshow_Example.debug.xcconfig */, 187 | 09417F1351C21E0DCE8667BE /* Pods-ImageSlideshow_Example.release.xcconfig */, 188 | ); 189 | name = Pods; 190 | sourceTree = ""; 191 | }; 192 | 88C40B0495D05599E2889C22 /* Frameworks */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | 8AA07A6EDAC622F1407E6C80 /* Pods_ImageSlideshow_Example.framework */, 196 | ); 197 | name = Frameworks; 198 | sourceTree = ""; 199 | }; 200 | D00C7A2420B4C05C00E5725B /* Resources */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | D00C7A2620B4C0A100E5725B /* ic_cross_white@2x.png */, 204 | D00C7A2520B4C0A000E5725B /* ic_cross_white@3x.png */, 205 | ); 206 | path = Resources; 207 | sourceTree = ""; 208 | }; 209 | D0E8A9E31D97EB6D007EC517 /* ImageSlideshow_framework */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | D0083DCD1EB739E700126B21 /* ActivityIndicator.swift */, 213 | 43F0FAD32568BAEB008FFD39 /* Bundle+Module.swift */, 214 | D0E8A9EA1D97EB94007EC517 /* FullScreenSlideshowViewController.swift */, 215 | D0E8A9E41D97EB6D007EC517 /* ImageSlideshow_framework.h */, 216 | D0E8A9EB1D97EB94007EC517 /* ImageSlideshow.swift */, 217 | D0E8A9EC1D97EB94007EC517 /* ImageSlideshowItem.swift */, 218 | D0E8A9E51D97EB6D007EC517 /* Info.plist */, 219 | D0E8A9ED1D97EB94007EC517 /* InputSource.swift */, 220 | D0B974AF202738F6006217CF /* PageIndicator.swift */, 221 | F802998E20CE9EA7009D64DD /* PageIndicatorPosition.swift */, 222 | D00C7A2420B4C05C00E5725B /* Resources */, 223 | F539204B210F03600057EFB3 /* SwiftSupport.swift */, 224 | D0E8A9EE1D97EB94007EC517 /* UIImage+AspectFit.swift */, 225 | D0E8A9EF1D97EB94007EC517 /* UIImageView+Tools.swift */, 226 | D0E8A9F01D97EB94007EC517 /* ZoomAnimatedTransitioning.swift */, 227 | ); 228 | path = ImageSlideshow_framework; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXGroup section */ 232 | 233 | /* Begin PBXHeadersBuildPhase section */ 234 | D0E8A9DF1D97EB6D007EC517 /* Headers */ = { 235 | isa = PBXHeadersBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | D0E8A9E61D97EB6D007EC517 /* ImageSlideshow_framework.h in Headers */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXHeadersBuildPhase section */ 243 | 244 | /* Begin PBXNativeTarget section */ 245 | 607FACCF1AFB9204008FA782 /* ImageSlideshow_Example */ = { 246 | isa = PBXNativeTarget; 247 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ImageSlideshow_Example" */; 248 | buildPhases = ( 249 | C32710DF8FE75A6D757948CC /* [CP] Check Pods Manifest.lock */, 250 | 607FACCC1AFB9204008FA782 /* Sources */, 251 | 607FACCD1AFB9204008FA782 /* Frameworks */, 252 | 607FACCE1AFB9204008FA782 /* Resources */, 253 | 7EF425DA4D49DBDDA22C665D /* [CP] Embed Pods Frameworks */, 254 | ); 255 | buildRules = ( 256 | ); 257 | dependencies = ( 258 | ); 259 | name = ImageSlideshow_Example; 260 | productName = ImageSlideshow; 261 | productReference = 607FACD01AFB9204008FA782 /* ImageSlideshow_Example.app */; 262 | productType = "com.apple.product-type.application"; 263 | }; 264 | 607FACE41AFB9204008FA782 /* ImageSlideshow_Tests */ = { 265 | isa = PBXNativeTarget; 266 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ImageSlideshow_Tests" */; 267 | buildPhases = ( 268 | 607FACE11AFB9204008FA782 /* Sources */, 269 | 607FACE21AFB9204008FA782 /* Frameworks */, 270 | 607FACE31AFB9204008FA782 /* Resources */, 271 | ); 272 | buildRules = ( 273 | ); 274 | dependencies = ( 275 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 276 | ); 277 | name = ImageSlideshow_Tests; 278 | productName = Tests; 279 | productReference = 607FACE51AFB9204008FA782 /* ImageSlideshow_Tests.xctest */; 280 | productType = "com.apple.product-type.bundle.unit-test"; 281 | }; 282 | D0E8A9E11D97EB6D007EC517 /* ImageSlideshow_framework */ = { 283 | isa = PBXNativeTarget; 284 | buildConfigurationList = D0E8A9E91D97EB6D007EC517 /* Build configuration list for PBXNativeTarget "ImageSlideshow_framework" */; 285 | buildPhases = ( 286 | D0E8A9DD1D97EB6D007EC517 /* Sources */, 287 | D0E8A9DE1D97EB6D007EC517 /* Frameworks */, 288 | D0E8A9DF1D97EB6D007EC517 /* Headers */, 289 | D0E8A9E01D97EB6D007EC517 /* Resources */, 290 | ); 291 | buildRules = ( 292 | ); 293 | dependencies = ( 294 | ); 295 | name = ImageSlideshow_framework; 296 | productName = ImageSlideshow_framework; 297 | productReference = D0E8A9E21D97EB6D007EC517 /* ImageSlideshow.framework */; 298 | productType = "com.apple.product-type.framework"; 299 | }; 300 | /* End PBXNativeTarget section */ 301 | 302 | /* Begin PBXProject section */ 303 | 607FACC81AFB9204008FA782 /* Project object */ = { 304 | isa = PBXProject; 305 | attributes = { 306 | LastSwiftMigration = 0700; 307 | LastSwiftUpdateCheck = 0700; 308 | LastUpgradeCheck = 0940; 309 | ORGANIZATIONNAME = CocoaPods; 310 | TargetAttributes = { 311 | 607FACCF1AFB9204008FA782 = { 312 | CreatedOnToolsVersion = 6.3.1; 313 | DevelopmentTeam = 5VWB99DS38; 314 | LastSwiftMigration = 1020; 315 | }; 316 | 607FACE41AFB9204008FA782 = { 317 | CreatedOnToolsVersion = 6.3.1; 318 | DevelopmentTeam = 5VWB99DS38; 319 | LastSwiftMigration = 1020; 320 | TestTargetID = 607FACCF1AFB9204008FA782; 321 | }; 322 | D0E8A9E11D97EB6D007EC517 = { 323 | CreatedOnToolsVersion = 8.0; 324 | DevelopmentTeam = 9X4J83EL7M; 325 | LastSwiftMigration = 0800; 326 | ProvisioningStyle = Automatic; 327 | }; 328 | }; 329 | }; 330 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "ImageSlideshow" */; 331 | compatibilityVersion = "Xcode 3.2"; 332 | developmentRegion = English; 333 | hasScannedForEncodings = 0; 334 | knownRegions = ( 335 | English, 336 | en, 337 | Base, 338 | ); 339 | mainGroup = 607FACC71AFB9204008FA782; 340 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 341 | projectDirPath = ""; 342 | projectRoot = ""; 343 | targets = ( 344 | 607FACCF1AFB9204008FA782 /* ImageSlideshow_Example */, 345 | 607FACE41AFB9204008FA782 /* ImageSlideshow_Tests */, 346 | D0E8A9E11D97EB6D007EC517 /* ImageSlideshow_framework */, 347 | ); 348 | }; 349 | /* End PBXProject section */ 350 | 351 | /* Begin PBXResourcesBuildPhase section */ 352 | 607FACCE1AFB9204008FA782 /* Resources */ = { 353 | isa = PBXResourcesBuildPhase; 354 | buildActionMask = 2147483647; 355 | files = ( 356 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 357 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 358 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 359 | ); 360 | runOnlyForDeploymentPostprocessing = 0; 361 | }; 362 | 607FACE31AFB9204008FA782 /* Resources */ = { 363 | isa = PBXResourcesBuildPhase; 364 | buildActionMask = 2147483647; 365 | files = ( 366 | ); 367 | runOnlyForDeploymentPostprocessing = 0; 368 | }; 369 | D0E8A9E01D97EB6D007EC517 /* Resources */ = { 370 | isa = PBXResourcesBuildPhase; 371 | buildActionMask = 2147483647; 372 | files = ( 373 | D00C7A2720B4C0A100E5725B /* ic_cross_white@3x.png in Resources */, 374 | D00C7A2820B4C0A100E5725B /* ic_cross_white@2x.png in Resources */, 375 | ); 376 | runOnlyForDeploymentPostprocessing = 0; 377 | }; 378 | /* End PBXResourcesBuildPhase section */ 379 | 380 | /* Begin PBXShellScriptBuildPhase section */ 381 | 7EF425DA4D49DBDDA22C665D /* [CP] Embed Pods Frameworks */ = { 382 | isa = PBXShellScriptBuildPhase; 383 | buildActionMask = 2147483647; 384 | files = ( 385 | ); 386 | inputPaths = ( 387 | "${PODS_ROOT}/Target Support Files/Pods-ImageSlideshow_Example/Pods-ImageSlideshow_Example-frameworks.sh", 388 | "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", 389 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 390 | "${BUILT_PRODUCTS_DIR}/AlamofireImage/AlamofireImage.framework", 391 | "${BUILT_PRODUCTS_DIR}/ImageSlideshow/ImageSlideshow.framework", 392 | "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework", 393 | "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", 394 | ); 395 | name = "[CP] Embed Pods Frameworks"; 396 | outputPaths = ( 397 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AFNetworking.framework", 398 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 399 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AlamofireImage.framework", 400 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ImageSlideshow.framework", 401 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework", 402 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", 403 | ); 404 | runOnlyForDeploymentPostprocessing = 0; 405 | shellPath = /bin/sh; 406 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImageSlideshow_Example/Pods-ImageSlideshow_Example-frameworks.sh\"\n"; 407 | showEnvVarsInLog = 0; 408 | }; 409 | C32710DF8FE75A6D757948CC /* [CP] Check Pods Manifest.lock */ = { 410 | isa = PBXShellScriptBuildPhase; 411 | buildActionMask = 2147483647; 412 | files = ( 413 | ); 414 | inputPaths = ( 415 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 416 | "${PODS_ROOT}/Manifest.lock", 417 | ); 418 | name = "[CP] Check Pods Manifest.lock"; 419 | outputPaths = ( 420 | "$(DERIVED_FILE_DIR)/Pods-ImageSlideshow_Example-checkManifestLockResult.txt", 421 | ); 422 | runOnlyForDeploymentPostprocessing = 0; 423 | shellPath = /bin/sh; 424 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 425 | showEnvVarsInLog = 0; 426 | }; 427 | /* End PBXShellScriptBuildPhase section */ 428 | 429 | /* Begin PBXSourcesBuildPhase section */ 430 | 607FACCC1AFB9204008FA782 /* Sources */ = { 431 | isa = PBXSourcesBuildPhase; 432 | buildActionMask = 2147483647; 433 | files = ( 434 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 435 | F802998F20CE9EA7009D64DD /* PageIndicatorPosition.swift in Sources */, 436 | D007954F2043262C0053E25F /* TableViewController.swift in Sources */, 437 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 438 | ); 439 | runOnlyForDeploymentPostprocessing = 0; 440 | }; 441 | 607FACE11AFB9204008FA782 /* Sources */ = { 442 | isa = PBXSourcesBuildPhase; 443 | buildActionMask = 2147483647; 444 | files = ( 445 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, 446 | F802999020CE9EA7009D64DD /* PageIndicatorPosition.swift in Sources */, 447 | ); 448 | runOnlyForDeploymentPostprocessing = 0; 449 | }; 450 | D0E8A9DD1D97EB6D007EC517 /* Sources */ = { 451 | isa = PBXSourcesBuildPhase; 452 | buildActionMask = 2147483647; 453 | files = ( 454 | D0E8A9F21D97EB94007EC517 /* ImageSlideshow.swift in Sources */, 455 | D0E8A9F41D97EB94007EC517 /* InputSource.swift in Sources */, 456 | D0E8A9F61D97EB94007EC517 /* UIImageView+Tools.swift in Sources */, 457 | D0E8A9F71D97EB94007EC517 /* ZoomAnimatedTransitioning.swift in Sources */, 458 | D0083DCE1EB739E700126B21 /* ActivityIndicator.swift in Sources */, 459 | 43F0FAD42568BAEB008FFD39 /* Bundle+Module.swift in Sources */, 460 | F802999120CE9EA7009D64DD /* PageIndicatorPosition.swift in Sources */, 461 | D0E8A9F51D97EB94007EC517 /* UIImage+AspectFit.swift in Sources */, 462 | D0E8A9F11D97EB94007EC517 /* FullScreenSlideshowViewController.swift in Sources */, 463 | F539204C210F03610057EFB3 /* SwiftSupport.swift in Sources */, 464 | D0B974B0202738F6006217CF /* PageIndicator.swift in Sources */, 465 | D0E8A9F31D97EB94007EC517 /* ImageSlideshowItem.swift in Sources */, 466 | ); 467 | runOnlyForDeploymentPostprocessing = 0; 468 | }; 469 | /* End PBXSourcesBuildPhase section */ 470 | 471 | /* Begin PBXTargetDependency section */ 472 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 473 | isa = PBXTargetDependency; 474 | target = 607FACCF1AFB9204008FA782 /* ImageSlideshow_Example */; 475 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 476 | }; 477 | /* End PBXTargetDependency section */ 478 | 479 | /* Begin PBXVariantGroup section */ 480 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = { 481 | isa = PBXVariantGroup; 482 | children = ( 483 | 607FACDA1AFB9204008FA782 /* Base */, 484 | ); 485 | name = Main.storyboard; 486 | sourceTree = ""; 487 | }; 488 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 489 | isa = PBXVariantGroup; 490 | children = ( 491 | 607FACDF1AFB9204008FA782 /* Base */, 492 | ); 493 | name = LaunchScreen.xib; 494 | sourceTree = ""; 495 | }; 496 | /* End PBXVariantGroup section */ 497 | 498 | /* Begin XCBuildConfiguration section */ 499 | 607FACED1AFB9204008FA782 /* Debug */ = { 500 | isa = XCBuildConfiguration; 501 | buildSettings = { 502 | ALWAYS_SEARCH_USER_PATHS = NO; 503 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 504 | CLANG_CXX_LIBRARY = "libc++"; 505 | CLANG_ENABLE_MODULES = YES; 506 | CLANG_ENABLE_OBJC_ARC = YES; 507 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 508 | CLANG_WARN_BOOL_CONVERSION = YES; 509 | CLANG_WARN_COMMA = YES; 510 | CLANG_WARN_CONSTANT_CONVERSION = YES; 511 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 512 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 513 | CLANG_WARN_EMPTY_BODY = YES; 514 | CLANG_WARN_ENUM_CONVERSION = YES; 515 | CLANG_WARN_INFINITE_RECURSION = YES; 516 | CLANG_WARN_INT_CONVERSION = YES; 517 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 518 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 519 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 520 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 521 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 522 | CLANG_WARN_STRICT_PROTOTYPES = YES; 523 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 524 | CLANG_WARN_UNREACHABLE_CODE = YES; 525 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 526 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 527 | COPY_PHASE_STRIP = NO; 528 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 529 | ENABLE_STRICT_OBJC_MSGSEND = YES; 530 | ENABLE_TESTABILITY = YES; 531 | GCC_C_LANGUAGE_STANDARD = gnu99; 532 | GCC_DYNAMIC_NO_PIC = NO; 533 | GCC_NO_COMMON_BLOCKS = YES; 534 | GCC_OPTIMIZATION_LEVEL = 0; 535 | GCC_PREPROCESSOR_DEFINITIONS = ( 536 | "DEBUG=1", 537 | "$(inherited)", 538 | ); 539 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 540 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 541 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 542 | GCC_WARN_UNDECLARED_SELECTOR = YES; 543 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 544 | GCC_WARN_UNUSED_FUNCTION = YES; 545 | GCC_WARN_UNUSED_VARIABLE = YES; 546 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 547 | MTL_ENABLE_DEBUG_INFO = YES; 548 | ONLY_ACTIVE_ARCH = YES; 549 | SDKROOT = iphoneos; 550 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 551 | SWIFT_VERSION = 4.2; 552 | }; 553 | name = Debug; 554 | }; 555 | 607FACEE1AFB9204008FA782 /* Release */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | ALWAYS_SEARCH_USER_PATHS = NO; 559 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 560 | CLANG_CXX_LIBRARY = "libc++"; 561 | CLANG_ENABLE_MODULES = YES; 562 | CLANG_ENABLE_OBJC_ARC = YES; 563 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 564 | CLANG_WARN_BOOL_CONVERSION = YES; 565 | CLANG_WARN_COMMA = YES; 566 | CLANG_WARN_CONSTANT_CONVERSION = YES; 567 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 568 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 569 | CLANG_WARN_EMPTY_BODY = YES; 570 | CLANG_WARN_ENUM_CONVERSION = YES; 571 | CLANG_WARN_INFINITE_RECURSION = YES; 572 | CLANG_WARN_INT_CONVERSION = YES; 573 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 574 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 575 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 576 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 577 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 578 | CLANG_WARN_STRICT_PROTOTYPES = YES; 579 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 580 | CLANG_WARN_UNREACHABLE_CODE = YES; 581 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 582 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 583 | COPY_PHASE_STRIP = NO; 584 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 585 | ENABLE_NS_ASSERTIONS = NO; 586 | ENABLE_STRICT_OBJC_MSGSEND = YES; 587 | GCC_C_LANGUAGE_STANDARD = gnu99; 588 | GCC_NO_COMMON_BLOCKS = YES; 589 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 590 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 591 | GCC_WARN_UNDECLARED_SELECTOR = YES; 592 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 593 | GCC_WARN_UNUSED_FUNCTION = YES; 594 | GCC_WARN_UNUSED_VARIABLE = YES; 595 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 596 | MTL_ENABLE_DEBUG_INFO = NO; 597 | SDKROOT = iphoneos; 598 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 599 | SWIFT_VERSION = 4.2; 600 | VALIDATE_PRODUCT = YES; 601 | }; 602 | name = Release; 603 | }; 604 | 607FACF01AFB9204008FA782 /* Debug */ = { 605 | isa = XCBuildConfiguration; 606 | baseConfigurationReference = FD45C56C18E7B8EC08371B86 /* Pods-ImageSlideshow_Example.debug.xcconfig */; 607 | buildSettings = { 608 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 609 | DEVELOPMENT_TEAM = 5VWB99DS38; 610 | INFOPLIST_FILE = ImageSlideshow/Info.plist; 611 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 612 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 613 | MODULE_NAME = ExampleApp; 614 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 615 | PRODUCT_NAME = "$(TARGET_NAME)"; 616 | SWIFT_VERSION = 5.0; 617 | }; 618 | name = Debug; 619 | }; 620 | 607FACF11AFB9204008FA782 /* Release */ = { 621 | isa = XCBuildConfiguration; 622 | baseConfigurationReference = 09417F1351C21E0DCE8667BE /* Pods-ImageSlideshow_Example.release.xcconfig */; 623 | buildSettings = { 624 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 625 | DEVELOPMENT_TEAM = 5VWB99DS38; 626 | INFOPLIST_FILE = ImageSlideshow/Info.plist; 627 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 628 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 629 | MODULE_NAME = ExampleApp; 630 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 631 | PRODUCT_NAME = "$(TARGET_NAME)"; 632 | SWIFT_VERSION = 5.0; 633 | }; 634 | name = Release; 635 | }; 636 | 607FACF31AFB9204008FA782 /* Debug */ = { 637 | isa = XCBuildConfiguration; 638 | buildSettings = { 639 | BUNDLE_LOADER = "$(TEST_HOST)"; 640 | DEVELOPMENT_TEAM = 5VWB99DS38; 641 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 642 | GCC_PREPROCESSOR_DEFINITIONS = ( 643 | "DEBUG=1", 644 | "$(inherited)", 645 | ); 646 | INFOPLIST_FILE = Tests/Info.plist; 647 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 648 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 649 | PRODUCT_NAME = "$(TARGET_NAME)"; 650 | SWIFT_VERSION = 5.0; 651 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ImageSlideshow_Example.app/ImageSlideshow_Example"; 652 | }; 653 | name = Debug; 654 | }; 655 | 607FACF41AFB9204008FA782 /* Release */ = { 656 | isa = XCBuildConfiguration; 657 | buildSettings = { 658 | BUNDLE_LOADER = "$(TEST_HOST)"; 659 | DEVELOPMENT_TEAM = 5VWB99DS38; 660 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 661 | INFOPLIST_FILE = Tests/Info.plist; 662 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 663 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 664 | PRODUCT_NAME = "$(TARGET_NAME)"; 665 | SWIFT_VERSION = 5.0; 666 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ImageSlideshow_Example.app/ImageSlideshow_Example"; 667 | }; 668 | name = Release; 669 | }; 670 | D0E8A9E71D97EB6D007EC517 /* Debug */ = { 671 | isa = XCBuildConfiguration; 672 | buildSettings = { 673 | CLANG_ANALYZER_NONNULL = YES; 674 | CLANG_ENABLE_MODULES = YES; 675 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 676 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 677 | CODE_SIGN_IDENTITY = ""; 678 | CURRENT_PROJECT_VERSION = 1; 679 | DEBUG_INFORMATION_FORMAT = dwarf; 680 | DEFINES_MODULE = YES; 681 | DEVELOPMENT_TEAM = 9X4J83EL7M; 682 | DYLIB_COMPATIBILITY_VERSION = 1; 683 | DYLIB_CURRENT_VERSION = 1; 684 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 685 | INFOPLIST_FILE = ImageSlideshow_framework/Info.plist; 686 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 687 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 688 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 689 | PRODUCT_BUNDLE_IDENTIFIER = "io.zvo.ImageSlideshow-framework"; 690 | PRODUCT_NAME = ImageSlideshow; 691 | SKIP_INSTALL = YES; 692 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 693 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 694 | TARGETED_DEVICE_FAMILY = "1,2"; 695 | VERSIONING_SYSTEM = "apple-generic"; 696 | VERSION_INFO_PREFIX = ""; 697 | }; 698 | name = Debug; 699 | }; 700 | D0E8A9E81D97EB6D007EC517 /* Release */ = { 701 | isa = XCBuildConfiguration; 702 | buildSettings = { 703 | CLANG_ANALYZER_NONNULL = YES; 704 | CLANG_ENABLE_MODULES = YES; 705 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 706 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 707 | CODE_SIGN_IDENTITY = ""; 708 | CURRENT_PROJECT_VERSION = 1; 709 | DEFINES_MODULE = YES; 710 | DEVELOPMENT_TEAM = 9X4J83EL7M; 711 | DYLIB_COMPATIBILITY_VERSION = 1; 712 | DYLIB_CURRENT_VERSION = 1; 713 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 714 | INFOPLIST_FILE = ImageSlideshow_framework/Info.plist; 715 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 716 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 717 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 718 | PRODUCT_BUNDLE_IDENTIFIER = "io.zvo.ImageSlideshow-framework"; 719 | PRODUCT_NAME = ImageSlideshow; 720 | SKIP_INSTALL = YES; 721 | TARGETED_DEVICE_FAMILY = "1,2"; 722 | VERSIONING_SYSTEM = "apple-generic"; 723 | VERSION_INFO_PREFIX = ""; 724 | }; 725 | name = Release; 726 | }; 727 | /* End XCBuildConfiguration section */ 728 | 729 | /* Begin XCConfigurationList section */ 730 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "ImageSlideshow" */ = { 731 | isa = XCConfigurationList; 732 | buildConfigurations = ( 733 | 607FACED1AFB9204008FA782 /* Debug */, 734 | 607FACEE1AFB9204008FA782 /* Release */, 735 | ); 736 | defaultConfigurationIsVisible = 0; 737 | defaultConfigurationName = Release; 738 | }; 739 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ImageSlideshow_Example" */ = { 740 | isa = XCConfigurationList; 741 | buildConfigurations = ( 742 | 607FACF01AFB9204008FA782 /* Debug */, 743 | 607FACF11AFB9204008FA782 /* Release */, 744 | ); 745 | defaultConfigurationIsVisible = 0; 746 | defaultConfigurationName = Release; 747 | }; 748 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "ImageSlideshow_Tests" */ = { 749 | isa = XCConfigurationList; 750 | buildConfigurations = ( 751 | 607FACF31AFB9204008FA782 /* Debug */, 752 | 607FACF41AFB9204008FA782 /* Release */, 753 | ); 754 | defaultConfigurationIsVisible = 0; 755 | defaultConfigurationName = Release; 756 | }; 757 | D0E8A9E91D97EB6D007EC517 /* Build configuration list for PBXNativeTarget "ImageSlideshow_framework" */ = { 758 | isa = XCConfigurationList; 759 | buildConfigurations = ( 760 | D0E8A9E71D97EB6D007EC517 /* Debug */, 761 | D0E8A9E81D97EB6D007EC517 /* Release */, 762 | ); 763 | defaultConfigurationIsVisible = 0; 764 | defaultConfigurationName = Release; 765 | }; 766 | /* End XCConfigurationList section */ 767 | }; 768 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 769 | } 770 | --------------------------------------------------------------------------------