22 Commits

Author SHA1 Message Date
Visal Rajapakse 0f6ff38a71 Fixed broken preview video
Fixes #11
2021-08-04 11:23:45 +05:30
Visal Rajapakse e072b40ea5 Merge pull request #6 from jabernall24/updating_comments
Update comments with correct default values for glideFactors
2021-06-10 23:56:27 +05:30
Visal Rajapakse de1e60ee6b Merge pull request #8 from jabernall24/magic_number_55
Give some context to 55 for circular slideshows
2021-06-03 11:27:54 +05:30
Visal Rajapakse f469bd70ab Merge pull request #9 from jabernall24/test_glidecell
Adding some tests for GlideCell
2021-06-03 11:26:47 +05:30
Visal Rajapakse 8d19bad3e9 Merge pull request #5 from jabernall24/adding_tests
Adding tests for Glideshow
2021-06-03 11:26:30 +05:30
jabernall24 689f0b1bfb Adding some tests for GlideCell 2021-06-01 11:55:46 -07:00
jabernall24 fcdf49d4a4 Give some context to 55 2021-06-01 11:07:43 -07:00
jabernall24 52b001769e Update comments with correct default values 2021-06-01 10:48:37 -07:00
jabernall24 09aa06bec1 Adding tests for Glideshow 2021-06-01 10:37:59 -07:00
Visal Rajapakse 35c8f76b22 Update README.md 2021-04-06 20:12:28 +05:30
Visal Rajapakse 249fd977e3 Update README.md 2021-03-10 17:16:26 +05:30
Visal Rajapakse f61639a53f Delegate method updates 2021-03-10 15:34:36 +05:30
Visal Rajapakse cebff16524 Podspec update v1.1.1 2021-03-10 09:02:21 +05:30
Visal Rajapakse 03d73775f2 Update README.md 2021-03-10 08:34:12 +05:30
Visal Rajapakse 3a45fcc386 Update README.md 2021-03-10 01:22:00 +05:30
Visal Rajapakse efc710363e Update README.md 2021-03-10 01:06:43 +05:30
Visal Rajapakse 47730019ad Set min target to iOS 11 2021-03-10 00:49:00 +05:30
Visal Rajapakse 1827c4686e Downloading images from URL 2021-03-09 23:33:32 +05:30
Visal Rajapakse c004cbf227 iOS 11 compatibility 2021-03-09 17:23:36 +05:30
Visal Rajapakse 569f0792ec Update README.md
Added documentation
2021-03-09 16:15:24 +05:30
Visal Rajapakse 61521f6919 iOS 11> support 2021-03-09 15:59:58 +05:30
Visal Rajapakse 9be12f614b GlideshowExample improvements 2021-03-09 15:58:57 +05:30
29 changed files with 731 additions and 78 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |spec|
spec.name = "Glideshow"
spec.version = "1.0.0"
spec.version = "1.1.1"
spec.summary = "A slideshow written in Swift 5 that adds transitions to labels within a slide"
spec.description = <<-DESC
@@ -12,8 +12,8 @@ Pod::Spec.new do |spec|
spec.license = { :type => "MIT", :file => "LICENSE" }
spec.author = { "Visal Rajapakse" => "visalrajapakse@gmail.com" }
spec.platform = :ios, "13.0"
spec.ios.deployment_target = "13.0"
spec.platform = :ios, "11.0"
spec.ios.deployment_target = "11.0"
spec.swift_version = "5"
spec.source = { :git => "https://github.com/v15a1/Glideshow.git", :tag => "#{spec.version}" }
+16 -4
View File
@@ -22,6 +22,9 @@
4B0CF40E25EBD50F005975B1 /* Glideshow.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B0739E625EB56BD003FF9C1 /* Glideshow.framework */; };
4B0CF40F25EBD50F005975B1 /* Glideshow.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B0739E625EB56BD003FF9C1 /* Glideshow.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4B0CF41A25EBD590005975B1 /* GlideCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CF41925EBD590005975B1 /* GlideCell.swift */; };
4B3447F525F7CD6D00A713F2 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3447F425F7CD6D00A713F2 /* ImageCache.swift */; };
4BB22DFB25F7D35800B6FD9D /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB22DFA25F7D35800B6FD9D /* ImageLoader.swift */; };
6E82188B2666B6450012A2CD /* GlideCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E82188A2666B6450012A2CD /* GlideCellTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -74,6 +77,9 @@
4B0CF40325EB760D005975B1 /* GlideLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlideLabel.swift; sourceTree = "<group>"; };
4B0CF40825EB76BD005975B1 /* GlideItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlideItem.swift; sourceTree = "<group>"; };
4B0CF41925EBD590005975B1 /* GlideCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlideCell.swift; sourceTree = "<group>"; };
4B3447F425F7CD6D00A713F2 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
4BB22DFA25F7D35800B6FD9D /* ImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLoader.swift; sourceTree = "<group>"; };
6E82188A2666B6450012A2CD /* GlideCellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlideCellTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -131,6 +137,8 @@
4B0739EA25EB56BD003FF9C1 /* Info.plist */,
4B073A1D25EB5955003FF9C1 /* Glideshow.swift */,
4B0CF41925EBD590005975B1 /* GlideCell.swift */,
4B3447F425F7CD6D00A713F2 /* ImageCache.swift */,
4BB22DFA25F7D35800B6FD9D /* ImageLoader.swift */,
4B0CF40325EB760D005975B1 /* GlideLabel.swift */,
4B0CF40825EB76BD005975B1 /* GlideItem.swift */,
);
@@ -142,6 +150,7 @@
children = (
4B0739F425EB56BD003FF9C1 /* GlideshowTests.swift */,
4B0739F625EB56BD003FF9C1 /* Info.plist */,
6E82188A2666B6450012A2CD /* GlideCellTests.swift */,
);
path = GlideshowTests;
sourceTree = "<group>";
@@ -309,7 +318,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4BB22DFB25F7D35800B6FD9D /* ImageLoader.swift in Sources */,
4B0CF40425EB760D005975B1 /* GlideLabel.swift in Sources */,
4B3447F525F7CD6D00A713F2 /* ImageCache.swift in Sources */,
4B0CF40925EB76BD005975B1 /* GlideItem.swift in Sources */,
4B073A1E25EB5955003FF9C1 /* Glideshow.swift in Sources */,
4B0CF41A25EBD590005975B1 /* GlideCell.swift in Sources */,
@@ -321,6 +332,7 @@
buildActionMask = 2147483647;
files = (
4B0739F525EB56BD003FF9C1 /* GlideshowTests.swift in Sources */,
6E82188B2666B6450012A2CD /* GlideCellTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -503,7 +515,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Glideshow/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -530,7 +542,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Glideshow/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -590,7 +602,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = FVZ5PWG6VV;
INFOPLIST_FILE = GlideshowExample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -610,7 +622,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = FVZ5PWG6VV;
INFOPLIST_FILE = GlideshowExample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
+65 -30
View File
@@ -6,6 +6,7 @@
//
import UIKit
import Combine
//MARK: Protocol - GlideableCellDelegate
/// Protocol to inform cell about scroll state
@@ -54,30 +55,6 @@ class GlideCell: UICollectionViewCell {
// Protocol variable to hide/show labels based on value
public var isProminent: Bool = true
/// Value of the slide title
public var title : String? {
didSet{
slideTitle.text = title
layoutSubviews()
}
}
/// Value of the slide description
public var story : String?{
didSet{
slideDescription.text = story
layoutSubviews()
}
}
/// Value of the slide caption
public var caption : String?{
didSet{
slideCaption.text = caption
layoutSubviews()
}
}
/// Sets up slide background image if image is available
public var backgroundImage : UIImage? {
@@ -91,7 +68,6 @@ class GlideCell: UICollectionViewCell {
/// Slide caption GlideLabel
public var slideCaption : GlideLabel = {
let label = GlideLabel()
label.glideFactor = 1
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.numberOfLines = 0
@@ -102,7 +78,6 @@ class GlideCell: UICollectionViewCell {
/// Slide title GlideLabel
public var slideTitle : GlideLabel = {
let label = GlideLabel()
label.glideFactor = 2
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 20, weight: .black)
label.textColor = .white
@@ -113,7 +88,6 @@ class GlideCell: UICollectionViewCell {
/// Slide description GlideLabel
public var slideDescription : GlideLabel = {
let label = GlideLabel()
label.glideFactor = 3
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.numberOfLines = 0
@@ -166,7 +140,7 @@ class GlideCell: UICollectionViewCell {
}()
/// Spacing between labels of the slide. Default value : 8
public var labelSpacing : CGFloat!
public var labelSpacing : CGFloat = 8
/// Animateable GlideLabels for gliding
public var animateableViews : [GlideLabel]?
@@ -178,14 +152,14 @@ class GlideCell: UICollectionViewCell {
}
}
/// glide factor for the description lable. Default: 2
/// glide factor for the description lable. Default: 3
public var descriptionGlideFactor : CGFloat = 3 {
didSet{
slideDescription.glideFactor = titleGlideFactor
}
}
/// glide factor for the title lable. Default: 2
/// glide factor for the title lable. Default: 1
public var captionGlideFactor : CGFloat = 1 {
didSet{
slideCaption.glideFactor = titleGlideFactor
@@ -202,6 +176,9 @@ class GlideCell: UICollectionViewCell {
/// Maximum width of a GlideLabel. Calculated using leading inset of cell
private var animateableMaxWidth : CGFloat!
@available(iOS 13, *)
private lazy var cancellable : AnyCancellable? = nil
public override func awakeFromNib() {
super.awakeFromNib()
@@ -218,6 +195,18 @@ class GlideCell: UICollectionViewCell {
initialize()
}
override func prepareForReuse() {
super.prepareForReuse()
if backgroundImage != nil {
imageView.image = nil
}
if #available(iOS 13, *) {
cancellable?.cancel()
} else {
return
}
}
public override func layoutSubviews() {
super.layoutSubviews()
// Setting up cell
@@ -236,6 +225,52 @@ class GlideCell: UICollectionViewCell {
slide.clipsToBounds = true
}
/// Configures cell
/// - Parameter item: `GlideItem` to configure cell with
public func configure( with item : GlideItem, placeholderImage : UIImage?){
slideCaption.text = item.caption
slideTitle.text = item.title
slideDescription.text = item.description
if let bgImage = item.backgroundImage{
backgroundImage = bgImage
}else{
backgroundImage = placeholderImage
if #available(iOS 13.0, *) {
cancellable = loadImage(for: item).sink{
[weak self] image in self?.showNetworkImage(for: image)
}
} else {
imageView.loadImage(urlString: item.imgURL!)
}
}
layoutIfNeeded()
}
/// Displays retrieved image
/// - Parameter image: Image to display
private func showNetworkImage(for image : UIImage?) {
UIView.transition(with: self, duration: 0.3, options: .transitionCrossDissolve, animations: {
self.backgroundImage = image
}, completion: nil)
}
/// Caches loaded image
/// - Parameter item: `GlideItem` to retrieve URL
/// - Returns: Returns `Just` publisher with the cached image if any.
@available(iOS 13.0, *)
private func loadImage(for item: GlideItem) -> AnyPublisher<UIImage?, Never> {
return Just(item.imgURL)
.flatMap({ poster -> AnyPublisher<UIImage?, Never> in
let url = URL(string: item.imgURL ?? "")!
return ImageLoader.shared.loadImage(from: url)
})
.eraseToAnyPublisher()
}
/// Setup Description, Title, Caption in stated order by positioning based on each Glidelabels content
private func setupAnimateables(){
//setting up animateables
+22 -1
View File
@@ -7,7 +7,7 @@
import UIKit
public class GlideItem {
public struct GlideItem {
/// Caption of the slide
public var caption : String!
@@ -21,6 +21,8 @@ public class GlideItem {
/// Asset image for slide background
public var backgroundImage : UIImage?
public var imgURL : String?
/// Initializer for GlideItem
///
/// - Parameters:
@@ -39,4 +41,23 @@ public class GlideItem {
self.description = description
self.backgroundImage = backgroundImage
}
/// Initializer for GlideItem
///
/// - Parameters:
/// - caption: Caption of the slide
/// - title: Title of the slide
/// - description: Description of the slide
/// - imageURL: Image URL to load
public init(
caption : String! = nil,
title : String! = nil,
description : String! = nil,
imageURL : String! = nil
){
self.caption = caption
self.title = title
self.description = description
self.imgURL = imageURL
}
}
+30 -27
View File
@@ -7,6 +7,9 @@
import UIKit
// Magic number used to give the user the feeling they are able to swipe infinitely
fileprivate let kInifiniteSwipingMultiplier = 55
@objc
/// The delegate protocol that notifies slideshow state changes
public protocol GlideshowProtocol : AnyObject {
@@ -102,6 +105,8 @@ public class Glideshow: UIView {
/// Default slide color
public var defaultSlideColor : UIColor = UIColor.lightGray
public var placeHolderImage : UIImage?
/// Spacing between `SlideLabels` embedded in the slide
public var labelSpacing : CGFloat = 8
@@ -111,6 +116,15 @@ public class Glideshow: UIView {
/// Slide title font
public var titleFont : UIFont? = UIFont.systemFont(ofSize: 20, weight: .black)
/// Glide factor of the slide description
public var descriptionGlideFactor : CGFloat = 3
/// Glide factor of the slide title
public var titleGlideFactor : CGFloat = 2
/// Glide factor of the slide description
public var captionGlideFactor : CGFloat = 1
/// Slide desctription font
public var descriptionFont : UIFont? = UIFont.systemFont(ofSize: 16, weight: .regular)
@@ -147,6 +161,7 @@ public class Glideshow: UIView {
/// Page indicator
public var pageIndicator : UIPageControl? {
didSet{
setPagesForIndicator()
setPageIndicatorIfNeeded()
}
}
@@ -158,24 +173,6 @@ public class Glideshow: UIView {
}
}
/// Color of the page indictor of the selected page
var currentPageIndicatorTint : UIColor = UIColor.darkGray {
didSet {
if pageIndicator != nil {
pageIndicator?.currentPageIndicatorTintColor = currentPageIndicatorTint
}
}
}
/// Color of the page indictor of all pages
var pageIndicatorTintColor : UIColor = UIColor.lightGray {
didSet {
if pageIndicator != nil {
pageIndicator?.pageIndicatorTintColor = pageIndicatorTintColor
}
}
}
/// Scrolls to start/end of the collectionView if slideshow is circular
lazy var currentIndex : Int = collectionView?.indexPathsForVisibleItems.sorted().first?.last ?? 0 - 1
@@ -277,8 +274,8 @@ public class Glideshow: UIView {
if pageIndicator == nil {
pageIndicator = UIPageControl()
pageIndicator?.pageIndicatorTintColor = pageIndicatorTintColor
pageIndicator?.currentPageIndicatorTintColor = currentPageIndicatorTint
pageIndicator?.pageIndicatorTintColor = UIColor.lightGray
pageIndicator?.currentPageIndicatorTintColor = UIColor.darkGray
self.addSubview(pageIndicator!)
}
@@ -396,15 +393,17 @@ public class Glideshow: UIView {
/// setting number of pages, current page for the page control
fileprivate func setPagesForIndicator(){
if items == nil { return }
pageIndicator?.numberOfPages = items!.count
pageIndicator?.currentPage = pageByMidSection
}
fileprivate func setPageIndicatorIfNeeded(){
guard self.width != nil else { return }
if self.width == nil { return }
switch pageIndicatorPosition {
case .hidden:
collectionView?.frame = self.bounds
case .bottom:
slideMargin.bottom = 10
@@ -467,7 +466,7 @@ public class Glideshow: UIView {
extension Glideshow : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (items?.count ?? 0) * (isCircular ? 55 : 1)
return (items?.count ?? 0) * (isCircular ? kInifiniteSwipingMultiplier : 1)
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
@@ -487,6 +486,9 @@ extension Glideshow : UICollectionViewDelegate, UICollectionViewDataSource, UICo
cell.slideTitle.textColor = titleColor
cell.slideDescription.textColor = descriptionColor
cell.slideCaption.textColor = captionColor
cell.slideTitle.glideFactor = titleGlideFactor
cell.slideDescription.glideFactor = descriptionGlideFactor
cell.slideCaption.glideFactor = captionGlideFactor
cell.labelSpacing = labelSpacing
cell.slide.backgroundColor = defaultSlideColor
// layout
@@ -499,10 +501,10 @@ extension Glideshow : UICollectionViewDelegate, UICollectionViewDataSource, UICo
}
/// Cell setup for passed `GlideItems`
if let item = items?[indexPath.item % (items?.count ?? 0)] {
cell.caption = item.caption
cell.title = item.title
cell.story = item.description
cell.backgroundImage = item.backgroundImage
cell.configure(
with: item,
placeholderImage: placeHolderImage
)
}
return cell
@@ -574,6 +576,7 @@ extension Glideshow : UICollectionViewDelegate, UICollectionViewDataSource, UICo
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
delegate?.glideshowDidSelecteRowAt?(indexPath: indexPath, self)
let index = IndexPath(item: ((pageByMidSection) % items!.count), section: 0)
delegate?.glideshowDidSelecteRowAt?(indexPath: index, self)
}
}
+121
View File
@@ -0,0 +1,121 @@
//
// ImageCache.swift
// Glideshow
//
// Created by Visal Rajapakse on 2021-03-09.
//
// Referenced from Maksym Shcheglov
// https://medium.com/flawless-app-stories/reusable-image-cache-in-swift-9b90eb338e8d
//
import Foundation
import UIKit.UIImage
import Combine
// Declares in-memory image cache
public protocol ImageCacheType: class {
// Returns the image associated with a given url
func image(for url: URL) -> UIImage?
// Inserts the image of the specified url in the cache
func insertImage(_ image: UIImage?, for url: URL)
// Removes the image of the specified url in the cache
func removeImage(for url: URL)
// Removes all images from the cache
func removeAllImages()
// Accesses the value associated with the given key for reading and writing
subscript(_ url: URL) -> UIImage? { get set }
}
public class ImageCache: ImageCacheType {
// 1st level cache, that contains encoded images
lazy var imageCache: NSCache<AnyObject, AnyObject> = {
let cache = NSCache<AnyObject, AnyObject>()
cache.countLimit = config.countLimit
return cache
}()
// 2nd level cache, that contains decoded images
lazy var decodedImageCache: NSCache<AnyObject, AnyObject> = {
let cache = NSCache<AnyObject, AnyObject>()
cache.totalCostLimit = config.memoryLimit
return cache
}()
private let lock = NSLock()
private let config: Config
public struct Config {
public let countLimit: Int
public let memoryLimit: Int
public static let defaultConfig = Config(countLimit: 100, memoryLimit: 1024 * 1024 * 100) // 100 MB
}
public init(config: Config = Config.defaultConfig) {
self.config = config
}
public func image(for url: URL) -> UIImage? {
lock.lock(); defer { lock.unlock() }
// the best case scenario -> there is a decoded image in memory
if let decodedImage = decodedImageCache.object(forKey: url as AnyObject) as? UIImage {
return decodedImage
}
// search for image data
if let image = imageCache.object(forKey: url as AnyObject) as? UIImage {
let decodedImage = image.decodedImage()
decodedImageCache.setObject(image as AnyObject, forKey: url as AnyObject, cost: decodedImage.diskSize)
return decodedImage
}
return nil
}
public func insertImage(_ image: UIImage?, for url: URL) {
guard let image = image else { return removeImage(for: url) }
let decompressedImage = image.decodedImage()
lock.lock(); defer { lock.unlock() }
imageCache.setObject(decompressedImage, forKey: url as AnyObject, cost: 1)
decodedImageCache.setObject(image as AnyObject, forKey: url as AnyObject, cost: decompressedImage.diskSize)
}
public func removeImage(for url: URL) {
lock.lock(); defer { lock.unlock() }
imageCache.removeObject(forKey: url as AnyObject)
decodedImageCache.removeObject(forKey: url as AnyObject)
}
public func removeAllImages() {
lock.lock(); defer { lock.unlock() }
imageCache.removeAllObjects()
decodedImageCache.removeAllObjects()
}
public subscript(_ key: URL) -> UIImage? {
get {
return image(for: key)
}
set {
return insertImage(newValue, for: key)
}
}
}
fileprivate extension UIImage {
func decodedImage() -> UIImage {
guard let cgImage = cgImage else { return self }
let size = CGSize(width: cgImage.width, height: cgImage.height)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: cgImage.bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue)
context?.draw(cgImage, in: CGRect(origin: .zero, size: size))
guard let decodedImage = context?.makeImage() else { return self }
return UIImage(cgImage: decodedImage)
}
// Rough estimation of how much memory image uses in bytes
var diskSize: Int {
guard let cgImage = cgImage else { return 0 }
return cgImage.bytesPerRow * cgImage.height
}
}
+74
View File
@@ -0,0 +1,74 @@
//
// ImageLoader.swift
// Glideshow
//
// Created by Visal Rajapakse on 2021-03-09.
//
// Referenced from Maksym Shcheglov
// https://medium.com/flawless-app-stories/reusable-image-cache-in-swift-9b90eb338e8d
//
import Foundation
import UIKit.UIImage
import Combine
public final class ImageLoader {
public static let shared = ImageLoader()
private let imageCache: ImageCacheType
private lazy var backgroundQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 5
return queue
}()
public init(cache: ImageCacheType = ImageCache()) {
self.imageCache = cache
}
@available(iOS 13.0, *)
public func loadImage(from url: URL) -> AnyPublisher<UIImage?, Never> {
if let image = imageCache[url] {
return Just(image).eraseToAnyPublisher()
}
return URLSession.shared.dataTaskPublisher(for: url)
.map { (data, response) -> UIImage? in return UIImage(data: data) }
.catch { error in return Just(nil) }
.handleEvents(receiveOutput: {[unowned self] image in
guard let image = image else { return }
self.imageCache[url] = image
})
.subscribe(on: backgroundQueue)
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
}
var cache = NSCache<NSString, UIImage>()
extension UIImageView {
func loadImage(urlString: String) {
if let cacheImage = cache.object(forKey: urlString as NSString) {
self.image = cacheImage
return
}
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
return
}
guard let data = data else { return }
let image = UIImage(data: data)
cache.setObject(image!, forKey: urlString as NSString)
DispatchQueue.main.async {
self.image = image
}
}.resume()
}
}
+2 -1
View File
@@ -7,10 +7,11 @@
import UIKit
@available(iOS 13.0, *)
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "dish_default_image.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "0805fe161e1e1089d916c9286696d5eb.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "1ad7f0c78859a871347dd732cfd2b76e.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "42b5b97f3a2dea3b4a860b7786f628ad.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "d42fdbf73868af1f844b88a30617f9d7.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "aesthetic-nawpic-9.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "placeholder-image.jpg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+1
View File
@@ -7,6 +7,7 @@
import UIKit
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
+17 -9
View File
@@ -12,7 +12,6 @@ class ViewController: UIViewController {
let glideshow = Glideshow()
let button = UIButton(type: .system)
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
@@ -28,24 +27,23 @@ class ViewController: UIViewController {
private func setupUI(){
view.backgroundColor = .white
glideshow.interval = 3
glideshow.items = [
GlideItem(caption : "WELCOME",title : "Hola Amigos", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit."),
GlideItem(caption : "Welcome", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", backgroundImage: #imageLiteral(resourceName: "image5")),
GlideItem(caption : "Welcome",title : "Hello", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", backgroundImage: #imageLiteral(resourceName: "image2")),
GlideItem(caption : "Welcome",title : "Hello", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", backgroundImage: #imageLiteral(resourceName: "image3")),
GlideItem(caption : "Welcome",title : "Hello", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.Lorem ipsum dolor sit amet, consectetur adipiscing elit.", backgroundImage: #imageLiteral(resourceName: "image4"))
GlideItem(caption : "Привет", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", imageURL: "https://picsum.photos/1080/720"),
GlideItem(caption : "Hello in Japanese",title : "こんにちは", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", backgroundImage: #imageLiteral(resourceName: "image10")),
GlideItem(caption : "Hello in Chinese",title : "你好", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", backgroundImage: #imageLiteral(resourceName: "image7")),
GlideItem(caption : "Hello in Thai",title : "สวัสดี", description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.Lorem ipsum dolor sit amet, consectetur adipiscing elit.", backgroundImage: #imageLiteral(resourceName: "image9"))
]
glideshow.delegate = self
glideshow.isCircular = true
glideshow.interval = 5
glideshow.placeHolderImage = #imageLiteral(resourceName: "dish_default_image")
glideshow.gradientColor =
UIColor.black.withAlphaComponent(0.8)
glideshow.captionFont = UIFont.systemFont(ofSize: 16, weight: .light)
glideshow.titleFont = UIFont.systemFont(ofSize: 30, weight: .black)
glideshow.gradientHeightFactor = 0.8
glideshow.pageIndicatorPosition = .bottom
glideshow.interval = 5
button.setTitle("Go to item number 3", for: .normal)
button.addTarget(self, action: #selector(goToSlide), for: .touchUpInside)
@@ -60,3 +58,13 @@ class ViewController: UIViewController {
}
}
extension ViewController : GlideshowProtocol {
func glideshowDidSelecteRowAt(indexPath: IndexPath, _ glideshow: Glideshow) {
print(indexPath)
}
func pageDidChange(_ glideshow: Glideshow, didChangePageTo page: Int) {
print(page)
}
}
+41
View File
@@ -0,0 +1,41 @@
//
// GlideCellTests.swift
// GlideshowTests
//
// Created by Jesus Andres Bernal Lopez on 6/1/21.
//
import XCTest
@testable import Glideshow
class GlideCellTests: XCTestCase {
func test_configureWithBackgroundImage() {
let cell = GlideCell()
let testItem = GlideItem(caption: "a-caption", title: "a-title", description: "a-description", backgroundImage: UIImage.checkmark)
cell.configure(with: testItem, placeholderImage: nil)
XCTAssertEqual(cell.slideCaption.text, testItem.caption)
XCTAssertEqual(cell.slideTitle.text, testItem.title)
XCTAssertEqual(cell.slideDescription.text, testItem.description)
XCTAssertEqual(cell.backgroundImage, testItem.backgroundImage)
}
func test_configureWithImageURL() {
let cell = GlideCell()
let testItem = GlideItem(caption: "a-caption", title: "a-title", description: "a-description", imageURL: "a-url")
let placeholderImage = UIImage.checkmark
cell.configure(with: testItem, placeholderImage: placeholderImage)
XCTAssertEqual(cell.slideCaption.text, testItem.caption)
XCTAssertEqual(cell.slideTitle.text, testItem.title)
XCTAssertEqual(cell.slideDescription.text, testItem.description)
XCTAssertEqual(cell.backgroundImage, placeholderImage)
}
}
+98
View File
@@ -13,4 +13,102 @@ class GlideshowTests: XCTestCase {
override func setUp() {
glideshow = Glideshow()
}
func test_interval() {
XCTAssertEqual(glideshow.interval, 0)
}
func test_slideMargin() {
XCTAssertEqual(glideshow.slideMargin, UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20))
}
func test_slidePadding() {
XCTAssertEqual(glideshow.slidePadding, UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20))
}
func test_defaultSldeColor() {
XCTAssertEqual(glideshow.defaultSlideColor, .lightGray)
}
func test_placeHolderImage() {
XCTAssertEqual(glideshow.placeHolderImage, nil)
}
func test_labelSpacing() {
XCTAssertEqual(glideshow.labelSpacing, 8)
}
func test_isGradientEnabled() {
XCTAssertEqual(glideshow.isGradientEnabled, false)
}
func test_titleFont() {
XCTAssertEqual(glideshow.titleFont, UIFont.systemFont(ofSize: 20, weight: .black))
}
func test_descriptionGlideFactor() {
XCTAssertEqual(glideshow.descriptionGlideFactor, 3)
}
func test_titleGlideFactor() {
XCTAssertEqual(glideshow.titleGlideFactor, 2)
}
func test_captionGlideFactor() {
XCTAssertEqual(glideshow.captionGlideFactor, 1)
}
func test_descriptionFont() {
XCTAssertEqual(glideshow.descriptionFont, UIFont.systemFont(ofSize: 16, weight: .regular))
}
func test_captionFont() {
XCTAssertEqual(glideshow.captionFont, UIFont.systemFont(ofSize: 14, weight: .light))
}
func test_titleColor() {
XCTAssertEqual(glideshow.titleColor, .white)
}
func test_descriptionColor() {
XCTAssertEqual(glideshow.descriptionColor, .white)
}
func test_captionColor() {
XCTAssertEqual(glideshow.captionColor, .white)
}
func test_gradientColor() {
XCTAssertEqual(glideshow.gradientColor, UIColor.black.withAlphaComponent(0.6))
XCTAssertEqual(glideshow.isGradientEnabled, false)
glideshow.gradientColor = .orange
XCTAssertEqual(glideshow.gradientColor, .orange)
XCTAssertEqual(glideshow.isGradientEnabled, true)
}
func test_gradientHeightFactor() {
XCTAssertEqual(glideshow.gradientHeightFactor, 0.5)
XCTAssertEqual(glideshow.isGradientEnabled, false)
glideshow.gradientHeightFactor = 0.8
XCTAssertEqual(glideshow.gradientHeightFactor, 0.8)
XCTAssertEqual(glideshow.isGradientEnabled, true)
}
func test_pageIndicator() {
XCTAssertEqual(glideshow.pageIndicator!.pageIndicatorTintColor, .lightGray)
XCTAssertEqual(glideshow.pageIndicator!.currentPageIndicatorTintColor, .darkGray)
XCTAssertEqual(glideshow.pageIndicator!.numberOfPages, 0)
glideshow.items = [
GlideItem(caption: "", title: "", description: "", backgroundImage: UIImage.checkmark),
GlideItem(caption: "", title: "", description: "", backgroundImage: UIImage.checkmark)
]
XCTAssertEqual(glideshow.pageIndicator!.numberOfPages, 2)
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "Glideshow",
platforms: [
.iOS(.v12)
.iOS(.v11)
],
products: [
.library(
+93 -2
View File
@@ -1,3 +1,94 @@
# Glideshow
A description of this package.
![Frame 3](https://user-images.githubusercontent.com/46480892/110459021-74813d80-80f2-11eb-8dcf-620489289431.png)
# Glideshow
[![Generic badge](https://img.shields.io/badge/Swift-5.3-orange.svg)](https://shields.io/) [![Generic badge](https://img.shields.io/badge/iOS-13.0-blue.svg)](https://shields.io/) [![Generic badge](https://img.shields.io/badge/Version-1.1.1-orange.svg)](https://shields.io/) [![Generic badge](https://img.shields.io/badge/platform-ios-green.svg)](https://shields.io/)
A slideshow with *pizzazz!* Glideshow adds transitions to the slideshows labels to set it apart from other conventional "boring" slideshows,
![ezgif com-optimize](https://user-images.githubusercontent.com/46480892/128128971-f1dcb1bb-0339-448c-bd84-1f4f06213945.gif)
## Installation
#### CocoaPods
The Glideshow project is available via [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile:
```
pod 'Glideshow'
```
#### Swift Package Manager
Add `https://github.com/v15a1/Glideshow` as a Swift Package in Xcode and follow the instructions.
## How to use
Add a Glideshow view to your view hiearchy by Interface Builder or programmatically
#### Loading images
The contents of the slideshow can be set with the use of `GlideItem` as demonstrated below. Further customizations can be configured as mentioned in the **Configuration** section
***NOTE : Setting the `glideFactor`to 0 will result in the labels being stationary***
```swift
// @IBOutlet weak var glideshow : Glideshow!
var glideshow = Glideshow()
glideshow.items = [
GlideItem(title : "Hello there", description: "General Kenobi!", backgroundImage: UIImage(named: "image1")),
GlideItem(description: "General Kenobi!", backgroundImage: UIImage(named: "image2")),
GlideItem(title : "Hello there", backgroundImage: UIImage(named: "image3")),
GlideItem(title : "Hello there", description: "General Kenobi!")
// Network images
GlideItem(caption : "Hello there", description: "General Kenobi!", imageURL: "[ IMAGE URL ]")
]
```
## Configuration
The behaviour is configurable by the following properties
| Property | Description |
|----------|-------------|
| `interval` | Slideshow interval is seconds ( Default 0 ) |
| `isCircular` | Enables circular scrolling ( Default `true` )|
| `slideMargin` | Margin of the slides to the `UICollectionView` |
| `slidePadding` | Padding of the content |
| `defaultSlideColor` | Background color of the slides ( Default `UIColor.lightGray` ) |
| `labelSpacing` | Vertical spacing between labels within a slide ( Default 8 ) |
| `isGradientEnabled` | Displays gradient in the slide ( Default `false` ) |
| `gradientColor` | Base color of the gradient ( Default `UIColor.black.withAlphaComponent(0.6)`) |
| `gradientHeightFactor` | Height of the gradient based on slide height ( Default 0.5 ) |
| `titleFont` | Sets the `font` of the slide title |
| `descriptionFont` | Sets the `font` of the slide description |
| `captionFont` | Sets the `font` of the slide caption |
| `titleColor` | Sets the `textColor` of the slide title |
| `descriptionColor` | Sets the `textColor` of the slide description |
| `captionColor` | Sets the `textColor` of the slide slide caption |
| `titleGlideFactor` | Configures the speed of the transition of the title label ( Default 2 ) |
| `descriptionGlideFactor` | Configures the speed of the transition of the description label ( Default 3 ) |
| `captionGlideFactor` | Configures the speed of the transition of the caption label ( Default 1 ) |
| `pageIndicatorPosition` | Configures positon of the pge indicator |
| `placeHolderImage` | Configures slide background placeholder if a URL is specified, else `nil` |
### Contributions
A few issue to tackle
- [ ] Pass slide data through delegates
- [x] Support for Network images
- [ ] Multiple animations
- [ ] Customizable label positionings
- [ ] Code optimizations
Steps to contribution:
1. Fork the repo
2. Clone the project
3. Make your changes
4. Make a pull request
If you have any question please feel free to open an issue