From a885479a2cc6cb34588b3ec79e8c4f9c9088b048 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 26 Nov 2016 23:33:02 +0300 Subject: [PATCH] ImageLoader implementation added --- Cartfile | 1 + Cartfile.resolved | 1 + LeadKit/LeadKit.xcodeproj/project.pbxproj | 153 ++++++++++++++++++ .../Classes/ImageLoader/ImageLoader.swift | 76 +++++++++ .../ImageOperation/ImageFakeOperation.swift | 32 ++++ .../ImageLoadingOperation.swift | 38 +++++ .../ImageOperation/ImageOperation.swift | 35 ++++ .../ImageOperationPerformer.swift | 14 ++ .../ImageOperationWrapper.swift | 43 +++++ .../ImageOperation/ImageStartOperation.swift | 54 +++++++ .../ImagePlugin/ImageBluringPlugin.swift | 75 +++++++++ .../ImagePlugin/ImageFilteringPlugin.swift | 54 +++++++ .../ImageLoader/ImagePlugin/ImagePlugin.swift | 31 ++++ .../ImagePlugin/ImagePluginOperation.swift | 30 ++++ .../ImagePlugin/ImageResizingPlugin.swift | 59 +++++++ .../ImagePlugin/ImageRoundingPlugin.swift | 63 ++++++++ .../ImageSource/ImageSourceType.swift | 49 ++++++ .../ImageSource/NetworkImageSource.swift | 46 ++++++ .../UIImageView+ImageLoader.swift | 51 ++++++ .../Rx/ObservableType+Extensions.swift | 28 ++++ .../LeadKit/Functions/AbstractMethod.swift | 13 ++ .../Helpers/MemoryStore/MemoryStore.swift | 60 +++++++ LeadKit/LeadKit/Helpers/Sync/Sync.swift | 50 ++++++ 23 files changed, 1056 insertions(+) create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImageLoader.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageFakeOperation.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageLoadingOperation.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperation.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperationPerformer.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperationWrapper.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageStartOperation.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageBluringPlugin.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageFilteringPlugin.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImagePlugin.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImagePluginOperation.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageResizingPlugin.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageRoundingPlugin.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImageSource/ImageSourceType.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/ImageSource/NetworkImageSource.swift create mode 100644 LeadKit/LeadKit/Classes/ImageLoader/UIImageView+Loader/UIImageView+ImageLoader.swift create mode 100644 LeadKit/LeadKit/Extensions/Rx/ObservableType+Extensions.swift create mode 100644 LeadKit/LeadKit/Functions/AbstractMethod.swift create mode 100644 LeadKit/LeadKit/Helpers/MemoryStore/MemoryStore.swift create mode 100644 LeadKit/LeadKit/Helpers/Sync/Sync.swift diff --git a/Cartfile b/Cartfile index eaf37639..81cbed73 100644 --- a/Cartfile +++ b/Cartfile @@ -2,3 +2,4 @@ github "CocoaLumberjack/CocoaLumberjack" ~> 3.0.0 github "ReactiveX/RxSwift" "3.0.1" github "RxSwiftCommunity/RxAlamofire" "3.0.1" github "Hearst-DD/ObjectMapper" ~> 2.1 +github "Alamofire/AlamofireImage" ~> 3.2 diff --git a/Cartfile.resolved b/Cartfile.resolved index 7c9515ec..00640893 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -2,4 +2,5 @@ github "Alamofire/Alamofire" "4.2.0" github "CocoaLumberjack/CocoaLumberjack" "3.0.0" github "Hearst-DD/ObjectMapper" "2.2.1" github "ReactiveX/RxSwift" "3.0.1" +github "Alamofire/AlamofireImage" "3.2.0" github "RxSwiftCommunity/RxAlamofire" "3.0.1" diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index 3676e1bb..6c1c2714 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -7,6 +7,26 @@ objects = { /* Begin PBXBuildFile section */ + 24F5EFDA1DEA148A00B9ADB6 /* ObservableType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFD91DEA148A00B9ADB6 /* ObservableType+Extensions.swift */; }; + 24F5EFE11DEA187D00B9ADB6 /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFE01DEA187D00B9ADB6 /* ImageLoader.swift */; }; + 24F5EFE41DEA18F300B9ADB6 /* MemoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFE31DEA18F300B9ADB6 /* MemoryStore.swift */; }; + 24F5EFEA1DEA191A00B9ADB6 /* Sync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFE91DEA191A00B9ADB6 /* Sync.swift */; }; + 24F5EFF41DEA199300B9ADB6 /* ImageOperationWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFEE1DEA199300B9ADB6 /* ImageOperationWrapper.swift */; }; + 24F5EFF51DEA199300B9ADB6 /* ImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFEF1DEA199300B9ADB6 /* ImageOperation.swift */; }; + 24F5EFF61DEA199300B9ADB6 /* ImageOperationPerformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFF01DEA199300B9ADB6 /* ImageOperationPerformer.swift */; }; + 24F5EFF71DEA199300B9ADB6 /* ImageLoadingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFF11DEA199300B9ADB6 /* ImageLoadingOperation.swift */; }; + 24F5EFF81DEA199300B9ADB6 /* ImageFakeOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFF21DEA199300B9ADB6 /* ImageFakeOperation.swift */; }; + 24F5EFF91DEA199300B9ADB6 /* ImageStartOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFF31DEA199300B9ADB6 /* ImageStartOperation.swift */; }; + 24F5EFFE1DEA1A1500B9ADB6 /* ImageSourceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFFA1DEA1A1500B9ADB6 /* ImageSourceType.swift */; }; + 24F5F0011DEA1A1500B9ADB6 /* NetworkImageSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5EFFD1DEA1A1500B9ADB6 /* NetworkImageSource.swift */; }; + 24F5F0081DEA1A1F00B9ADB6 /* ImagePluginOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0021DEA1A1F00B9ADB6 /* ImagePluginOperation.swift */; }; + 24F5F0091DEA1A1F00B9ADB6 /* ImagePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0031DEA1A1F00B9ADB6 /* ImagePlugin.swift */; }; + 24F5F00A1DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0041DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift */; }; + 24F5F00B1DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0051DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift */; }; + 24F5F00C1DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0061DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift */; }; + 24F5F00D1DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0071DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift */; }; + 24F5F00F1DEA228F00B9ADB6 /* AbstractMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F00E1DEA228F00B9ADB6 /* AbstractMethod.swift */; }; + 24F5F0121DEA249900B9ADB6 /* UIImageView+ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5F0111DEA249900B9ADB6 /* UIImageView+ImageLoader.swift */; }; 78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78011A631D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift */; }; 78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78011AB21D48B53600EA16A2 /* ApiRequestParameters.swift */; }; 780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780D23421DA412470084620D /* CGImage+Alpha.swift */; }; @@ -75,6 +95,26 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 24F5EFD91DEA148A00B9ADB6 /* ObservableType+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObservableType+Extensions.swift"; sourceTree = ""; }; + 24F5EFE01DEA187D00B9ADB6 /* ImageLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoader.swift; sourceTree = ""; }; + 24F5EFE31DEA18F300B9ADB6 /* MemoryStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryStore.swift; sourceTree = ""; }; + 24F5EFE91DEA191A00B9ADB6 /* Sync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sync.swift; sourceTree = ""; }; + 24F5EFEE1DEA199300B9ADB6 /* ImageOperationWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageOperationWrapper.swift; sourceTree = ""; }; + 24F5EFEF1DEA199300B9ADB6 /* ImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageOperation.swift; sourceTree = ""; }; + 24F5EFF01DEA199300B9ADB6 /* ImageOperationPerformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageOperationPerformer.swift; sourceTree = ""; }; + 24F5EFF11DEA199300B9ADB6 /* ImageLoadingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadingOperation.swift; sourceTree = ""; }; + 24F5EFF21DEA199300B9ADB6 /* ImageFakeOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageFakeOperation.swift; sourceTree = ""; }; + 24F5EFF31DEA199300B9ADB6 /* ImageStartOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageStartOperation.swift; sourceTree = ""; }; + 24F5EFFA1DEA1A1500B9ADB6 /* ImageSourceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSourceType.swift; sourceTree = ""; }; + 24F5EFFD1DEA1A1500B9ADB6 /* NetworkImageSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageSource.swift; sourceTree = ""; }; + 24F5F0021DEA1A1F00B9ADB6 /* ImagePluginOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePluginOperation.swift; sourceTree = ""; }; + 24F5F0031DEA1A1F00B9ADB6 /* ImagePlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePlugin.swift; sourceTree = ""; }; + 24F5F0041DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageResizingPlugin.swift; sourceTree = ""; }; + 24F5F0051DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRoundingPlugin.swift; sourceTree = ""; }; + 24F5F0061DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageFilteringPlugin.swift; sourceTree = ""; }; + 24F5F0071DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageBluringPlugin.swift; sourceTree = ""; }; + 24F5F00E1DEA228F00B9ADB6 /* AbstractMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AbstractMethod.swift; sourceTree = ""; }; + 24F5F0111DEA249900B9ADB6 /* UIImageView+ImageLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+ImageLoader.swift"; sourceTree = ""; }; 78011A631D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+DefaultReuseIdentifier.swift"; sourceTree = ""; }; 78011AB21D48B53600EA16A2 /* ApiRequestParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiRequestParameters.swift; sourceTree = ""; }; 780D23421DA412470084620D /* CGImage+Alpha.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGImage+Alpha.swift"; sourceTree = ""; }; @@ -173,6 +213,95 @@ path = Frameworks; sourceTree = ""; }; + 24F5EFCA1DEA0F3200B9ADB6 /* ImageLoader */ = { + isa = PBXGroup; + children = ( + 24F5F0101DEA23E200B9ADB6 /* UIImageView */, + 24F5EFEB1DEA197400B9ADB6 /* ImageOperation */, + 24F5EFEC1DEA197400B9ADB6 /* ImagePlugin */, + 24F5EFED1DEA197400B9ADB6 /* ImageSource */, + 24F5EFE01DEA187D00B9ADB6 /* ImageLoader.swift */, + ); + path = ImageLoader; + sourceTree = ""; + }; + 24F5EFD81DEA144100B9ADB6 /* Rx */ = { + isa = PBXGroup; + children = ( + 24F5EFD91DEA148A00B9ADB6 /* ObservableType+Extensions.swift */, + ); + path = Rx; + sourceTree = ""; + }; + 24F5EFDB1DEA176500B9ADB6 /* Helpers */ = { + isa = PBXGroup; + children = ( + 24F5EFE21DEA18F300B9ADB6 /* MemoryStore */, + 24F5EFE81DEA191A00B9ADB6 /* Sync */, + ); + path = Helpers; + sourceTree = ""; + }; + 24F5EFE21DEA18F300B9ADB6 /* MemoryStore */ = { + isa = PBXGroup; + children = ( + 24F5EFE31DEA18F300B9ADB6 /* MemoryStore.swift */, + ); + path = MemoryStore; + sourceTree = ""; + }; + 24F5EFE81DEA191A00B9ADB6 /* Sync */ = { + isa = PBXGroup; + children = ( + 24F5EFE91DEA191A00B9ADB6 /* Sync.swift */, + ); + path = Sync; + sourceTree = ""; + }; + 24F5EFEB1DEA197400B9ADB6 /* ImageOperation */ = { + isa = PBXGroup; + children = ( + 24F5EFEF1DEA199300B9ADB6 /* ImageOperation.swift */, + 24F5EFEE1DEA199300B9ADB6 /* ImageOperationWrapper.swift */, + 24F5EFF01DEA199300B9ADB6 /* ImageOperationPerformer.swift */, + 24F5EFF11DEA199300B9ADB6 /* ImageLoadingOperation.swift */, + 24F5EFF21DEA199300B9ADB6 /* ImageFakeOperation.swift */, + 24F5EFF31DEA199300B9ADB6 /* ImageStartOperation.swift */, + ); + path = ImageOperation; + sourceTree = ""; + }; + 24F5EFEC1DEA197400B9ADB6 /* ImagePlugin */ = { + isa = PBXGroup; + children = ( + 24F5F0021DEA1A1F00B9ADB6 /* ImagePluginOperation.swift */, + 24F5F0031DEA1A1F00B9ADB6 /* ImagePlugin.swift */, + 24F5F0041DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift */, + 24F5F0051DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift */, + 24F5F0061DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift */, + 24F5F0071DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift */, + ); + path = ImagePlugin; + sourceTree = ""; + }; + 24F5EFED1DEA197400B9ADB6 /* ImageSource */ = { + isa = PBXGroup; + children = ( + 24F5EFFA1DEA1A1500B9ADB6 /* ImageSourceType.swift */, + 24F5EFFD1DEA1A1500B9ADB6 /* NetworkImageSource.swift */, + ); + path = ImageSource; + sourceTree = ""; + }; + 24F5F0101DEA23E200B9ADB6 /* UIImageView */ = { + isa = PBXGroup; + children = ( + 24F5F0111DEA249900B9ADB6 /* UIImageView+ImageLoader.swift */, + ); + name = UIImageView; + path = "UIImageView+Loader"; + sourceTree = ""; + }; 78011A651D47AF3000EA16A2 /* Enums */ = { isa = PBXGroup; children = ( @@ -284,6 +413,7 @@ children = ( 78B0FC7B1C6B2BAE00358B64 /* Logging */, 78753E2A1DE58BED006BC0FB /* Cursors */, + 24F5EFCA1DEA0F3200B9ADB6 /* ImageLoader */, ); path = Classes; sourceTree = ""; @@ -342,6 +472,7 @@ 78011A651D47AF3000EA16A2 /* Enums */, 78CFEE491C5C45E500F50370 /* Protocols */, 78D4B54B1DA650FC005B0764 /* Functions */, + 24F5EFDB1DEA176500B9ADB6 /* Helpers */, 78CFEE2D1C5C456B00F50370 /* LeadKit.h */, 78CFEE2F1C5C456B00F50370 /* Info.plist */, ); @@ -376,6 +507,7 @@ 786D78E61D53C355006B2CEA /* Alamofire */, 7884DB9A1DC1432B00E52A63 /* UserDefaults */, 789CC6091DE584C000F789D3 /* CursorType */, + 24F5EFD81DEA144100B9ADB6 /* Rx */, ); path = Extensions; sourceTree = ""; @@ -410,6 +542,7 @@ isa = PBXGroup; children = ( 78D4B5491DA64EAB005B0764 /* Any+TypeName.swift */, + 24F5F00E1DEA228F00B9ADB6 /* AbstractMethod.swift */, ); path = Functions; sourceTree = ""; @@ -624,34 +757,47 @@ 78CFEE541C5C45E500F50370 /* UIView+LoadFromNib.swift in Sources */, 78D4B5461DA64D49005B0764 /* UIViewController+DefaultStoryboardIdentifier.swift in Sources */, 7834236A1DB8D0E100A79643 /* StoryboardProtocol.swift in Sources */, + 24F5EFE41DEA18F300B9ADB6 /* MemoryStore.swift in Sources */, 78CFEE521C5C45E500F50370 /* UITableView+CellRegistration.swift in Sources */, 78B0FC7F1C6B2C4D00358B64 /* Log.swift in Sources */, 78753E2E1DE58DBA006BC0FB /* FixedPageCursor.swift in Sources */, 789CC60B1DE584F800F789D3 /* CursorType+Slice.swift in Sources */, 78753E2C1DE58BF9006BC0FB /* StaticCursor.swift in Sources */, + 24F5F00D1DEA1A1F00B9ADB6 /* ImageBluringPlugin.swift in Sources */, 78D4B54A1DA64EAB005B0764 /* Any+TypeName.swift in Sources */, 78CFEE571C5C45E500F50370 /* StaticNibNameProtocol.swift in Sources */, + 24F5F00B1DEA1A1F00B9ADB6 /* ImageRoundingPlugin.swift in Sources */, 788EC15A1CF64528009CFB6B /* UIStoryboard+InstantiateViewController.swift in Sources */, 787783671CA04D4A001CDC9B /* String+SizeCalculation.swift in Sources */, 78B036431DA4FEC90021D5CC /* CGImage+Transform.swift in Sources */, 78011A641D47ABC500EA16A2 /* UIView+DefaultReuseIdentifier.swift in Sources */, 786D78EC1D53C46E006B2CEA /* AlamofireManager+Extensions.swift in Sources */, 78B0FC811C6B2CD500358B64 /* App.swift in Sources */, + 24F5F00A1DEA1A1F00B9ADB6 /* ImageResizingPlugin.swift in Sources */, 78B036491DA562C30021D5CC /* CGImage+Template.swift in Sources */, 78753E301DE594B4006BC0FB /* MapCursor.swift in Sources */, + 24F5EFEA1DEA191A00B9ADB6 /* Sync.swift in Sources */, 780D23461DA416F80084620D /* CGContext+Initializers.swift in Sources */, + 24F5F00F1DEA228F00B9ADB6 /* AbstractMethod.swift in Sources */, 95B39A861D9D51250057BD54 /* String+Localization.swift in Sources */, + 24F5F0091DEA1A1F00B9ADB6 /* ImagePlugin.swift in Sources */, + 24F5EFFE1DEA1A1500B9ADB6 /* ImageSourceType.swift in Sources */, 78C36F7E1D801E3E00E7EBEA /* Double+Rounding.swift in Sources */, 78CFEE551C5C45E500F50370 /* NibNameProtocol.swift in Sources */, + 24F5EFDA1DEA148A00B9ADB6 /* ObservableType+Extensions.swift in Sources */, 78CFEE561C5C45E500F50370 /* ReuseIdentifierProtocol.swift in Sources */, + 24F5EFF51DEA199300B9ADB6 /* ImageOperation.swift in Sources */, 78A0FCC81DC366A10070B5E1 /* StoryboardProtocol+Extensions.swift in Sources */, + 24F5EFF61DEA199300B9ADB6 /* ImageOperationPerformer.swift in Sources */, 78B036411DA4D7060021D5CC /* UIImage+Extensions.swift in Sources */, + 24F5EFF81DEA199300B9ADB6 /* ImageFakeOperation.swift in Sources */, 78A0FCC71DC366A10070B5E1 /* StoryboardProtocol+DefaultBundle.swift in Sources */, 78753E241DE58A5D006BC0FB /* CursorError.swift in Sources */, 786D78E81D53C378006B2CEA /* AlamofireRequest+Extensions.swift in Sources */, 78C36F811D8021DD00E7EBEA /* UIColor+Hex.swift in Sources */, 78CFEE5B1C5C45E500F50370 /* ViewModelProtocol.swift in Sources */, 780D23431DA412470084620D /* CGImage+Alpha.swift in Sources */, + 24F5F0011DEA1A1500B9ADB6 /* NetworkImageSource.swift in Sources */, 78CFEE5A1C5C45E500F50370 /* ViewHeightProtocol.swift in Sources */, 787682FA1CAD40C300532AB3 /* StaticEstimatedViewHeightProtocol.swift in Sources */, 78A74EA91C6B373700FE9724 /* UIView+DefaultNibName.swift in Sources */, @@ -659,13 +805,20 @@ E126CBB31DB68DDA00E1B2F8 /* UICollectionView+CellRegistration.swift in Sources */, 78CFEE581C5C45E500F50370 /* StaticViewHeightProtocol.swift in Sources */, 787783631CA03CA0001CDC9B /* IndexPath+ImmutableIndexPath.swift in Sources */, + 24F5F0081DEA1A1F00B9ADB6 /* ImagePluginOperation.swift in Sources */, + 24F5EFE11DEA187D00B9ADB6 /* ImageLoader.swift in Sources */, 78B036471DA5624D0021D5CC /* CGImage+Creation.swift in Sources */, + 24F5EFF41DEA199300B9ADB6 /* ImageOperationWrapper.swift in Sources */, 789CC6081DE5835600F789D3 /* CursorType.swift in Sources */, + 24F5F00C1DEA1A1F00B9ADB6 /* ImageFilteringPlugin.swift in Sources */, 78B0364B1DA61EDE0021D5CC /* CGImage+Crop.swift in Sources */, 78B036451DA561D00021D5CC /* CGImage+Utils.swift in Sources */, + 24F5EFF71DEA199300B9ADB6 /* ImageLoadingOperation.swift in Sources */, 78CFEE591C5C45E500F50370 /* StoryboardIdentifierProtocol.swift in Sources */, 78011AB31D48B53600EA16A2 /* ApiRequestParameters.swift in Sources */, + 24F5EFF91DEA199300B9ADB6 /* ImageStartOperation.swift in Sources */, 78B0FC7D1C6B2BE200358B64 /* LogFormatter.swift in Sources */, + 24F5F0121DEA249900B9ADB6 /* UIImageView+ImageLoader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImageLoader.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImageLoader.swift new file mode 100644 index 00000000..a953fb26 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImageLoader.swift @@ -0,0 +1,76 @@ +// +// ImageLoader.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public class ImageLoader { + private let imageSource: ImageSource + + private let processedImageCache = MemoryStore() + private var loadingObservablesCache = [String : Observable]() + + private let loadingSyncLock = Sync.Lock() + + public func loadImage(key: TKey) -> ImageOperation { + let loadingObservable = Observable.deferred({ () -> Observable in + let imageCacheKey = key.description + return self.beginLoadingImage(key: key, imageCacheKey: imageCacheKey) + }) + .subcribeOnBackgroundScheduler() + + return ImageLoadingOperation(imageLoader: self, key: key, loadingObservable: loadingObservable) + } + + internal func performImageOperation(operation: ImageOperation) -> Observable { + return Observable.deferred({ () -> Observable in + let operationCachingKey = operation._cachingKey + + if let cachedImage = self.processedImageCache.loadData(key: operationCachingKey) { + return Observable.just(cachedImage) + } else { + return operation._imageSource + .observeOnBackgroundScheduler() + .do(onNext: { (image) in + self.processedImageCache.storeData(key: operationCachingKey, data: image) + }) + } + }) + .subcribeOnBackgroundScheduler() + } + + private func beginLoadingImage(key: TKey, imageCacheKey: String) -> Observable { + return loadingSyncLock.sync { + if let loadingObservable = loadingObservablesCache[imageCacheKey] { + return loadingObservable + } + + let loadingObservable = imageSource.loadImage(key) + .observeOnBackgroundScheduler() + .do(onDispose: { + self.removeLoadingObservable(cacheKey: imageCacheKey) + }) + .shareReplay(1) + + loadingObservablesCache[imageCacheKey] = loadingObservable + + return loadingObservable + } + } + + private func removeLoadingObservable(cacheKey: String) { + loadingSyncLock.sync { + loadingObservablesCache.removeValue(forKey: cacheKey) + return + } + } + + public init(imageSource: TSource) where TSource.KeyType == TKey { + self.imageSource = imageSource.asImageSource() + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageFakeOperation.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageFakeOperation.swift new file mode 100644 index 00000000..198febdf --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageFakeOperation.swift @@ -0,0 +1,32 @@ +// +// ImageFakeOperation.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public class ImageFakeOperation: ImageOperationWrapper { + public typealias PerformerObservableHandler = (Observable) -> Observable + + private let performerObservableHandler: PerformerObservableHandler + + public override func _preparePerformerObservable(performObservable: Observable) -> Observable { + return super._preparePerformerObservable(performObservable: performerObservableHandler(performObservable)) + } + + public init(originalOperation: ImageOperation, performerObservableHandler: @escaping PerformerObservableHandler) { + self.performerObservableHandler = performerObservableHandler + super.init(originalOperation: originalOperation) + } +} + + +public extension ImageOperation { + public func performWith(_ perfrormHandler: @escaping ImageFakeOperation.PerformerObservableHandler) -> ImageOperation { + return ImageFakeOperation(originalOperation: self, performerObservableHandler: perfrormHandler) + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageLoadingOperation.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageLoadingOperation.swift new file mode 100644 index 00000000..266dec19 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageLoadingOperation.swift @@ -0,0 +1,38 @@ +// +// ImageLoadingOperation.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public class ImageLoadingOperation: ImageOperation, ImageOperationPerformer { + public override var _imageSource: Observable { + return loadingObservable + } + + public override var _performer: ImageOperationPerformer { + return self + } + + public override var _cachingKey: String { + return key.description + } + + public func performImageOperation(operation: ImageOperation) -> Observable { + return imageLoader.performImageOperation(operation: operation) + } + + private let loadingObservable: Observable + private let imageLoader: ImageLoader + private let key: TKey + + public init(imageLoader: ImageLoader, key: TKey, loadingObservable: Observable) { + self.imageLoader = imageLoader + self.key = key + self.loadingObservable = loadingObservable + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperation.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperation.swift new file mode 100644 index 00000000..b4f5e158 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperation.swift @@ -0,0 +1,35 @@ +// +// ImageOperation.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public class ImageOperation: ObservableType { + public typealias E = UIImage + + open var _imageSource: Observable { + abstractMethod("_imageSource") + } + + open var _performer: ImageOperationPerformer { + abstractMethod("_performer") + } + + open var _cachingKey: String { + abstractMethod("_cachingKey") + } + + open func _preparePerformerObservable(performObservable: Observable) -> Observable { + return performObservable + } + + public func subscribe(_ observer: O) -> Disposable where O.E == UIImage { + return _preparePerformerObservable(performObservable: _performer.performImageOperation(operation: self)) + .subscribe(observer) + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperationPerformer.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperationPerformer.swift new file mode 100644 index 00000000..65f1f7a6 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperationPerformer.swift @@ -0,0 +1,14 @@ +// +// ImageOperationPerformer.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public protocol ImageOperationPerformer { + func performImageOperation(operation: ImageOperation) -> Observable +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperationWrapper.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperationWrapper.swift new file mode 100644 index 00000000..6aa949c2 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageOperationWrapper.swift @@ -0,0 +1,43 @@ +// +// ImageOperationWrapper.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public class ImageOperationWrapper: ImageOperation { + open override var _imageSource: Observable { + return _prepareWorkingObservable(workingObservable: originalOperation._imageSource) + } + + open override var _performer: ImageOperationPerformer { + return originalOperation._performer + } + + open override var _cachingKey: String { + return _prepareCachingKey(cachingKey: originalOperation._cachingKey) + } + + open func _prepareWorkingObservable(workingObservable: Observable) -> Observable { + return workingObservable + } + + open override func _preparePerformerObservable(performObservable: Observable) -> Observable { + return super._preparePerformerObservable(performObservable: + originalOperation._preparePerformerObservable(performObservable: performObservable)) + } + + open func _prepareCachingKey(cachingKey: String) -> String { + return cachingKey + } + + private let originalOperation: ImageOperation + + public init(originalOperation: ImageOperation) { + self.originalOperation = originalOperation + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageStartOperation.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageStartOperation.swift new file mode 100644 index 00000000..d190be04 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImageOperation/ImageStartOperation.swift @@ -0,0 +1,54 @@ +// +// ImageStartOperation.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public class ImageStartOperation: ImageOperation, ImageOperationPerformer { + public override var _imageSource: Observable { + return source + } + + public override var _performer: ImageOperationPerformer { + return self + } + + public override var _cachingKey: String { + return key + } + + public func performImageOperation(operation: ImageOperation) -> Observable { + return operation._imageSource + } + + private let source: Observable + private let key: String + + public init(source: Observable, key: String? = nil) { + self.key = key ?? String() + self.source = source + } +} + +public extension UIImage { + public func asImageOperation(key: String? = nil) -> ImageOperation { + return Observable.just(self).asImageOperation() + } +} + +public extension ImageOperation { + public func asImageOperation(key: String? = nil) -> ImageOperation { + return self + } +} + +public extension ObservableConvertibleType where E == UIImage { + public func asImageOperation(key: String? = nil) -> ImageOperation { + return ImageStartOperation(source: self.asObservable(), key: key) + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageBluringPlugin.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageBluringPlugin.swift new file mode 100644 index 00000000..2d13486e --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageBluringPlugin.swift @@ -0,0 +1,75 @@ +// +// ImageBluringPlugin.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation + +public class ImageBluringPlugin: ImageFilteringPlugin { + public enum BlurType { + case box(radius: CGFloat) + case disc(radius: CGFloat) + case gaussian(radius: CGFloat) + case masked(radius: CGFloat, maskImage: UIImage) + case median() + case motion(radius: CGFloat, angle: CGFloat) + + public func filterParams() -> [String : Any] { + var resultRadius: CGFloat? + var resultParams = [String : Any]() + + switch self { + case .box(let radius): + resultRadius = radius + case .disc(let radius): + resultRadius = radius + case .gaussian(let radius): + resultRadius = radius + case .masked(let radius, let maskImage): + resultRadius = radius + resultParams["inputMask"] = maskImage.cgImage + case.median: + break + case .motion(let radius, let angle): + resultRadius = radius + resultParams["inputAngle"] = angle + } + + if let resultRadius = resultRadius { + resultParams["inputRadius"] = resultRadius + } + + return resultParams + } + + public func filterName() -> String { + switch self { + case .box: + return "CIBoxBlur" + case .disc: + return "CIDiscBlur" + case .gaussian: + return "CIGaussianBlur" + case .masked: + return "CIMaskedVariableBlur" + case.median: + return "CIMedianFilter" + case .motion: + return "CIMotionBlur" + } + } + } + + public init(blurType: BlurType) { + super.init(name: blurType.filterName(), params: blurType.filterParams()) + } +} + +public extension ImageOperation { + public func blur(blurType: ImageBluringPlugin.BlurType) -> ImageOperation { + return ImagePluginOperation(originalOperation: self, plugin: ImageBluringPlugin(blurType: blurType)) + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageFilteringPlugin.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageFilteringPlugin.swift new file mode 100644 index 00000000..6c21f47e --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageFilteringPlugin.swift @@ -0,0 +1,54 @@ +// +// ImageFilteringPlugin.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import AlamofireImage +import RxSwift + +public class ImageFilteringPlugin: ImagePluginType { + public enum FilterError: Error { + case unknownError(description: String) + } + + public typealias FitlerParamsType = [String : Any] + + private let filterName: String + private let filterParams: FitlerParamsType? + + public var pluginKey: String { + var resultKey = filterName + if let filterParams = filterParams { + resultKey += filterParams.description + } + + return resultKey + } + + public func perform(image: UIImage) -> Observable { + return Observable.deferred({ () -> Observable in + guard let reusltImage = image.af_imageFiltered(withCoreImageFilter: self.filterName, parameters: self.filterParams) else { + throw FilterError.unknownError(description: "ImageFilterPlugin failed with filter named \(self.filterName)") + } + + return Observable.just(reusltImage) + }) + .subcribeOnBackgroundScheduler() + } + + public init(name: String, params: FitlerParamsType? = nil) { + filterName = name + filterParams = params + } +} + + +public extension ImageOperation { + public func filter(name: String, params: ImageFilteringPlugin.FitlerParamsType? = nil) -> ImageOperation { + return ImagePluginOperation(originalOperation: self, plugin: ImageFilteringPlugin(name: name, params: params)) + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImagePlugin.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImagePlugin.swift new file mode 100644 index 00000000..7560f059 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImagePlugin.swift @@ -0,0 +1,31 @@ +// +// ImagePlugin.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public protocol ImagePluginType { + func perform(image: UIImage) -> Observable + var pluginKey: String { get } +} + +extension UIImage { + public func performPlugins(plugins: [ImagePluginType]) -> Observable { + var observable = Observable.just(self) + + for plugin in plugins { + observable = observable.flatMap(plugin.perform) + } + + return observable + } + + public func performPlugins(plugins: ImagePluginType ...) -> Observable { + return performPlugins(plugins: plugins) + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImagePluginOperation.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImagePluginOperation.swift new file mode 100644 index 00000000..453bcfa8 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImagePluginOperation.swift @@ -0,0 +1,30 @@ +// +// ImagePluginOperation.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public class ImagePluginOperation: ImageOperationWrapper { + public override func _prepareCachingKey(cachingKey: String) -> String { + return cachingKey + plugin.pluginKey + } + + public override func _prepareWorkingObservable(workingObservable: Observable) -> Observable { + return workingObservable + .flatMap({ (image) -> Observable in + return self.plugin.perform(image: image) + }) + } + + private let plugin: ImagePluginType + + public init(originalOperation: ImageOperation, plugin: ImagePluginType) { + self.plugin = plugin + super.init(originalOperation: originalOperation) + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageResizingPlugin.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageResizingPlugin.swift new file mode 100644 index 00000000..ae412019 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageResizingPlugin.swift @@ -0,0 +1,59 @@ +// +// ImageResizingPlugin.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import AlamofireImage +import RxSwift + +public class ImageResizingPlugin: ImagePluginType { + public enum Mode: String { + case scale + case aspectFit + case aspectFill + + static var `default`: Mode { + return .scale + } + } + + private let mode: Mode + private let size: CGSize + + public var pluginKey: String { + return mode.rawValue + String(describing: size.width) + String(describing: size.height) + } + + public func perform(image: UIImage) -> Observable { + return Observable.deferred({ () -> Observable in + return Observable.just(self.beginPerform(image: image)) + }) + .subcribeOnBackgroundScheduler() + } + + private func beginPerform(image: UIImage) -> UIImage { + switch mode { + case .scale: + return image.af_imageScaled(to: size) + case .aspectFit: + return image.af_imageAspectScaled(toFit: size) + case .aspectFill: + return image.af_imageAspectScaled(toFill: size) + } + } + + public init(size: CGSize, mode: Mode = .default) { + self.size = size + self.mode = mode; + } +} + +public extension ImageOperation { + public func resize(size: CGSize, mode: ImageResizingPlugin.Mode = .default) -> ImageOperation { + return ImagePluginOperation(originalOperation: self, plugin: ImageResizingPlugin(size: size, mode: mode)) + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageRoundingPlugin.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageRoundingPlugin.swift new file mode 100644 index 00000000..b330b63e --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImagePlugin/ImageRoundingPlugin.swift @@ -0,0 +1,63 @@ +// +// ImageRoundingPlugin.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import AlamofireImage +import RxSwift + +public class ImageRoundingPlugin: ImagePluginType { + public enum Mode { + case cicrular + case corners(radius: CGFloat) + + static var `default`: Mode { + return .cicrular + } + + public var key: String { + switch self { + case .cicrular: + return "Circular" + case .corners(let radius): + return "Corners" + String(describing: radius) + } + } + } + + private let mode: Mode + + public var pluginKey: String { + return mode.key + } + + public func perform(image: UIImage) -> Observable { + return Observable.deferred({ () -> Observable in + return Observable.just(self.beginPerform(image: image)) + }) + .subcribeOnBackgroundScheduler() + } + + private func beginPerform(image: UIImage) -> UIImage { + switch mode { + case .cicrular: + return image.af_imageRoundedIntoCircle() + case .corners(let radius): + return image.af_imageRounded(withCornerRadius: radius) + } + } + + public init(mode: Mode = .default) { + self.mode = mode; + } +} + +public extension ImageOperation { + public func round(mode: ImageRoundingPlugin.Mode = .default) -> ImageOperation { + return ImagePluginOperation(originalOperation: self, plugin: ImageRoundingPlugin(mode: mode)) + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImageSource/ImageSourceType.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImageSource/ImageSourceType.swift new file mode 100644 index 00000000..69dde2e4 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImageSource/ImageSourceType.swift @@ -0,0 +1,49 @@ +// +// ImageSource.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public protocol ImageSourceType { + associatedtype KeyType + + func loadImage(_ key: KeyType) -> Observable + + func asImageSource() -> ImageSource +} + +extension ImageSourceType { + public func asImageSource() -> ImageSource { + return ImageSource(source: self) + } +} + +public class ImageSource: ImageSourceType { + public typealias KeyType = TKey + public typealias ImageLoadingHandler = (KeyType) -> Observable + + private let sourceLoadImageHandler: ImageLoadingHandler + + public func loadImage(_ key: KeyType) -> Observable { + return sourceLoadImageHandler(key) + } + + public init(imageLoadingHandler: @escaping ImageLoadingHandler) { + sourceLoadImageHandler = imageLoadingHandler + } + + public convenience init(source: TSource) where TSource.KeyType == KeyType { + self.init(imageLoadingHandler: source.loadImage) + } +} + +extension ImageSource { + public func asImageSource() -> ImageSource { + return self + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/ImageSource/NetworkImageSource.swift b/LeadKit/LeadKit/Classes/ImageLoader/ImageSource/NetworkImageSource.swift new file mode 100644 index 00000000..c2754f60 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/ImageSource/NetworkImageSource.swift @@ -0,0 +1,46 @@ +// +// NetworkImageSource.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import RxSwift +import Alamofire +import AlamofireImage + +public class NetworkImageSource: ImageSourceType where TKey: CustomStringConvertible { + public typealias KeyType = TKey + + private let sessionManager: SessionManager + + public func loadImage(_ key: KeyType) -> Observable { + return Observable.deferred({ () -> Observable in + return self.beginLoadImage(try key.asURL()) + }) + } + + private func beginLoadImage(_ url: URL) -> Observable { + return Observable.create({ (observer) -> Disposable in + let request = self.sessionManager.request(url) + .responseImage(completionHandler: { (response) in + switch response.result { + case .success(let image): + observer.onNext(image) + observer.onCompleted() + case .failure(let error): + observer.onError(error) + } + }) + + return Disposables.create { + request.cancel() + } + }) + } + + public init(sessionManager: SessionManager = SessionManager.default) { + self.sessionManager = sessionManager + } +} diff --git a/LeadKit/LeadKit/Classes/ImageLoader/UIImageView+Loader/UIImageView+ImageLoader.swift b/LeadKit/LeadKit/Classes/ImageLoader/UIImageView+Loader/UIImageView+ImageLoader.swift new file mode 100644 index 00000000..d4771ab2 --- /dev/null +++ b/LeadKit/LeadKit/Classes/ImageLoader/UIImageView+Loader/UIImageView+ImageLoader.swift @@ -0,0 +1,51 @@ +// +// UIImageView+ImageLoader.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public extension ImageResizingPlugin.Mode { + public static func fromUIContentMode(contentMode: UIViewContentMode) -> ImageResizingPlugin.Mode { + switch contentMode { + case .scaleAspectFill: + return .aspectFill + case .scaleAspectFit: + return .aspectFit + default: + return .scale + } + } +} + +public extension UIImageView { + public func loadImage(key: TKey, loader: ImageLoader, resizeImage: Bool = true, + placeholder: UIImage? = nil) -> ImageOperation { + + var resultOperation = loader.loadImage(key: key) + + if resizeImage { + resultOperation = resultOperation.resize(size: frame.size, mode: ImageResizingPlugin.Mode.fromUIContentMode(contentMode: contentMode)) + } + + resultOperation = resultOperation.performWith({ (observable) -> Observable in + var resultObservable = observable.observeOnMainScheduler() + .do(onNext: { [weak self] (image) in + self?.image = image + }) + .takeUntil(self.rx.deallocated) + + if let placeholder = placeholder { + resultObservable = resultObservable.startWith(placeholder) + } + + return resultObservable + }) + + return resultOperation + } +} diff --git a/LeadKit/LeadKit/Extensions/Rx/ObservableType+Extensions.swift b/LeadKit/LeadKit/Extensions/Rx/ObservableType+Extensions.swift new file mode 100644 index 00000000..89381baa --- /dev/null +++ b/LeadKit/LeadKit/Extensions/Rx/ObservableType+Extensions.swift @@ -0,0 +1,28 @@ +// +// ObservableType+Extensions.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation +import RxSwift + +public extension ObservableType { + public func subcribeOnBackgroundScheduler() -> Observable { + return self.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default)) + } + + public func observeOnBackgroundScheduler() -> Observable { + return self.observeOn(ConcurrentDispatchQueueScheduler(qos: .default)) + } + + public func subcribeOnMainScheduler() -> Observable { + return self.subscribeOn(MainScheduler.instance) + } + + public func observeOnMainScheduler() -> Observable { + return self.observeOn(MainScheduler.instance) + } +} diff --git a/LeadKit/LeadKit/Functions/AbstractMethod.swift b/LeadKit/LeadKit/Functions/AbstractMethod.swift new file mode 100644 index 00000000..97bb3264 --- /dev/null +++ b/LeadKit/LeadKit/Functions/AbstractMethod.swift @@ -0,0 +1,13 @@ +// +// AbstractMethod.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation + +public func abstractMethod(_ methodName: String = "Method") -> Never { + fatalError("\(methodName) has not been implemented") +} diff --git a/LeadKit/LeadKit/Helpers/MemoryStore/MemoryStore.swift b/LeadKit/LeadKit/Helpers/MemoryStore/MemoryStore.swift new file mode 100644 index 00000000..df5902f7 --- /dev/null +++ b/LeadKit/LeadKit/Helpers/MemoryStore/MemoryStore.swift @@ -0,0 +1,60 @@ +// +// MemoryStore.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + +import Foundation + +public class MemoryStore { + public typealias KeyType = TKey + public typealias DataType = TData + + private var syncLock = Sync.Lock() + private var dictionaryStore: [KeyType : DataType] = [:] + private let clearOnMemoryWarning: Bool + + public init(clearOnMemoryWarning: Bool = false) { + self.clearOnMemoryWarning = clearOnMemoryWarning + + if clearOnMemoryWarning { + NotificationCenter.default.addObserver(self, selector: #selector(onMemoryWarning), + name: NSNotification.Name.UIApplicationDidReceiveMemoryWarning, object: UIApplication.shared) + } + } + + public func removeAllData() { + syncLock.sync { + return dictionaryStore.removeAll() + } + } + + public func loadData(key: KeyType) -> DataType? { + return syncLock.sync { + return dictionaryStore[key] + } + } + + public func storeData(key: TKey, data: DataType?) { + syncLock.sync { + guard let data = data else { + dictionaryStore.removeValue(forKey: key) + return + } + + dictionaryStore[key] = data + } + } + + @objc private func onMemoryWarning() { + removeAllData() + } + + deinit { + if clearOnMemoryWarning { + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidReceiveMemoryWarning, object: UIApplication.shared) + } + } +} diff --git a/LeadKit/LeadKit/Helpers/Sync/Sync.swift b/LeadKit/LeadKit/Helpers/Sync/Sync.swift new file mode 100644 index 00000000..bd37125f --- /dev/null +++ b/LeadKit/LeadKit/Helpers/Sync/Sync.swift @@ -0,0 +1,50 @@ +// +// Sync.swift +// LeadKit +// +// Created by Anton on 26.11.16. +// Copyright © 2016 Touch Instinct. All rights reserved. +// + + +public final class Sync { + public final class Lock { + public func lock() { + Sync.beginSyncLock(self) + } + + public func unlock() { + Sync.endSyncLock(self) + } + + public func sync(block: () throws -> Void) rethrows { + try Sync.synchronized(lockObj: self, block: block) + } + + public func sync(block: () throws -> T) rethrows -> T { + return try Sync.synchronized(lockObj: self, block: block) + } + } + + public static func beginSyncLock(_ lockObj: Any) -> Void { + objc_sync_enter(lockObj) + } + + public static func endSyncLock(_ lockObj: Any) -> Void { + objc_sync_exit(lockObj) + } + + public static func synchronized(lockObj: Any, block: () throws -> Void) rethrows -> Void { + beginSyncLock(lockObj) + try block() + endSyncLock(lockObj) + } + + public static func synchronized(lockObj: Any, block: () throws -> T) rethrows -> T { + beginSyncLock(lockObj) + let result: T = try block() + endSyncLock(lockObj) + + return result + } +}