Compare commits

...

31 Commits

Author SHA1 Message Date
Ivan Smolin de0d556bae simplify TotalCountCursor constraints 2018-03-06 15:22:44 +03:00
Ivan Smolin 7ab28068ad Merge branch 'master' into feature/general_loading_and_pagination
# Conflicts:
#	LeadKit.xcodeproj/project.pbxproj
2018-01-18 16:03:05 +03:00
Ivan Smolin 38eb97d60f image rotation 2017-12-20 16:09:26 +03:00
Ivan Smolin 99ef62dfea Rx DataSourceProtocol conformance 2017-12-06 14:17:26 +03:00
Ivan Smolin b5f36019ae fix renderTemplate for support UIImage+Extensions 2017-12-05 19:49:43 +03:00
Ivan Smolin 2c59c0ff90 proper indent 2017-12-05 19:48:45 +03:00
Ivan Smolin bd5fc5bfb1 Merge branch 'master' into feature/general_loading_and_pagination
# Conflicts:
#	Sources/Extensions/Alamofire/AlamofireManager+Extensions.swift
2017-12-05 16:56:16 +03:00
Ivan Smolin 0677931dd3 Ability to map primitive response types (`String`, `Int`, `[String]`, etc.). 2017-12-05 16:04:49 +03:00
Ivan Smolin 85b1a119e2 Merge branch 'master' into feature/general_loading_and_pagination
# Conflicts:
#	Sources/Extensions/Alamofire/AlamofireManager+Extensions.swift
2017-12-01 17:21:19 +03:00
Ivan Smolin b3f963a524 Revert "fix crash when NetworkService creates lazy in background thread (globalinit_*_func0)"
This reverts commit 149467cd4d.
2017-12-01 15:52:32 +03:00
Ivan Smolin 91b6cc8f3c add acceptable status codes to network service constructor 2017-12-01 15:28:53 +03:00
Ivan Smolin 149467cd4d fix crash when NetworkService creates lazy in background thread (globalinit_*_func0) 2017-12-01 15:11:47 +03:00
Ivan Smolin 5c66ad9c69 pass acceptable status codes 2017-12-01 14:59:44 +03:00
Ivan Smolin 5f001c75f0 Merge branch 'master' into feature/general_loading_and_pagination
# Conflicts:
#	Sources/Classes/Views/SeparatorCellTableCell/SeparatorTableCell.swift
2017-12-01 14:59:23 +03:00
Ivan Smolin f685b8555d separator cell 2017-11-14 17:25:04 +03:00
Ivan Smolin 8b17fd5d17 fix double calling load more 2017-11-13 16:16:50 +03:00
Ivan Smolin e76f557d2e fix total count cursor 2017-11-12 22:38:42 +03:00
Ivan Smolin 9a47fff654 fix total count cursor result sharing 2017-11-12 22:07:46 +03:00
Ivan Smolin b0e3463581 fix safe index 2017-11-10 12:57:30 +03:00
Ivan Smolin 3cdae4b398 small fix 2017-11-09 13:17:32 +03:00
Ivan Smolin 5a6a8e77a2 Merge branch 'master' into feature/general_loading_and_pagination 2017-11-09 13:15:26 +03:00
Ivan Smolin a9e3ded0c2 pagination wrapper fixes 2017-11-09 13:14:46 +03:00
Ivan Smolin dd170e0873 naming fix 2017-11-08 17:57:17 +03:00
Ivan Smolin ba94c670b3 pass xib view to configure function 2017-11-08 14:22:37 +03:00
Ivan Smolin 03cc92553c pagination fixes 2017-11-08 14:22:23 +03:00
Ivan Smolin 334105c7af change parameter naming 2017-11-03 20:44:04 +03:00
Ivan Smolin 54b3b22d72 total count cursor fixes 2017-11-03 13:33:55 +03:00
Ivan Smolin 80c416f247 pass datasource to pagination results 2017-11-03 05:02:19 +03:00
Ivan Smolin cf6f8e9268 fix wrong error state 2017-11-02 22:28:05 +03:00
Ivan Smolin 02dfc709c3 temporary change podspec version 2017-11-02 20:58:10 +03:00
Ivan Smolin d33a865b3e PaginationWrapper now works with UICollectionView 2017-11-02 20:45:29 +03:00
48 changed files with 2313 additions and 876 deletions

View File

@ -101,9 +101,8 @@ Pod::Spec.new do |s|
"Sources/Classes/Views/SeparatorRowBox/*",
"Sources/Classes/Views/SeparatorCell/*",
"Sources/Classes/Views/EmptyCell/*",
"Sources/Classes/Pagination/PaginationTableViewWrapper.swift",
"Sources/Classes/Pagination/PaginationWrapper.swift",
"Sources/Extensions/NetworkService/NetworkService+ActivityIndicator.swift",
"Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift",
"Sources/Extensions/TableDirector/*",
"Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift"
]

View File

@ -41,11 +41,6 @@
6714625D1EB3396E00EAB194 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461CE1EB3396E00EAB194 /* LogFormatter.swift */; };
6714625E1EB3396E00EAB194 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461CE1EB3396E00EAB194 /* LogFormatter.swift */; };
6714625F1EB3396E00EAB194 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461CE1EB3396E00EAB194 /* LogFormatter.swift */; };
671462601EB3396E00EAB194 /* PaginationTableViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D01EB3396E00EAB194 /* PaginationTableViewWrapper.swift */; };
671462641EB3396E00EAB194 /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */; };
671462651EB3396E00EAB194 /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */; };
671462661EB3396E00EAB194 /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */; };
671462671EB3396E00EAB194 /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */; };
671462681EB3396E00EAB194 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D31EB3396E00EAB194 /* NetworkService.swift */; };
671462691EB3396E00EAB194 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D31EB3396E00EAB194 /* NetworkService.swift */; };
6714626A1EB3396E00EAB194 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D31EB3396E00EAB194 /* NetworkService.swift */; };
@ -108,7 +103,6 @@
671462AD1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */; };
671462AE1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */; };
671462AF1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */; };
671462B41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift */; };
671462B81EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F61EB3396E00EAB194 /* Sequence+ConcurrentMap.swift */; };
671462B91EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F61EB3396E00EAB194 /* Sequence+ConcurrentMap.swift */; };
671462BA1EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F61EB3396E00EAB194 /* Sequence+ConcurrentMap.swift */; };
@ -282,9 +276,27 @@
671463CF1EB34B1E00EAB194 /* TestView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 671463B71EB34B1E00EAB194 /* TestView.xib */; };
67186B311EB248F100CFAFFB /* LeadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67186B281EB248F100CFAFFB /* LeadKit.framework */; };
67186B3F1EB24A1900CFAFFB /* LeadKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 67186B201EB247A200CFAFFB /* LeadKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
672CC2A81FEA6A6A00EBFB0A /* RotateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */; };
672CC2A91FEA6A7400EBFB0A /* RotateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */; };
672CC2AA1FEA6A7500EBFB0A /* RotateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */; };
672CC2AB1FEA6A7600EBFB0A /* RotateDrawingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */; };
672CC2B31FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */; };
672CC2B41FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */; };
672CC2B51FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */; };
672CC2B61FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */; };
6740D5D21FABDA46006BB7C0 /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */; };
6740D5D31FABDA46006BB7C0 /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */; };
6740D5D41FABDA46006BB7C0 /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */; };
6740D5D51FABDA47006BB7C0 /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */; };
674AF55C1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */; };
674AF55D1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */; };
674AF55E1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */; };
674CC1DA1FAB8D06007B0EAE /* UICollectionView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BFD1FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift */; };
674CC1DB1FAB8D09007B0EAE /* PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BF11FAB78770089D1B1 /* PaginationWrappable.swift */; };
674CC1DD1FAB8D0A007B0EAE /* PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BF11FAB78770089D1B1 /* PaginationWrappable.swift */; };
674CC1DE1FAB8D1B007B0EAE /* Single+CursorExhausted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A071FAB50BB008175B4 /* Single+CursorExhausted.swift */; };
674CC1DF1FAB8D1C007B0EAE /* Single+CursorExhausted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A071FAB50BB008175B4 /* Single+CursorExhausted.swift */; };
674CC1E01FAB8D1D007B0EAE /* Single+CursorExhausted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A071FAB50BB008175B4 /* Single+CursorExhausted.swift */; };
675C1FB41F97CA32007D5249 /* AppearanceConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F118461F8FEF97004AADAF /* AppearanceConfigurable.swift */; };
675C1FB51F97CA33007D5249 /* AppearanceConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F118461F8FEF97004AADAF /* AppearanceConfigurable.swift */; };
675C1FB61F97CA33007D5249 /* AppearanceConfigurable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F118461F8FEF97004AADAF /* AppearanceConfigurable.swift */; };
@ -305,6 +317,9 @@
6771DFEC1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6771DFE91EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift */; };
6771DFED1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6771DFE91EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift */; };
6782BBA91EB31D5A0086E0B8 /* LeadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6782BBA01EB31D590086E0B8 /* LeadKit.framework */; };
678B43F01FBA3D5500D1F77D /* SeparatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678B43EF1FBA3D5500D1F77D /* SeparatorCell.swift */; };
678B43F71FBA3DB100D1F77D /* SeparatorCell+DefaultConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678B43F61FBA3DB100D1F77D /* SeparatorCell+DefaultConfiguration.swift */; };
678B43FC1FBA3DF300D1F77D /* SeparatorCell+UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 678B43FB1FBA3DF300D1F77D /* SeparatorCell+UITableViewCell.swift */; };
67952C3C1EB3266100B3BA1A /* LeadKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 67186B201EB247A200CFAFFB /* LeadKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
67952C3D1EB3266200B3BA1A /* LeadKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 67186B201EB247A200CFAFFB /* LeadKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
67952DCE1EB327B500B3BA1A /* LeadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67952DC51EB327B400B3BA1A /* LeadKit.framework */; };
@ -315,11 +330,70 @@
67A1FF941EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A1FF931EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift */; };
67A1FF951EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A1FF931EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift */; };
67A1FF971EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A1FF931EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift */; };
67B5A34C1FD8074800F36C36 /* Rx+DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B5A34B1FD8074800F36C36 /* Rx+DataSourceProtocol.swift */; };
67CB1BF21FAB78770089D1B1 /* PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BF11FAB78770089D1B1 /* PaginationWrappable.swift */; };
67CB1BF61FAB78DC0089D1B1 /* AnyPaginationWrappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BF51FAB78DC0089D1B1 /* AnyPaginationWrappableView.swift */; };
67CB1BFA1FAB79530089D1B1 /* UITableView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BF91FAB79530089D1B1 /* UITableView+PaginationWrappable.swift */; };
67CB1BFB1FAB79530089D1B1 /* UITableView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BF91FAB79530089D1B1 /* UITableView+PaginationWrappable.swift */; };
67CB1BFC1FAB79530089D1B1 /* UITableView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BF91FAB79530089D1B1 /* UITableView+PaginationWrappable.swift */; };
67CB1BFE1FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BFD1FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift */; };
67CB1C001FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1BFD1FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift */; };
67CB1C021FAB7A640089D1B1 /* PaginationWrapperDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1C011FAB7A640089D1B1 /* PaginationWrapperDelegate.swift */; };
67CB1C031FAB7A640089D1B1 /* PaginationWrapperDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1C011FAB7A640089D1B1 /* PaginationWrapperDelegate.swift */; };
67CB1C041FAB7A640089D1B1 /* PaginationWrapperDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1C011FAB7A640089D1B1 /* PaginationWrapperDelegate.swift */; };
67CB1C061FAB7AE00089D1B1 /* PaginationWrapperDelegate+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1C051FAB7AE00089D1B1 /* PaginationWrapperDelegate+DefaultImplementation.swift */; };
67CB1C071FAB7AE00089D1B1 /* PaginationWrapperDelegate+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1C051FAB7AE00089D1B1 /* PaginationWrapperDelegate+DefaultImplementation.swift */; };
67CB1C0A1FAB7B4D0089D1B1 /* PaginationWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CB1C091FAB7B4D0089D1B1 /* PaginationWrapper.swift */; };
67CDEE401EB369BF00895905 /* ConfigurableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462241EB3396E00EAB194 /* ConfigurableController.swift */; };
67E6C2351EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */; };
67E6C2361EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */; };
67E6C2371EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */; };
67E6C2381EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */; };
67F139F51FAB4F22008175B4 /* TotalCountCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F139F41FAB4F22008175B4 /* TotalCountCursor.swift */; };
67F139F61FAB4F22008175B4 /* TotalCountCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F139F41FAB4F22008175B4 /* TotalCountCursor.swift */; };
67F139F71FAB4F22008175B4 /* TotalCountCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F139F41FAB4F22008175B4 /* TotalCountCursor.swift */; };
67F139F81FAB4F22008175B4 /* TotalCountCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F139F41FAB4F22008175B4 /* TotalCountCursor.swift */; };
67F139FB1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F139FA1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift */; };
67F139FC1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F139FA1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift */; };
67F139FD1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F139FA1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift */; };
67F139FE1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F139FA1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift */; };
67F13A081FAB50BB008175B4 /* Single+CursorExhausted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A071FAB50BB008175B4 /* Single+CursorExhausted.swift */; };
67F13A151FAB5A87008175B4 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A141FAB5A87008175B4 /* LoadingState.swift */; };
67F13A161FAB5A87008175B4 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A141FAB5A87008175B4 /* LoadingState.swift */; };
67F13A171FAB5A87008175B4 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A141FAB5A87008175B4 /* LoadingState.swift */; };
67F13A181FAB5A87008175B4 /* LoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A141FAB5A87008175B4 /* LoadingState.swift */; };
67F13A1A1FAB5ABB008175B4 /* LoadingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A191FAB5ABB008175B4 /* LoadingProtocol.swift */; };
67F13A1B1FAB5ABB008175B4 /* LoadingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A191FAB5ABB008175B4 /* LoadingProtocol.swift */; };
67F13A1C1FAB5ABB008175B4 /* LoadingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A191FAB5ABB008175B4 /* LoadingProtocol.swift */; };
67F13A1D1FAB5ABB008175B4 /* LoadingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A191FAB5ABB008175B4 /* LoadingProtocol.swift */; };
67F13A1F1FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A1E1FAB5AE8008175B4 /* LoadingConfiguration.swift */; };
67F13A201FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A1E1FAB5AE8008175B4 /* LoadingConfiguration.swift */; };
67F13A211FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A1E1FAB5AE8008175B4 /* LoadingConfiguration.swift */; };
67F13A221FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A1E1FAB5AE8008175B4 /* LoadingConfiguration.swift */; };
67F13A241FAB5B25008175B4 /* GeneralLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A231FAB5B25008175B4 /* GeneralLoadingState.swift */; };
67F13A251FAB5B25008175B4 /* GeneralLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A231FAB5B25008175B4 /* GeneralLoadingState.swift */; };
67F13A261FAB5B25008175B4 /* GeneralLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A231FAB5B25008175B4 /* GeneralLoadingState.swift */; };
67F13A271FAB5B25008175B4 /* GeneralLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A231FAB5B25008175B4 /* GeneralLoadingState.swift */; };
67F13A2A1FAB5FE0008175B4 /* PaginationLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A291FAB5FE0008175B4 /* PaginationLoadingState.swift */; };
67F13A2B1FAB5FE0008175B4 /* PaginationLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A291FAB5FE0008175B4 /* PaginationLoadingState.swift */; };
67F13A2C1FAB5FE0008175B4 /* PaginationLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A291FAB5FE0008175B4 /* PaginationLoadingState.swift */; };
67F13A2D1FAB5FE0008175B4 /* PaginationLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A291FAB5FE0008175B4 /* PaginationLoadingState.swift */; };
67F13A2F1FAB6053008175B4 /* LoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A2E1FAB6053008175B4 /* LoadingViewModel.swift */; };
67F13A301FAB6053008175B4 /* LoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A2E1FAB6053008175B4 /* LoadingViewModel.swift */; };
67F13A311FAB6053008175B4 /* LoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A2E1FAB6053008175B4 /* LoadingViewModel.swift */; };
67F13A321FAB6053008175B4 /* LoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A2E1FAB6053008175B4 /* LoadingViewModel.swift */; };
67F13A341FAB60C0008175B4 /* GeneralLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A331FAB60C0008175B4 /* GeneralLoadingViewModel.swift */; };
67F13A351FAB60C0008175B4 /* GeneralLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A331FAB60C0008175B4 /* GeneralLoadingViewModel.swift */; };
67F13A361FAB60C0008175B4 /* GeneralLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A331FAB60C0008175B4 /* GeneralLoadingViewModel.swift */; };
67F13A371FAB60C0008175B4 /* GeneralLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A331FAB60C0008175B4 /* GeneralLoadingViewModel.swift */; };
67F13A391FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A381FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift */; };
67F13A3A1FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A381FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift */; };
67F13A3B1FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A381FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift */; };
67F13A3C1FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A381FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift */; };
67F13A3E1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A3D1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift */; };
67F13A3F1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A3D1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift */; };
67F13A401FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A3D1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift */; };
67F13A411FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A3D1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift */; };
67F13A431FAB6256008175B4 /* PaginationLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A421FAB6256008175B4 /* PaginationLoadingViewModel.swift */; };
67F13A441FAB6256008175B4 /* PaginationLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A421FAB6256008175B4 /* PaginationLoadingViewModel.swift */; };
67F13A451FAB6256008175B4 /* PaginationLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A421FAB6256008175B4 /* PaginationLoadingViewModel.swift */; };
67F13A461FAB6256008175B4 /* PaginationLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F13A421FAB6256008175B4 /* PaginationLoadingViewModel.swift */; };
67FDC25F1FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; };
67FDC2601FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; };
67FDC2611FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; };
@ -349,9 +423,9 @@
A6C9A5111F8BC79D009311CC /* Comparable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D10EAA1F8A9278003E69DD /* Comparable+Extensions.swift */; };
A6D10EAB1F8A9278003E69DD /* Comparable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D10EAA1F8A9278003E69DD /* Comparable+Extensions.swift */; };
A6E0DDDE1F8A696F002CA74E /* EmptyCellRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A66428A71F8A654600C6308D /* EmptyCellRow.swift */; };
A6E0DDDF1F8A696F002CA74E /* SeparatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A66428A61F8A653600C6308D /* SeparatorCell.swift */; };
A6E0DDDF1F8A696F002CA74E /* SeparatorTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A66428A61F8A653600C6308D /* SeparatorTableCell.swift */; };
A6E0DDE11F8A696F002CA74E /* SeparatorRowBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = A66428A81F8A655600C6308D /* SeparatorRowBox.swift */; };
A6E0DDE31F8A696F002CA74E /* SeparatorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A66428A61F8A653600C6308D /* SeparatorCell.swift */; };
A6E0DDE31F8A696F002CA74E /* SeparatorTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A66428A61F8A653600C6308D /* SeparatorTableCell.swift */; };
A6E0DDE51F8A696F002CA74E /* SeparatorRowBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = A66428A81F8A655600C6308D /* SeparatorRowBox.swift */; };
A6E0DDEF1F8A6C57002CA74E /* CellSeparatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E0DDEE1F8A6C57002CA74E /* CellSeparatorType.swift */; };
A6E0DDF11F8A6C80002CA74E /* SeparatorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E0DDF01F8A6C80002CA74E /* SeparatorConfiguration.swift */; };
@ -424,8 +498,6 @@
671461CC1EB3396E00EAB194 /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
671461CD1EB3396E00EAB194 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
671461CE1EB3396E00EAB194 /* LogFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogFormatter.swift; sourceTree = "<group>"; };
671461D01EB3396E00EAB194 /* PaginationTableViewWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationTableViewWrapper.swift; sourceTree = "<group>"; };
671461D11EB3396E00EAB194 /* PaginationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationViewModel.swift; sourceTree = "<group>"; };
671461D31EB3396E00EAB194 /* NetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
671461D51EB3396E00EAB194 /* XibView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibView.swift; sourceTree = "<group>"; };
671461D71EB3396E00EAB194 /* CursorError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorError.swift; sourceTree = "<group>"; };
@ -442,7 +514,6 @@
671461E91EB3396E00EAB194 /* CursorType+Slice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CursorType+Slice.swift"; sourceTree = "<group>"; };
671461EB1EB3396E00EAB194 /* Double+Rounding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Rounding.swift"; sourceTree = "<group>"; };
671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+DeferredJust.swift"; sourceTree = "<group>"; };
671461F41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PaginationTableViewWrapperDelegate+DefaultImplementation.swift"; sourceTree = "<group>"; };
671461F61EB3396E00EAB194 /* Sequence+ConcurrentMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sequence+ConcurrentMap.swift"; sourceTree = "<group>"; };
671461FC1EB3396E00EAB194 /* String+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = "<group>"; };
671461FD1EB3396E00EAB194 /* String+SizeCalculation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SizeCalculation.swift"; sourceTree = "<group>"; };
@ -498,6 +569,9 @@
67186B301EB248F100CFAFFB /* LeadKit iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LeadKit iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
67186B411EB24AA000CFAFFB /* iOS.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = iOS.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
67186C1A1EB24B7800CFAFFB /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = "<group>"; };
672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotateDrawingOperation.swift; sourceTree = "<group>"; };
672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FloatingPoint+DegreesRadiansConvertion.swift"; sourceTree = "<group>"; };
6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSourceProtocol.swift; sourceTree = "<group>"; };
674AF55B1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+LoadingIndicator.swift"; sourceTree = "<group>"; };
6771DFD71EE99EBA002DCDAE /* DateFormattingService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFormattingService.swift; sourceTree = "<group>"; };
6771DFDD1EE99F6F002DCDAE /* DateFormattingArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFormattingArguments.swift; sourceTree = "<group>"; };
@ -507,6 +581,9 @@
6782BBA01EB31D590086E0B8 /* LeadKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LeadKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6782BBA81EB31D5A0086E0B8 /* LeadKit tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LeadKit tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
6782BBB91EB31DD90086E0B8 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
678B43EF1FBA3D5500D1F77D /* SeparatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorCell.swift; sourceTree = "<group>"; };
678B43F61FBA3DB100D1F77D /* SeparatorCell+DefaultConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SeparatorCell+DefaultConfiguration.swift"; sourceTree = "<group>"; };
678B43FB1FBA3DF300D1F77D /* SeparatorCell+UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SeparatorCell+UITableViewCell.swift"; sourceTree = "<group>"; };
67952C391EB3203F00B3BA1A /* Info-iOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = "<group>"; };
67952C3A1EB3205D00B3BA1A /* Info-watchOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-watchOS.plist"; sourceTree = "<group>"; };
67952C3B1EB3208000B3BA1A /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = "<group>"; };
@ -518,7 +595,27 @@
679C77D61F98F7A60094BE10 /* UIAlertController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Extensions.swift"; sourceTree = "<group>"; };
67A1FF8E1EBCA09B00D6C89F /* UIImage+Spinner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Spinner.swift"; sourceTree = "<group>"; };
67A1FF931EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CABasicAnimation+Rotation.swift"; sourceTree = "<group>"; };
67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLoadCursor.swift; sourceTree = "<group>"; };
67B5A34B1FD8074800F36C36 /* Rx+DataSourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Rx+DataSourceProtocol.swift"; sourceTree = "<group>"; };
67CB1BF11FAB78770089D1B1 /* PaginationWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationWrappable.swift; sourceTree = "<group>"; };
67CB1BF51FAB78DC0089D1B1 /* AnyPaginationWrappableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyPaginationWrappableView.swift; sourceTree = "<group>"; };
67CB1BF91FAB79530089D1B1 /* UITableView+PaginationWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+PaginationWrappable.swift"; sourceTree = "<group>"; };
67CB1BFD1FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+PaginationWrappable.swift"; sourceTree = "<group>"; };
67CB1C011FAB7A640089D1B1 /* PaginationWrapperDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationWrapperDelegate.swift; sourceTree = "<group>"; };
67CB1C051FAB7AE00089D1B1 /* PaginationWrapperDelegate+DefaultImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaginationWrapperDelegate+DefaultImplementation.swift"; sourceTree = "<group>"; };
67CB1C091FAB7B4D0089D1B1 /* PaginationWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationWrapper.swift; sourceTree = "<group>"; };
67F139F41FAB4F22008175B4 /* TotalCountCursor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalCountCursor.swift; sourceTree = "<group>"; };
67F139FA1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalCountCursorConfiguration.swift; sourceTree = "<group>"; };
67F13A071FAB50BB008175B4 /* Single+CursorExhausted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Single+CursorExhausted.swift"; sourceTree = "<group>"; };
67F13A141FAB5A87008175B4 /* LoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingState.swift; sourceTree = "<group>"; };
67F13A191FAB5ABB008175B4 /* LoadingProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingProtocol.swift; sourceTree = "<group>"; };
67F13A1E1FAB5AE8008175B4 /* LoadingConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingConfiguration.swift; sourceTree = "<group>"; };
67F13A231FAB5B25008175B4 /* GeneralLoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralLoadingState.swift; sourceTree = "<group>"; };
67F13A291FAB5FE0008175B4 /* PaginationLoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationLoadingState.swift; sourceTree = "<group>"; };
67F13A2E1FAB6053008175B4 /* LoadingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingViewModel.swift; sourceTree = "<group>"; };
67F13A331FAB60C0008175B4 /* GeneralLoadingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralLoadingViewModel.swift; sourceTree = "<group>"; };
67F13A381FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralLoadingViewModelConfiguration.swift; sourceTree = "<group>"; };
67F13A3D1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationLoadingViewModelConfiguration.swift; sourceTree = "<group>"; };
67F13A421FAB6256008175B4 /* PaginationLoadingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationLoadingViewModel.swift; sourceTree = "<group>"; };
67FDC25E1FA310EA00C76A77 /* RequestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestError.swift; sourceTree = "<group>"; };
78405D3B3D3C3E17456877FF /* Pods_LeadKit_iOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit_iOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
82F8BB171F5DDED100C1061B /* Single+DeferredJust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Single+DeferredJust.swift"; sourceTree = "<group>"; };
@ -528,7 +625,7 @@
9966FB938D114F79F71AE037 /* Pods-LeadKit-LeadKit iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeadKit-LeadKit iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-LeadKit-LeadKit iOS/Pods-LeadKit-LeadKit iOS.release.xcconfig"; sourceTree = "<group>"; };
A658E54C1F8CD7790093527A /* TableRow+SeparatorsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TableRow+SeparatorsExtensions.swift"; sourceTree = "<group>"; };
A658E54F1F8CD9350093527A /* Array+SeparatorRowBoxExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+SeparatorRowBoxExtensions.swift"; sourceTree = "<group>"; };
A66428A61F8A653600C6308D /* SeparatorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorCell.swift; sourceTree = "<group>"; };
A66428A61F8A653600C6308D /* SeparatorTableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorTableCell.swift; sourceTree = "<group>"; };
A66428A71F8A654600C6308D /* EmptyCellRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyCellRow.swift; sourceTree = "<group>"; };
A66428A81F8A655600C6308D /* SeparatorRowBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRowBox.swift; sourceTree = "<group>"; };
A676AE471F97D28A001F9214 /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
@ -626,6 +723,7 @@
671461C41EB3396E00EAB194 /* Classes */ = {
isa = PBXGroup;
children = (
67F13A0C1FAB5266008175B4 /* GeneralLoading */,
671461C71EB3396E00EAB194 /* Cursors */,
671461CB1EB3396E00EAB194 /* Logging */,
671461CF1EB3396E00EAB194 /* Pagination */,
@ -640,8 +738,8 @@
children = (
671461C81EB3396E00EAB194 /* FixedPageCursor.swift */,
671461C91EB3396E00EAB194 /* MapCursor.swift */,
67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */,
671461CA1EB3396E00EAB194 /* StaticCursor.swift */,
67F139F41FAB4F22008175B4 /* TotalCountCursor.swift */,
);
path = Cursors;
sourceTree = "<group>";
@ -659,8 +757,7 @@
671461CF1EB3396E00EAB194 /* Pagination */ = {
isa = PBXGroup;
children = (
671461D01EB3396E00EAB194 /* PaginationTableViewWrapper.swift */,
671461D11EB3396E00EAB194 /* PaginationViewModel.swift */,
67CB1C091FAB7B4D0089D1B1 /* PaginationWrapper.swift */,
);
path = Pagination;
sourceTree = "<group>";
@ -678,7 +775,7 @@
isa = PBXGroup;
children = (
A6AF3B381F8B957400CDB971 /* SeparatorRowBox */,
A66428A41F8A651700C6308D /* SeparatorCell */,
A66428A41F8A651700C6308D /* SeparatorCellTableCell */,
A6C9A4F71F8BBC0F009311CC /* EmptyCell */,
A6C9A4F81F8BBC27009311CC /* SpinnerView */,
A6C9A4F91F8BBCCE009311CC /* XibView */,
@ -700,6 +797,10 @@
671461DA1EB3396E00EAB194 /* Extensions */ = {
isa = PBXGroup;
children = (
672CC2B11FEA727D00EBFB0A /* FloatingPoint */,
678B43F41FBA3D7C00D1F77D /* Views */,
67CB1BF81FAB793F0089D1B1 /* Pagination */,
67F139FF1FAB4FCC008175B4 /* Rx */,
671461DB1EB3396E00EAB194 /* Alamofire */,
EFBE57CE1EC35ED90040E00A /* Array */,
67A1FF921EBCA64A00D6C89F /* CABasicAnimation */,
@ -713,10 +814,7 @@
6771DFE81EEA7C8F002DCDAE /* DateFormattingService */,
671461EA1EB3396E00EAB194 /* Double */,
6714639C1EB33AC200EAB194 /* NetworkService */,
671461F01EB3396E00EAB194 /* Observable */,
671461F31EB3396E00EAB194 /* PaginationTableViewWrapperDelegate */,
671461F51EB3396E00EAB194 /* Sequence */,
82F8BB161F5DDED100C1061B /* Single */,
671461FA1EB3396E00EAB194 /* String */,
671461FE1EB3396E00EAB194 /* Support */,
671462001EB3396E00EAB194 /* TableDirector */,
@ -800,14 +898,6 @@
path = Observable;
sourceTree = "<group>";
};
671461F31EB3396E00EAB194 /* PaginationTableViewWrapperDelegate */ = {
isa = PBXGroup;
children = (
671461F41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift */,
);
path = PaginationTableViewWrapperDelegate;
sourceTree = "<group>";
};
671461F51EB3396E00EAB194 /* Sequence */ = {
isa = PBXGroup;
children = (
@ -913,15 +1003,15 @@
671462221EB3396E00EAB194 /* Protocols */ = {
isa = PBXGroup;
children = (
67CB1BEE1FAB78280089D1B1 /* Views */,
67F13A0D1FAB5A5C008175B4 /* Loading */,
67F139F91FAB4F66008175B4 /* Cursors */,
671462331EB3396E00EAB194 /* ConfigurableView.swift */,
679C77D41F98F78E0094BE10 /* AlertRepresentable.swift */,
671463A11EB33FF600EAB194 /* Animatable.swift */,
40F118461F8FEF97004AADAF /* AppearanceConfigurable.swift */,
671462231EB3396E00EAB194 /* BaseViewModel.swift */,
671462241EB3396E00EAB194 /* ConfigurableController.swift */,
671462251EB3396E00EAB194 /* CursorType.swift */,
671462261EB3396E00EAB194 /* DrawingOperation.swift */,
671462281EB3396E00EAB194 /* LoadingIndicator.swift */,
671462291EB3396E00EAB194 /* ModuleConfigurator.swift */,
A676AE541F981121001F9214 /* ObservableMappable.swift */,
6714622B1EB3396E00EAB194 /* ResettableType.swift */,
@ -961,6 +1051,7 @@
6714623B1EB3396E00EAB194 /* ImageDrawingOperation.swift */,
6714623C1EB3396E00EAB194 /* PaddingDrawingOperation.swift */,
6714623D1EB3396E00EAB194 /* ResizeDrawingOperation.swift */,
672CC2A71FEA6A6A00EBFB0A /* RotateDrawingOperation.swift */,
6714623E1EB3396E00EAB194 /* RoundDrawingOperation.swift */,
6714623F1EB3396E00EAB194 /* SolidFillDrawingOperation.swift */,
671462401EB3396E00EAB194 /* TemplateDrawingOperation.swift */,
@ -971,7 +1062,8 @@
671462421EB3396E00EAB194 /* Views */ = {
isa = PBXGroup;
children = (
671462431EB3396E00EAB194 /* AnyLoadingIndicator.swift */,
67CB1BF41FAB78B90089D1B1 /* Pagination */,
67CB1BF31FAB78B10089D1B1 /* LoadingIndicator */,
);
path = Views;
sourceTree = "<group>";
@ -1044,6 +1136,14 @@
path = Tests;
sourceTree = "<group>";
};
672CC2B11FEA727D00EBFB0A /* FloatingPoint */ = {
isa = PBXGroup;
children = (
672CC2B21FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift */,
);
path = FloatingPoint;
sourceTree = "<group>";
};
674AF55A1EC45B1600038A8F /* UIActivityIndicatorView */ = {
isa = PBXGroup;
children = (
@ -1076,6 +1176,31 @@
path = DateFormattingService;
sourceTree = "<group>";
};
678B43EE1FBA3D3A00D1F77D /* SeparatorCell */ = {
isa = PBXGroup;
children = (
678B43EF1FBA3D5500D1F77D /* SeparatorCell.swift */,
);
path = SeparatorCell;
sourceTree = "<group>";
};
678B43F41FBA3D7C00D1F77D /* Views */ = {
isa = PBXGroup;
children = (
678B43F51FBA3D8200D1F77D /* SeparatorCell */,
);
path = Views;
sourceTree = "<group>";
};
678B43F51FBA3D8200D1F77D /* SeparatorCell */ = {
isa = PBXGroup;
children = (
678B43F61FBA3DB100D1F77D /* SeparatorCell+DefaultConfiguration.swift */,
678B43FB1FBA3DF300D1F77D /* SeparatorCell+UITableViewCell.swift */,
);
path = SeparatorCell;
sourceTree = "<group>";
};
679C77D51F98F7A60094BE10 /* UIAlertController */ = {
isa = PBXGroup;
children = (
@ -1092,6 +1217,136 @@
path = CABasicAnimation;
sourceTree = "<group>";
};
67CB1BEE1FAB78280089D1B1 /* Views */ = {
isa = PBXGroup;
children = (
678B43EE1FBA3D3A00D1F77D /* SeparatorCell */,
67CB1BF01FAB784E0089D1B1 /* Pagination */,
67CB1BEF1FAB78460089D1B1 /* LoadingIndicator */,
);
path = Views;
sourceTree = "<group>";
};
67CB1BEF1FAB78460089D1B1 /* LoadingIndicator */ = {
isa = PBXGroup;
children = (
671462281EB3396E00EAB194 /* LoadingIndicator.swift */,
671463A11EB33FF600EAB194 /* Animatable.swift */,
);
path = LoadingIndicator;
sourceTree = "<group>";
};
67CB1BF01FAB784E0089D1B1 /* Pagination */ = {
isa = PBXGroup;
children = (
67CB1BF11FAB78770089D1B1 /* PaginationWrappable.swift */,
67CB1C011FAB7A640089D1B1 /* PaginationWrapperDelegate.swift */,
);
path = Pagination;
sourceTree = "<group>";
};
67CB1BF31FAB78B10089D1B1 /* LoadingIndicator */ = {
isa = PBXGroup;
children = (
671462431EB3396E00EAB194 /* AnyLoadingIndicator.swift */,
);
path = LoadingIndicator;
sourceTree = "<group>";
};
67CB1BF41FAB78B90089D1B1 /* Pagination */ = {
isa = PBXGroup;
children = (
67CB1BF51FAB78DC0089D1B1 /* AnyPaginationWrappableView.swift */,
);
path = Pagination;
sourceTree = "<group>";
};
67CB1BF81FAB793F0089D1B1 /* Pagination */ = {
isa = PBXGroup;
children = (
67CB1BF91FAB79530089D1B1 /* UITableView+PaginationWrappable.swift */,
67CB1BFD1FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift */,
67CB1C051FAB7AE00089D1B1 /* PaginationWrapperDelegate+DefaultImplementation.swift */,
);
path = Pagination;
sourceTree = "<group>";
};
67F139F91FAB4F66008175B4 /* Cursors */ = {
isa = PBXGroup;
children = (
671462251EB3396E00EAB194 /* CursorType.swift */,
67F139FA1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift */,
);
path = Cursors;
sourceTree = "<group>";
};
67F139FF1FAB4FCC008175B4 /* Rx */ = {
isa = PBXGroup;
children = (
67F13A061FAB5098008175B4 /* PrimitiveSequence */,
67F13A001FAB5011008175B4 /* SharedSequence */,
671461F01EB3396E00EAB194 /* Observable */,
67B5A34B1FD8074800F36C36 /* Rx+DataSourceProtocol.swift */,
);
path = Rx;
sourceTree = "<group>";
};
67F13A001FAB5011008175B4 /* SharedSequence */ = {
isa = PBXGroup;
children = (
);
path = SharedSequence;
sourceTree = "<group>";
};
67F13A061FAB5098008175B4 /* PrimitiveSequence */ = {
isa = PBXGroup;
children = (
82F8BB161F5DDED100C1061B /* Single */,
);
path = PrimitiveSequence;
sourceTree = "<group>";
};
67F13A0C1FAB5266008175B4 /* GeneralLoading */ = {
isa = PBXGroup;
children = (
);
path = GeneralLoading;
sourceTree = "<group>";
};
67F13A0D1FAB5A5C008175B4 /* Loading */ = {
isa = PBXGroup;
children = (
67F13A281FAB5FD3008175B4 /* PaginationLoading */,
67F13A0E1FAB5A64008175B4 /* GeneralLoading */,
67F13A141FAB5A87008175B4 /* LoadingState.swift */,
67F13A191FAB5ABB008175B4 /* LoadingProtocol.swift */,
67F13A1E1FAB5AE8008175B4 /* LoadingConfiguration.swift */,
67F13A2E1FAB6053008175B4 /* LoadingViewModel.swift */,
6740D5D11FABDA46006BB7C0 /* DataSourceProtocol.swift */,
);
path = Loading;
sourceTree = "<group>";
};
67F13A0E1FAB5A64008175B4 /* GeneralLoading */ = {
isa = PBXGroup;
children = (
67F13A231FAB5B25008175B4 /* GeneralLoadingState.swift */,
67F13A331FAB60C0008175B4 /* GeneralLoadingViewModel.swift */,
67F13A381FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift */,
);
path = GeneralLoading;
sourceTree = "<group>";
};
67F13A281FAB5FD3008175B4 /* PaginationLoading */ = {
isa = PBXGroup;
children = (
67F13A291FAB5FE0008175B4 /* PaginationLoadingState.swift */,
67F13A3D1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift */,
67F13A421FAB6256008175B4 /* PaginationLoadingViewModel.swift */,
);
path = PaginationLoading;
sourceTree = "<group>";
};
78CFEE201C5C456B00F50370 = {
isa = PBXGroup;
children = (
@ -1121,19 +1376,20 @@
82F8BB161F5DDED100C1061B /* Single */ = {
isa = PBXGroup;
children = (
67F13A071FAB50BB008175B4 /* Single+CursorExhausted.swift */,
82F8BB171F5DDED100C1061B /* Single+DeferredJust.swift */,
);
path = Single;
sourceTree = "<group>";
};
A66428A41F8A651700C6308D /* SeparatorCell */ = {
A66428A41F8A651700C6308D /* SeparatorCellTableCell */ = {
isa = PBXGroup;
children = (
A66428A61F8A653600C6308D /* SeparatorCell.swift */,
A66428A61F8A653600C6308D /* SeparatorTableCell.swift */,
A6E0DDEE1F8A6C57002CA74E /* CellSeparatorType.swift */,
A6E0DDF01F8A6C80002CA74E /* SeparatorConfiguration.swift */,
);
path = SeparatorCell;
path = SeparatorCellTableCell;
sourceTree = "<group>";
};
A6AF3B381F8B957400CDB971 /* SeparatorRowBox */ = {
@ -1946,25 +2202,33 @@
buildActionMask = 2147483647;
files = (
671463481EB3396E00EAB194 /* ResettableType.swift in Sources */,
67F139F51FAB4F22008175B4 /* TotalCountCursor.swift in Sources */,
EFBE57D01EC35EF20040E00A /* Array+Extensions.swift in Sources */,
671462E41EB3396E00EAB194 /* UIColor+Hex.swift in Sources */,
671462CC1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */,
671462801EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */,
67CB1BF21FAB78770089D1B1 /* PaginationWrappable.swift in Sources */,
671463541EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */,
671463601EB3396E00EAB194 /* SupportProtocol.swift in Sources */,
67F13A3E1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift in Sources */,
671462841EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */,
EFBE57DB1EC361620040E00A /* UIView+Layout.swift in Sources */,
6714639E1EB33AEB00EAB194 /* NetworkService+ActivityIndicator.swift in Sources */,
A6E0DDEF1F8A6C57002CA74E /* CellSeparatorType.swift in Sources */,
6714634C1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */,
672CC2A81FEA6A6A00EBFB0A /* RotateDrawingOperation.swift in Sources */,
671462F01EB3396E00EAB194 /* UIImage+SupportExtensions.swift in Sources */,
6771DFDE1EE99F6F002DCDAE /* DateFormattingArguments.swift in Sources */,
671462681EB3396E00EAB194 /* NetworkService.swift in Sources */,
671463101EB3396E00EAB194 /* UIViewController+DefaultXibName.swift in Sources */,
67CB1BF61FAB78DC0089D1B1 /* AnyPaginationWrappableView.swift in Sources */,
67F13A2F1FAB6053008175B4 /* LoadingViewModel.swift in Sources */,
674AF55C1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */,
671463401EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */,
671462641EB3396E00EAB194 /* PaginationViewModel.swift in Sources */,
67F13A431FAB6256008175B4 /* PaginationLoadingViewModel.swift in Sources */,
67A1FF8F1EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */,
67CB1BFE1FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift in Sources */,
672CC2B31FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */,
671462901EB3396E00EAB194 /* CGImage+Crop.swift in Sources */,
671462FC1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */,
36DAAF512007CC920090BE0D /* UITableView+Extensions.swift in Sources */,
@ -1981,40 +2245,52 @@
67FDC25F1FA310EA00C76A77 /* RequestError.swift in Sources */,
6714624C1EB3396E00EAB194 /* MapCursor.swift in Sources */,
A6C9A4FA1F8BBCF2009311CC /* EmptyCell.swift in Sources */,
67CB1C061FAB7AE00089D1B1 /* PaginationWrapperDelegate+DefaultImplementation.swift in Sources */,
671463241EB3396E00EAB194 /* Any+TypeName.swift in Sources */,
671463881EB3396E00EAB194 /* RoundDrawingOperation.swift in Sources */,
A6D10EAB1F8A9278003E69DD /* Comparable+Extensions.swift in Sources */,
678B43F71FBA3DB100D1F77D /* SeparatorCell+DefaultConfiguration.swift in Sources */,
67F139FB1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift in Sources */,
67F13A2A1FAB5FE0008175B4 /* PaginationLoadingState.swift in Sources */,
671463801EB3396E00EAB194 /* PaddingDrawingOperation.swift in Sources */,
671462601EB3396E00EAB194 /* PaginationTableViewWrapper.swift in Sources */,
671463281EB3396E00EAB194 /* BaseViewModel.swift in Sources */,
A6E0DDDF1F8A696F002CA74E /* SeparatorCell.swift in Sources */,
A6E0DDDF1F8A696F002CA74E /* SeparatorTableCell.swift in Sources */,
671462AC1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */,
671463001EB3396E00EAB194 /* UIView+LoadFromNib.swift in Sources */,
A676AE501F9810C1001F9214 /* Any+Cast.swift in Sources */,
6714627C1EB3396E00EAB194 /* AlamofireManager+Extensions.swift in Sources */,
671462D41EB3396E00EAB194 /* TableDirector+Extensions.swift in Sources */,
671462581EB3396E00EAB194 /* Log.swift in Sources */,
671462B41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift in Sources */,
67F13A1A1FAB5ABB008175B4 /* LoadingProtocol.swift in Sources */,
67F13A391FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift in Sources */,
671462781EB3396E00EAB194 /* ResizeMode.swift in Sources */,
A676AE551F98112E001F9214 /* ObservableMappable.swift in Sources */,
A6E0DDE11F8A696F002CA74E /* SeparatorRowBox.swift in Sources */,
A6E0DDDE1F8A696F002CA74E /* EmptyCellRow.swift in Sources */,
67F13A341FAB60C0008175B4 /* GeneralLoadingViewModel.swift in Sources */,
671463041EB3396E00EAB194 /* UIView+LoadingIndicator.swift in Sources */,
67F13A1F1FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */,
40F118491F8FF223004AADAF /* TableRow+AppearanceExtension.swift in Sources */,
671463701EB3396E00EAB194 /* ApiRequestParameters.swift in Sources */,
A658E5501F8CD9350093527A /* Array+SeparatorRowBoxExtensions.swift in Sources */,
678B43FC1FBA3DF300D1F77D /* SeparatorCell+UITableViewCell.swift in Sources */,
671462EC1EB3396E00EAB194 /* UIImage+Extensions.swift in Sources */,
67F13A241FAB5B25008175B4 /* GeneralLoadingState.swift in Sources */,
A6E0DDF11F8A6C80002CA74E /* SeparatorConfiguration.swift in Sources */,
6714636C1EB3396E00EAB194 /* XibNameProtocol.swift in Sources */,
6771DFEA1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */,
671462A01EB3396E00EAB194 /* Double+Rounding.swift in Sources */,
6714625C1EB3396E00EAB194 /* LogFormatter.swift in Sources */,
67F13A081FAB50BB008175B4 /* Single+CursorExhausted.swift in Sources */,
6740D5D21FABDA46006BB7C0 /* DataSourceProtocol.swift in Sources */,
671463081EB3396E00EAB194 /* UIView+Rotation.swift in Sources */,
6714626C1EB3396E00EAB194 /* XibView.swift in Sources */,
6714637C1EB3396E00EAB194 /* ImageDrawingOperation.swift in Sources */,
671463341EB3396E00EAB194 /* DrawingOperation.swift in Sources */,
671462701EB3396E00EAB194 /* CursorError.swift in Sources */,
671463981EB3396E00EAB194 /* AnyLoadingIndicator.swift in Sources */,
67F13A151FAB5A87008175B4 /* LoadingState.swift in Sources */,
67CB1BFA1FAB79530089D1B1 /* UITableView+PaginationWrappable.swift in Sources */,
671463A71EB340C000EAB194 /* UIViewController+ConfigurableController.swift in Sources */,
671463141EB3396E00EAB194 /* UIViewController+TopVisibleViewController.swift in Sources */,
A6F32C081F6EBDAA00AC08EE /* String+LocalizedComponent.swift in Sources */,
@ -2023,11 +2299,12 @@
671463641EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */,
671462481EB3396E00EAB194 /* FixedPageCursor.swift in Sources */,
671462C81EB3396E00EAB194 /* String+Localization.swift in Sources */,
67CB1C0A1FAB7B4D0089D1B1 /* PaginationWrapper.swift in Sources */,
678B43F01FBA3D5500D1F77D /* SeparatorCell.swift in Sources */,
671462B81EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */,
671463741EB3396E00EAB194 /* BorderDrawingOperation.swift in Sources */,
A676AE4B1F97D28A001F9214 /* String+Extensions.swift in Sources */,
6714633C1EB3396E00EAB194 /* LoadingIndicator.swift in Sources */,
67E6C2351EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */,
40F118471F8FEF97004AADAF /* AppearanceConfigurable.swift in Sources */,
671463181EB3396E00EAB194 /* UIWindow+Extensions.swift in Sources */,
671462541EB3396E00EAB194 /* App.swift in Sources */,
@ -2041,6 +2318,8 @@
671462501EB3396E00EAB194 /* StaticCursor.swift in Sources */,
6714629C1EB3396E00EAB194 /* CursorType+Slice.swift in Sources */,
671463681EB3396E00EAB194 /* ConfigurableView.swift in Sources */,
67CB1C021FAB7A640089D1B1 /* PaginationWrapperDelegate.swift in Sources */,
67B5A34C1FD8074800F36C36 /* Rx+DataSourceProtocol.swift in Sources */,
6771DFE41EE9A00A002DCDAE /* DateFormattingArguments+DateFormatter.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -2058,7 +2337,7 @@
671463BB1EB34B1E00EAB194 /* CursorTests.swift in Sources */,
A6F32C101F6EBE9600AC08EE /* StringExtensionTests.swift in Sources */,
671463BE1EB34B1E00EAB194 /* LoadFromNibTests.swift in Sources */,
A6E0DDE31F8A696F002CA74E /* SeparatorCell.swift in Sources */,
A6E0DDE31F8A696F002CA74E /* SeparatorTableCell.swift in Sources */,
A6C9A50F1F8BC79D009311CC /* Comparable+Extensions.swift in Sources */,
671463C41EB34B1E00EAB194 /* Post.swift in Sources */,
A6E0DDE51F8A696F002CA74E /* SeparatorRowBox.swift in Sources */,
@ -2070,22 +2349,28 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
67F13A1C1FAB5ABB008175B4 /* LoadingProtocol.swift in Sources */,
6714634A1EB3396E00EAB194 /* ResettableType.swift in Sources */,
671462E61EB3396E00EAB194 /* UIColor+Hex.swift in Sources */,
671462CE1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */,
A676AE4D1F9810C1001F9214 /* Any+Cast.swift in Sources */,
67F13A3B1FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift in Sources */,
EFBE57D21EC35EF20040E00A /* Array+Extensions.swift in Sources */,
672CC2A91FEA6A7400EBFB0A /* RotateDrawingOperation.swift in Sources */,
67F139F71FAB4F22008175B4 /* TotalCountCursor.swift in Sources */,
671462821EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */,
67E6C2371EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */,
671463561EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */,
6771DFE01EE99F6F002DCDAE /* DateFormattingArguments.swift in Sources */,
671463621EB3396E00EAB194 /* SupportProtocol.swift in Sources */,
67F13A361FAB60C0008175B4 /* GeneralLoadingViewModel.swift in Sources */,
6771DFEC1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */,
67F13A211FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */,
671462861EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */,
6714634E1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */,
6714626A1EB3396E00EAB194 /* NetworkService.swift in Sources */,
671463421EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */,
671462661EB3396E00EAB194 /* PaginationViewModel.swift in Sources */,
672CC2B51FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */,
67F13A171FAB5A87008175B4 /* LoadingState.swift in Sources */,
671462921EB3396E00EAB194 /* CGImage+Crop.swift in Sources */,
671463861EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */,
671463921EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */,
@ -2094,10 +2379,12 @@
6714624E1EB3396E00EAB194 /* MapCursor.swift in Sources */,
A676AE571F981130001F9214 /* ObservableMappable.swift in Sources */,
671463261EB3396E00EAB194 /* Any+TypeName.swift in Sources */,
67F13A451FAB6256008175B4 /* PaginationLoadingViewModel.swift in Sources */,
6714638A1EB3396E00EAB194 /* RoundDrawingOperation.swift in Sources */,
671463821EB3396E00EAB194 /* PaddingDrawingOperation.swift in Sources */,
6714632A1EB3396E00EAB194 /* BaseViewModel.swift in Sources */,
671462AE1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */,
67F13A2C1FAB5FE0008175B4 /* PaginationLoadingState.swift in Sources */,
A6F32C0B1F6EBE5C00AC08EE /* String+LocalizedComponent.swift in Sources */,
6714627E1EB3396E00EAB194 /* AlamofireManager+Extensions.swift in Sources */,
6771DFDA1EE99EBA002DCDAE /* DateFormattingService.swift in Sources */,
@ -2108,12 +2395,17 @@
6714636E1EB3396E00EAB194 /* XibNameProtocol.swift in Sources */,
67CDEE401EB369BF00895905 /* ConfigurableController.swift in Sources */,
A676AE481F97D28A001F9214 /* String+Extensions.swift in Sources */,
67F13A311FAB6053008175B4 /* LoadingViewModel.swift in Sources */,
671462A21EB3396E00EAB194 /* Double+Rounding.swift in Sources */,
6740D5D41FABDA46006BB7C0 /* DataSourceProtocol.swift in Sources */,
6714625E1EB3396E00EAB194 /* LogFormatter.swift in Sources */,
67F13A401FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift in Sources */,
6714637E1EB3396E00EAB194 /* ImageDrawingOperation.swift in Sources */,
67F13A261FAB5B25008175B4 /* GeneralLoadingState.swift in Sources */,
671463361EB3396E00EAB194 /* DrawingOperation.swift in Sources */,
671462721EB3396E00EAB194 /* CursorError.swift in Sources */,
671462961EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */,
67F139FD1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift in Sources */,
671463661EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */,
6771DFE61EE9A00A002DCDAE /* DateFormattingArguments+DateFormatter.swift in Sources */,
6714624A1EB3396E00EAB194 /* FixedPageCursor.swift in Sources */,
@ -2121,6 +2413,7 @@
671462BA1EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */,
671463761EB3396E00EAB194 /* BorderDrawingOperation.swift in Sources */,
671462561EB3396E00EAB194 /* App.swift in Sources */,
674CC1DF1FAB8D1C007B0EAE /* Single+CursorExhausted.swift in Sources */,
675C1FB51F97CA33007D5249 /* AppearanceConfigurable.swift in Sources */,
6714628E1EB3396E00EAB194 /* CGImage+Alpha.swift in Sources */,
671462761EB3396E00EAB194 /* LeadKitError.swift in Sources */,
@ -2142,19 +2435,25 @@
671462CF1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */,
671462831EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */,
671463571EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */,
67F13A371FAB60C0008175B4 /* GeneralLoadingViewModel.swift in Sources */,
672CC2B61FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */,
67F139FE1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift in Sources */,
671463631EB3396E00EAB194 /* SupportProtocol.swift in Sources */,
671462871EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */,
6714634F1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */,
67F13A2D1FAB5FE0008175B4 /* PaginationLoadingState.swift in Sources */,
671462F31EB3396E00EAB194 /* UIImage+SupportExtensions.swift in Sources */,
67F13A321FAB6053008175B4 /* LoadingViewModel.swift in Sources */,
6771DFE71EE9A00A002DCDAE /* DateFormattingArguments+DateFormatter.swift in Sources */,
6714626B1EB3396E00EAB194 /* NetworkService.swift in Sources */,
671463131EB3396E00EAB194 /* UIViewController+DefaultXibName.swift in Sources */,
671463431EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */,
671462671EB3396E00EAB194 /* PaginationViewModel.swift in Sources */,
671462931EB3396E00EAB194 /* CGImage+Crop.swift in Sources */,
671462FF1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */,
67FDC2621FA310EA00C76A77 /* RequestError.swift in Sources */,
671463871EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */,
674CC1DD1FAB8D0A007B0EAE /* PaginationWrappable.swift in Sources */,
67F13A461FAB6256008175B4 /* PaginationLoadingViewModel.swift in Sources */,
671463931EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */,
674AF55E1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */,
671463A51EB33FF600EAB194 /* Animatable.swift in Sources */,
@ -2175,11 +2474,16 @@
6771DFDB1EE99EBA002DCDAE /* DateFormattingService.swift in Sources */,
6714625B1EB3396E00EAB194 /* Log.swift in Sources */,
6714627B1EB3396E00EAB194 /* ResizeMode.swift in Sources */,
67CB1C001FAB79EE0089D1B1 /* UICollectionView+PaginationWrappable.swift in Sources */,
671463071EB3396E00EAB194 /* UIView+LoadingIndicator.swift in Sources */,
67F13A411FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift in Sources */,
67F13A1D1FAB5ABB008175B4 /* LoadingProtocol.swift in Sources */,
6771DFED1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */,
671463A91EB340C000EAB194 /* UIViewController+ConfigurableController.swift in Sources */,
67CB1BFC1FAB79530089D1B1 /* UITableView+PaginationWrappable.swift in Sources */,
A676AE581F981131001F9214 /* ObservableMappable.swift in Sources */,
671463731EB3396E00EAB194 /* ApiRequestParameters.swift in Sources */,
67F139F81FAB4F22008175B4 /* TotalCountCursor.swift in Sources */,
671462EF1EB3396E00EAB194 /* UIImage+Extensions.swift in Sources */,
6714636F1EB3396E00EAB194 /* XibNameProtocol.swift in Sources */,
EFBE57DE1EC361620040E00A /* UIView+Layout.swift in Sources */,
@ -2188,18 +2492,25 @@
6714625F1EB3396E00EAB194 /* LogFormatter.swift in Sources */,
6714630B1EB3396E00EAB194 /* UIView+Rotation.swift in Sources */,
6714626F1EB3396E00EAB194 /* XibView.swift in Sources */,
672CC2AA1FEA6A7500EBFB0A /* RotateDrawingOperation.swift in Sources */,
6714637F1EB3396E00EAB194 /* ImageDrawingOperation.swift in Sources */,
671463371EB3396E00EAB194 /* DrawingOperation.swift in Sources */,
67F13A221FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */,
671462731EB3396E00EAB194 /* CursorError.swift in Sources */,
6714639B1EB3396E00EAB194 /* AnyLoadingIndicator.swift in Sources */,
6771DFE11EE99F6F002DCDAE /* DateFormattingArguments.swift in Sources */,
67F13A181FAB5A87008175B4 /* LoadingState.swift in Sources */,
67F13A3C1FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift in Sources */,
671463171EB3396E00EAB194 /* UIViewController+TopVisibleViewController.swift in Sources */,
6740D5D51FABDA47006BB7C0 /* DataSourceProtocol.swift in Sources */,
6714628B1EB3396E00EAB194 /* CGFloat+Pixels.swift in Sources */,
671462971EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */,
671463671EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */,
67CB1C041FAB7A640089D1B1 /* PaginationWrapperDelegate.swift in Sources */,
67A1FF911EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */,
6714624B1EB3396E00EAB194 /* FixedPageCursor.swift in Sources */,
671462CB1EB3396E00EAB194 /* String+Localization.swift in Sources */,
67F13A271FAB5B25008175B4 /* GeneralLoadingState.swift in Sources */,
36977763200CF12100ED9C6E /* UITableView+Extensions.swift in Sources */,
671462BB1EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */,
671463771EB3396E00EAB194 /* BorderDrawingOperation.swift in Sources */,
@ -2211,10 +2522,10 @@
6714632F1EB3396E00EAB194 /* ConfigurableController.swift in Sources */,
6714628F1EB3396E00EAB194 /* CGImage+Alpha.swift in Sources */,
67051ADD1EBC7C36008EADC0 /* SpinnerView.swift in Sources */,
674CC1E01FAB8D1D007B0EAE /* Single+CursorExhausted.swift in Sources */,
671462771EB3396E00EAB194 /* LeadKitError.swift in Sources */,
671462DB1EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */,
6714638F1EB3396E00EAB194 /* SolidFillDrawingOperation.swift in Sources */,
67E6C2381EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */,
671462531EB3396E00EAB194 /* StaticCursor.swift in Sources */,
6714629F1EB3396E00EAB194 /* CursorType+Slice.swift in Sources */,
6714636B1EB3396E00EAB194 /* ConfigurableView.swift in Sources */,
@ -2240,33 +2551,42 @@
buildActionMask = 2147483647;
files = (
671463491EB3396E00EAB194 /* ResettableType.swift in Sources */,
67F13A251FAB5B25008175B4 /* GeneralLoadingState.swift in Sources */,
A676AE491F97D28A001F9214 /* String+Extensions.swift in Sources */,
67CB1C031FAB7A640089D1B1 /* PaginationWrapperDelegate.swift in Sources */,
671462E51EB3396E00EAB194 /* UIColor+Hex.swift in Sources */,
671462CD1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */,
67F139F61FAB4F22008175B4 /* TotalCountCursor.swift in Sources */,
6771DFDF1EE99F6F002DCDAE /* DateFormattingArguments.swift in Sources */,
6771DFEB1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */,
67F13A3F1FAB614D008175B4 /* PaginationLoadingViewModelConfiguration.swift in Sources */,
67A1FF951EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */,
672CC2AB1FEA6A7600EBFB0A /* RotateDrawingOperation.swift in Sources */,
671462811EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */,
67E6C2361EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */,
671463551EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */,
671463611EB3396E00EAB194 /* SupportProtocol.swift in Sources */,
671462851EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */,
67F13A351FAB60C0008175B4 /* GeneralLoadingViewModel.swift in Sources */,
6714634D1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */,
671462F11EB3396E00EAB194 /* UIImage+SupportExtensions.swift in Sources */,
671462691EB3396E00EAB194 /* NetworkService.swift in Sources */,
671463111EB3396E00EAB194 /* UIViewController+DefaultXibName.swift in Sources */,
67F13A301FAB6053008175B4 /* LoadingViewModel.swift in Sources */,
671463411EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */,
671462651EB3396E00EAB194 /* PaginationViewModel.swift in Sources */,
671462911EB3396E00EAB194 /* CGImage+Crop.swift in Sources */,
67051ADC1EBC7C36008EADC0 /* SpinnerView.swift in Sources */,
671462FD1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */,
671463851EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */,
672CC2B41FEA72A000EBFB0A /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */,
671462D11EB3396E00EAB194 /* UIScrollView+Support.swift in Sources */,
671463911EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */,
67F13A201FAB5AE8008175B4 /* LoadingConfiguration.swift in Sources */,
67F139FC1FAB4F7E008175B4 /* TotalCountCursorConfiguration.swift in Sources */,
671462991EB3396E00EAB194 /* CGSize+Resize.swift in Sources */,
671463311EB3396E00EAB194 /* CursorType.swift in Sources */,
6714624D1EB3396E00EAB194 /* MapCursor.swift in Sources */,
671463251EB3396E00EAB194 /* Any+TypeName.swift in Sources */,
67F13A3A1FAB60DE008175B4 /* GeneralLoadingViewModelConfiguration.swift in Sources */,
671463891EB3396E00EAB194 /* RoundDrawingOperation.swift in Sources */,
675C1FB61F97CA33007D5249 /* AppearanceConfigurable.swift in Sources */,
671463811EB3396E00EAB194 /* PaddingDrawingOperation.swift in Sources */,
@ -2285,26 +2605,33 @@
6714625D1EB3396E00EAB194 /* LogFormatter.swift in Sources */,
671463091EB3396E00EAB194 /* UIView+Rotation.swift in Sources */,
6714626D1EB3396E00EAB194 /* XibView.swift in Sources */,
67CB1C071FAB7AE00089D1B1 /* PaginationWrapperDelegate+DefaultImplementation.swift in Sources */,
6714637D1EB3396E00EAB194 /* ImageDrawingOperation.swift in Sources */,
671463351EB3396E00EAB194 /* DrawingOperation.swift in Sources */,
67FDC2601FA310EA00C76A77 /* RequestError.swift in Sources */,
671462711EB3396E00EAB194 /* CursorError.swift in Sources */,
67F13A161FAB5A87008175B4 /* LoadingState.swift in Sources */,
671463991EB3396E00EAB194 /* AnyLoadingIndicator.swift in Sources */,
671463A81EB340C000EAB194 /* UIViewController+ConfigurableController.swift in Sources */,
671463151EB3396E00EAB194 /* UIViewController+TopVisibleViewController.swift in Sources */,
67F13A441FAB6256008175B4 /* PaginationLoadingViewModel.swift in Sources */,
674CC1DE1FAB8D1B007B0EAE /* Single+CursorExhausted.swift in Sources */,
671462891EB3396E00EAB194 /* CGFloat+Pixels.swift in Sources */,
674AF55D1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */,
6771DFD91EE99EBA002DCDAE /* DateFormattingService.swift in Sources */,
A676AE4E1F9810C1001F9214 /* Any+Cast.swift in Sources */,
671462951EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */,
671463651EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */,
6740D5D31FABDA46006BB7C0 /* DataSourceProtocol.swift in Sources */,
A6C9A5101F8BC79D009311CC /* Comparable+Extensions.swift in Sources */,
671462491EB3396E00EAB194 /* FixedPageCursor.swift in Sources */,
671462C91EB3396E00EAB194 /* String+Localization.swift in Sources */,
67F13A2B1FAB5FE0008175B4 /* PaginationLoadingState.swift in Sources */,
671462B91EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */,
671463751EB3396E00EAB194 /* BorderDrawingOperation.swift in Sources */,
6714633D1EB3396E00EAB194 /* LoadingIndicator.swift in Sources */,
671463191EB3396E00EAB194 /* UIWindow+Extensions.swift in Sources */,
674CC1DB1FAB8D09007B0EAE /* PaginationWrappable.swift in Sources */,
EFBE57DC1EC361620040E00A /* UIView+Layout.swift in Sources */,
671462551EB3396E00EAB194 /* App.swift in Sources */,
67A1FF901EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */,
@ -2320,7 +2647,10 @@
6771DFE51EE9A00A002DCDAE /* DateFormattingArguments+DateFormatter.swift in Sources */,
671462511EB3396E00EAB194 /* StaticCursor.swift in Sources */,
6714629D1EB3396E00EAB194 /* CursorType+Slice.swift in Sources */,
67F13A1B1FAB5ABB008175B4 /* LoadingProtocol.swift in Sources */,
67CB1BFB1FAB79530089D1B1 /* UITableView+PaginationWrappable.swift in Sources */,
671463691EB3396E00EAB194 /* ConfigurableView.swift in Sources */,
674CC1DA1FAB8D06007B0EAE /* UICollectionView+PaginationWrappable.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -21,50 +21,53 @@
//
import RxSwift
import RxCocoa
/// Cursor implementation for single load operation
public class SingleLoadCursor<Element>: ResettableCursorType {
public final class TotalCountCursor<CC: TotalCountCursorConfiguration>: ResettableCursorDataSource {
private let loadingObservable: Single<[Element]>
public typealias Element = CC.ElementType
public typealias ResultType = [CC.ElementType]
private var content: [Element] = []
private let configuration: CC
/// Initializer for array content type
///
/// - Parameter loadingObservable: Single observable with element of [Element] type
public init(loadingObservable: Single<[Element]>) {
self.loadingObservable = loadingObservable
private var elements: [Element] = []
public private(set) var totalCount: Int = .max
private let disposeBag = DisposeBag()
public var exhausted: Bool {
return count >= totalCount
}
public required init(resetFrom other: SingleLoadCursor) {
self.loadingObservable = other.loadingObservable
}
public private(set) var exhausted = false
public var count: Int {
return content.count
return elements.count
}
public subscript(index: Int) -> Element {
return content[index]
return elements[index]
}
public init(configuration: CC) {
self.configuration = configuration
}
public required init(resetFrom other: TotalCountCursor) {
self.configuration = other.configuration.reset()
}
public func loadNextBatch() -> Single<[Element]> {
return Single.deferred {
if self.exhausted {
return .error(CursorError.exhausted)
return configuration.nextBatchObservable()
.map { [configuration] listing in
configuration.getResult(from: listing)
}
return self.loadingObservable.do(onNext: { [weak self] newItems in
self?.onGot(result: newItems)
.do(onNext: { listingResult in
self.totalCount = listingResult.totalCount
self.elements += listingResult.results
})
}
}
private func onGot(result: [Element]) {
content = result
exhausted = true
.map {
$0.results
}
}
}

View File

@ -1,378 +0,0 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
import RxSwift
import RxCocoa
import UIScrollView_InfiniteScroll
/// PaginationTableViewWrapper delegate used for pagination results handling and
/// customization of bound states (loading, empty, error, etc.).
public protocol PaginationTableViewWrapperDelegate: class {
associatedtype Cursor: ResettableCursorType
/// Delegate method that handles loading new chunk of data.
///
/// - Parameters:
/// - wrapper: Wrapper object that loaded new items.
/// - newItems: New items.
/// - cursor: Cursor used to load items
func paginationWrapper(wrapper: PaginationTableViewWrapper<Cursor, Self>,
didLoad newItems: [Cursor.Element],
usingCursor cursor: Cursor)
/// Delegate method that handles reloading or initial loading of data.
///
/// - Parameters:
/// - wrapper: Wrapper object that reload items.
/// - allItems: New items.
/// - cursor: Cursor used to load items
func paginationWrapper(wrapper: PaginationTableViewWrapper<Cursor, Self>,
didReload allItems: [Cursor.Element],
usingCursor cursor: Cursor)
/// Delegate method that returns placeholder view for empty state.
///
/// - Parameter wrapper: Wrapper object that requests empty placeholder view.
/// - Returns: Configured instace of UIView.
func emptyPlaceholder(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>) -> UIView
/// Delegate method that returns placeholder view for error state.
///
/// - Parameters:
/// - wrapper: Wrapper object that requests error placeholder view.
/// - error: Error that occured due data loading.
/// - Returns: Configured instace of UIView.
func errorPlaceholder(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>,
forError error: Error) -> UIView
/// Delegate method that returns loading idicator for initial loading state.
/// This indicator will appear at center of the placeholders container.
///
/// - Parameter wrapper: Wrapper object that requests loading indicator
/// - Returns: Configured instace of AnyLoadingIndicator.
func initialLoadingIndicator(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>) -> AnyLoadingIndicator
/// Delegate method that returns loading idicator for initial loading state.
///
/// - Parameter wrapper: Wrapper object that requests loading indicator.
/// - Returns: Configured instace of AnyLoadingIndicator.
func loadingMoreIndicator(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>) -> AnyLoadingIndicator
/// Delegate method that returns instance of UIButton for "retry load more" action.
///
/// - Parameter wrapper: Wrapper object that requests button for "retry load more" action.
/// - Returns: Configured instace of AnyLoadingIndicator.
func retryLoadMoreButton(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>) -> UIButton
/// Delegate method that returns preferred height for "retry load more" button.
///
/// - Parameter wrapper: Wrapper object that requests height "retry load more" button.
/// - Returns: Preferred height of "retry load more" button.
func retryLoadMoreButtonHeight(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>) -> CGFloat
// Delegate method, used to clear tableView if placeholder is shown.
func clearTableView()
}
/// Class that connects PaginationViewModel with UITableView. It handles all non-visual and visual states.
final public class PaginationTableViewWrapper<Cursor, Delegate: PaginationTableViewWrapperDelegate>
where Delegate.Cursor == Cursor {
private let tableView: UITableView
private let paginationViewModel: PaginationViewModel<Cursor>
private weak var delegate: Delegate?
/// Sets the offset between the real end of the scroll view content and the scroll position,
/// so the handler can be triggered before reaching end. Defaults to 0.0;
public var infiniteScrollTriggerOffset: CGFloat {
get {
return tableView.infiniteScrollTriggerOffset
}
set {
tableView.infiniteScrollTriggerOffset = newValue
}
}
private let disposeBag = DisposeBag()
private var currentPlaceholderView: UIView?
private var currentPlaceholderViewTopConstraint: NSLayoutConstraint?
private let applicationCurrentyActive = Variable<Bool>(true)
/// Initializer with table view, placeholders container view, cusor and delegate parameters.
///
/// - Parameters:
/// - tableView: UITableView instance to work with.
/// - cursor: Cursor object that acts as data source.
/// - delegate: Delegate object for data loading events handling and UI customization.
public init(tableView: UITableView, cursor: Cursor, delegate: Delegate) {
self.tableView = tableView
self.paginationViewModel = PaginationViewModel(cursor: cursor)
self.delegate = delegate
bindViewModelStates()
createRefreshControl()
bindAppStateNotifications()
}
/// Method that reload all data in internal view model.
public func reload() {
paginationViewModel.load(.reload)
}
/// Method acts like reload, but shows initial loading view after being invoked.
public func retry() {
paginationViewModel.load(.retry)
}
/// Method that enables placeholders animation due pull-to-refresh interaction.
///
/// - Parameter scrollObservable: Observable that emits content offset as CGPoint.
public func setScrollObservable(_ scrollObservable: Observable<CGPoint>) {
scrollObservable.subscribe(onNext: { [weak self] offset in
self?.currentPlaceholderViewTopConstraint?.constant = -offset.y
})
.disposed(by: disposeBag)
}
// MARK: - States handling
private func onInitialState() {
//
}
private func onLoadingState(afterState: PaginationViewModel<Cursor>.State) {
if case .initial = afterState {
tableView.isUserInteractionEnabled = false
removeCurrentPlaceholderView()
guard let loadingIndicator = delegate?.initialLoadingIndicator(forPaginationWrapper: self) else {
return
}
let loadingIndicatorView = loadingIndicator.view
loadingIndicatorView.translatesAutoresizingMaskIntoConstraints = true
tableView.backgroundView = loadingIndicatorView
loadingIndicator.startAnimating()
currentPlaceholderView = loadingIndicatorView
} else {
removeInfiniteScroll()
tableView.tableFooterView = nil
}
}
private func onLoadingMoreState(afterState: PaginationViewModel<Cursor>.State) {
if case .error = afterState { // user tap retry button in table footer
tableView.tableFooterView = nil
addInfiniteScroll()
tableView.beginInfiniteScroll(true)
}
}
private func onResultsState(newItems: [Cursor.Element],
inCursor cursor: Cursor,
afterState: PaginationViewModel<Cursor>.State) {
tableView.isUserInteractionEnabled = true
if case .loading = afterState {
delegate?.paginationWrapper(wrapper: self, didReload: newItems, usingCursor: cursor)
removeCurrentPlaceholderView()
tableView.support.refreshControl?.endRefreshing()
if !cursor.exhausted {
addInfiniteScroll()
}
} else if case .loadingMore = afterState {
delegate?.paginationWrapper(wrapper: self, didLoad: newItems, usingCursor: cursor)
tableView.finishInfiniteScroll()
}
}
private func onErrorState(error: Error, afterState: PaginationViewModel<Cursor>.State) {
if case .loading = afterState {
defer {
tableView.support.refreshControl?.endRefreshing()
}
guard let errorView = delegate?.errorPlaceholder(forPaginationWrapper: self, forError: error) else {
return
}
replacePlaceholderViewIfNeeded(with: errorView)
} else if case .loadingMore = afterState {
removeInfiniteScroll()
guard let retryButton = delegate?.retryLoadMoreButton(forPaginationWrapper: self),
let retryButtonHeigth = delegate?.retryLoadMoreButtonHeight(forPaginationWrapper: self) else {
return
}
retryButton.frame = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: retryButtonHeigth)
retryButton.rx.controlEvent(.touchUpInside)
.bind { [weak self] in
self?.paginationViewModel.load(.next)
}
.disposed(by: disposeBag)
tableView.tableFooterView = retryButton
}
}
private func onEmptyState() {
defer {
tableView.support.refreshControl?.endRefreshing()
}
guard let emptyView = delegate?.emptyPlaceholder(forPaginationWrapper: self) else {
return
}
replacePlaceholderViewIfNeeded(with: emptyView)
}
private func replacePlaceholderViewIfNeeded(with placeholderView: UIView) {
tableView.isUserInteractionEnabled = true
removeCurrentPlaceholderView()
placeholderView.translatesAutoresizingMaskIntoConstraints = false
placeholderView.isHidden = false
// I was unable to add pull-to-refresh placeholder scroll behaviour without this trick
let wrapperView = UIView()
wrapperView.addSubview(placeholderView)
let leadingConstraint = placeholderView.leadingAnchor.constraint(equalTo: wrapperView.leadingAnchor)
let trailingConstraint = placeholderView.trailingAnchor.constraint(equalTo: wrapperView.trailingAnchor)
let topConstraint = placeholderView.topAnchor.constraint(equalTo: wrapperView.topAnchor)
let bottomConstraint = placeholderView.bottomAnchor.constraint(equalTo: wrapperView.bottomAnchor)
wrapperView.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
currentPlaceholderViewTopConstraint = topConstraint
tableView.backgroundView = wrapperView
currentPlaceholderView = placeholderView
}
// MARK: - private stuff
private func onExhaustedState() {
removeInfiniteScroll()
}
private func addInfiniteScroll() {
tableView.addInfiniteScroll { [weak paginationViewModel] _ in
paginationViewModel?.load(.next)
}
tableView.infiniteScrollIndicatorView = delegate?.loadingMoreIndicator(forPaginationWrapper: self).view
}
private func removeInfiniteScroll() {
tableView.finishInfiniteScroll()
tableView.removeInfiniteScroll()
}
private func createRefreshControl() {
let refreshControl = UIRefreshControl()
refreshControl.rx.controlEvent(.valueChanged)
.bind { [weak self] in
self?.reload()
}
.disposed(by: disposeBag)
tableView.support.setRefreshControl(refreshControl)
}
private func bindViewModelStates() {
typealias State = PaginationViewModel<Cursor>.State
paginationViewModel.state.flatMap { [applicationCurrentyActive] state -> Driver<State> in
if applicationCurrentyActive.value {
return .just(state)
} else {
return applicationCurrentyActive
.asObservable()
.filter { $0 }
.delay(0.5, scheduler: MainScheduler.instance)
.asDriver(onErrorJustReturn: true)
.map { _ in state }
}
}
.drive(onNext: { [weak self] state in
switch state {
case .initial:
self?.onInitialState()
case .loading(let after):
self?.onLoadingState(afterState: after)
case .loadingMore(let after):
self?.onLoadingMoreState(afterState: after)
case .results(let newItems, let cursor, let after):
self?.onResultsState(newItems: newItems, inCursor: cursor, afterState: after)
case .error(let error, let after):
self?.delegate?.clearTableView()
self?.onErrorState(error: error, afterState: after)
case .empty:
self?.delegate?.clearTableView()
self?.onEmptyState()
case .exhausted:
self?.onExhaustedState()
}
})
.disposed(by: disposeBag)
}
private func removeCurrentPlaceholderView() {
tableView.backgroundView = nil
}
private func bindAppStateNotifications() {
let notificationCenter = NotificationCenter.default.rx
notificationCenter.notification(.UIApplicationWillResignActive)
.subscribe(onNext: { [weak self] _ in
self?.applicationCurrentyActive.value = false
})
.disposed(by: disposeBag)
notificationCenter.notification(.UIApplicationDidBecomeActive)
.subscribe(onNext: { [weak self] _ in
self?.applicationCurrentyActive.value = true
})
.disposed(by: disposeBag)
}
}

View File

@ -1,157 +0,0 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
/// Cursor type which can be resetted
public typealias ResettableCursorType = CursorType & ResettableType
/// Class that encapsulate all pagination logic
public final class PaginationViewModel<C: ResettableCursorType> {
/// Enum contains all possible states for PaginationViewModel class.
///
/// - initial: initial state of view model.
/// Can occur only once after initial binding.
/// - loading: loading state of view model. Contains previous state of view model.
/// Can occur after any state.
/// - loadingMore: loading more items state of view model. Contains previous state of view model.
/// Can occur after error or results state.
/// - results: results state of view model. Contains loaded items, cursor and previous state of view model.
/// Can occur after loading or loadingMore state.
/// - error: error state of view model. Contains received error and previous state of view model.
/// Can occur after loading or loadingMore state.
/// - empty: empty state of view model.
/// Can occur after loading or loadingMore state when we got empty result (zero items).
/// - exhausted: exhausted state of view model.
/// Can occur after results state or after initial->loading state when cursor reports that it's exhausted.
public indirect enum State {
case initial
case loading(after: State)
case loadingMore(after: State)
case results(newItems: [C.Element], inCursor: C, after: State)
case error(error: Error, after: State)
case empty
case exhausted
}
/// Enum represents possible load types for PaginationViewModel class
///
/// - reload: reload all items and reset cursor to initial state.
/// - next: load next batch of items.
/// - retry: reload to initial loading state
public enum LoadType {
case reload
case retry
case next
}
private var cursor: C
private let internalState = Variable<State>(.initial)
private var currentRequest: Disposable?
private let internalScheduler = SerialDispatchQueueScheduler(qos: .default)
/// Current PaginationViewModel state Driver
public var state: Driver<State> {
return internalState.asDriver()
}
/// Initializer with enclosed cursor
///
/// - Parameter cursor: cursor to use for pagination
public init(cursor: C) {
self.cursor = cursor
}
/// Mathod which triggers loading of items.
///
/// - Parameter loadType: type of loading. See LoadType enum.
public func load(_ loadType: LoadType) {
switch loadType {
case .reload:
reload()
case .retry:
reload(isRetry: true)
case .next:
if case .exhausted = internalState.value {
fatalError("You shouldn't call load(.next) after got .exhausted state!")
}
internalState.value = .loadingMore(after: internalState.value)
}
let currentCursor = cursor
currentRequest = currentCursor.loadNextBatch()
.subscribeOn(internalScheduler)
.subscribe(onSuccess: { [weak self] newItems in
self?.onGot(newItems: newItems, using: currentCursor)
}, onError: { [weak self] error in
self?.onGot(error: error)
})
}
private func onGot(newItems: [C.Element], using cursor: C) {
if newItems.isEmpty {
internalState.value = .empty
return
}
internalState.value = .results(newItems: newItems, inCursor: cursor, after: internalState.value)
if cursor.exhausted {
internalState.value = .exhausted
}
}
private func onGot(error: Error) {
if case .exhausted? = error as? CursorError, case .loading(let after) = internalState.value {
switch after {
case .initial, .empty: // cursor exhausted after creation
internalState.value = .empty
default:
internalState.value = .error(error: error, after: internalState.value)
}
} else {
internalState.value = .error(error: error, after: internalState.value)
}
}
private func reload(isRetry: Bool = false) {
currentRequest?.dispose()
cursor = cursor.reset()
if isRetry {
internalState.value = .initial
}
internalState.value = .loading(after: internalState.value)
}
}

View File

@ -0,0 +1,337 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
import UIScrollView_InfiniteScroll
/// Class that connects PaginationViewModel with UIScrollView. It handles all non-visual and visual states.
final public class PaginationWrapper<Cursor: ResettableCursorType, Delegate: PaginationWrapperDelegate>
where Cursor == Delegate.DataSourceType, Cursor.ResultType == [Cursor.Element] {
private typealias LoadingState = PaginationLoadingViewModel<Cursor>.LoadingStateType
private var wrappedView: AnyPaginationWrappableView
private let paginationViewModel: PaginationLoadingViewModel<Cursor>
private weak var delegate: Delegate?
/// Sets the offset between the real end of the scroll view content and the scroll position,
/// so the handler can be triggered before reaching end. Defaults to 0.0;
public var infiniteScrollTriggerOffset: CGFloat {
get {
return wrappedView.scrollView.infiniteScrollTriggerOffset
}
set {
wrappedView.scrollView.infiniteScrollTriggerOffset = newValue
}
}
public var pullToRefreshEnabled: Bool = true {
didSet {
if pullToRefreshEnabled {
createRefreshControl()
} else {
removeRefreshControl()
}
}
}
private let disposeBag = DisposeBag()
private var currentPlaceholderView: UIView?
private var currentPlaceholderViewTopConstraint: NSLayoutConstraint?
private let applicationCurrentyActive = Variable<Bool>(true)
/// Initializer with table view, placeholders container view, cusor and delegate parameters.
///
/// - Parameters:
/// - wrappedView: UIScrollView instance to work with.
/// - cursor: Cursor object that acts as data source.
/// - delegate: Delegate object for data loading events handling and UI customization.
public init(wrappedView: AnyPaginationWrappableView, cursor: Cursor, delegate: Delegate) {
self.wrappedView = wrappedView
self.delegate = delegate
let configuration = PaginationLoadingViewModelConfiguration(dataSource: cursor)
self.paginationViewModel = PaginationLoadingViewModel(configuration: configuration)
bindViewModelStates()
createRefreshControl()
bindAppStateNotifications()
}
/// Method that reload all data in internal view model.
public func reload() {
paginationViewModel.reload()
}
/// Method acts like reload, but shows initial loading view after being invoked.
public func retry() {
paginationViewModel.retry()
}
/// Method that enables placeholders animation due pull-to-refresh interaction.
///
/// - Parameter scrollObservable: Observable that emits content offset as CGPoint.
public func setScrollObservable(_ scrollObservable: Observable<CGPoint>) {
scrollObservable.subscribe(onNext: { [weak self] offset in
self?.currentPlaceholderViewTopConstraint?.constant = -offset.y
})
.disposed(by: disposeBag)
}
// MARK: - States handling
private func onInitialState() {
//
}
private func onLoadingState(afterState: LoadingState) {
if case .initial = afterState {
wrappedView.scrollView.isUserInteractionEnabled = false
removeCurrentPlaceholderView()
guard let loadingIndicator = delegate?.initialLoadingIndicator() else {
return
}
let loadingIndicatorView = loadingIndicator.view
loadingIndicatorView.translatesAutoresizingMaskIntoConstraints = true
wrappedView.backgroundView = loadingIndicatorView
loadingIndicator.startAnimating()
currentPlaceholderView = loadingIndicatorView
} else {
removeInfiniteScroll()
wrappedView.footerView = nil
}
}
private func onLoadingMoreState(afterState: LoadingState) {
if case .error = afterState { // user tap retry button in table footer
delegate?.retryLoadMoreButtonIsAboutToHide()
wrappedView.footerView = nil
addInfiniteScroll(withHandler: false)
wrappedView.scrollView.beginInfiniteScroll(true)
}
}
private func onResultsState(newItems: [Cursor.Element],
from cursor: Cursor,
afterState: LoadingState) {
wrappedView.scrollView.isUserInteractionEnabled = true
if case .loading = afterState {
delegate?.paginationWrapper(didReload: newItems, using: cursor)
removeCurrentPlaceholderView()
wrappedView.scrollView.support.refreshControl?.endRefreshing()
addInfiniteScroll(withHandler: true)
} else if case .loadingMore = afterState {
delegate?.paginationWrapper(didLoad: newItems, using: cursor)
readdInfiniteScrollWithHandler()
}
}
private func onErrorState(error: Error, afterState: LoadingState) {
if case .loading = afterState {
defer {
wrappedView.scrollView.support.refreshControl?.endRefreshing()
}
guard let errorView = delegate?.errorPlaceholder(for: error) else {
return
}
replacePlaceholderViewIfNeeded(with: errorView)
delegate?.clearView()
} else if case .loadingMore = afterState {
removeInfiniteScroll()
guard let retryButton = delegate?.retryLoadMoreButton(),
let retryButtonHeigth = delegate?.retryLoadMoreButtonHeight() else {
return
}
retryButton.frame = CGRect(x: 0, y: 0, width: wrappedView.scrollView.bounds.width, height: retryButtonHeigth)
retryButton.rx.controlEvent(.touchUpInside)
.bind { [weak self] in
self?.paginationViewModel.loadMore()
}
.disposed(by: disposeBag)
delegate?.retryLoadMoreButtonIsAboutToShow()
wrappedView.footerView = retryButton
}
}
private func onEmptyState() {
defer {
wrappedView.scrollView.support.refreshControl?.endRefreshing()
}
guard let emptyView = delegate?.emptyPlaceholder() else {
return
}
replacePlaceholderViewIfNeeded(with: emptyView)
}
private func replacePlaceholderViewIfNeeded(with placeholderView: UIView) {
wrappedView.scrollView.isUserInteractionEnabled = true
removeCurrentPlaceholderView()
placeholderView.translatesAutoresizingMaskIntoConstraints = false
placeholderView.isHidden = false
// I was unable to add pull-to-refresh placeholder scroll behaviour without this trick
let wrapperView = UIView()
wrapperView.addSubview(placeholderView)
let leadingConstraint = placeholderView.leadingAnchor.constraint(equalTo: wrapperView.leadingAnchor)
let trailingConstraint = placeholderView.trailingAnchor.constraint(equalTo: wrapperView.trailingAnchor)
let topConstraint = placeholderView.topAnchor.constraint(equalTo: wrapperView.topAnchor)
let bottomConstraint = placeholderView.bottomAnchor.constraint(equalTo: wrapperView.bottomAnchor)
wrapperView.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
currentPlaceholderViewTopConstraint = topConstraint
wrappedView.backgroundView = wrapperView
currentPlaceholderView = placeholderView
}
// MARK: - private stuff
private func onExhaustedState() {
removeInfiniteScroll()
}
private func readdInfiniteScrollWithHandler() {
removeInfiniteScroll()
addInfiniteScroll(withHandler: true)
}
private func addInfiniteScroll(withHandler: Bool) {
if withHandler {
wrappedView.scrollView.addInfiniteScroll { [weak paginationViewModel] _ in
paginationViewModel?.loadMore()
}
} else {
wrappedView.scrollView.addInfiniteScroll { _ in }
}
wrappedView.scrollView.infiniteScrollIndicatorView = delegate?.loadingMoreIndicator().view
}
private func removeInfiniteScroll() {
wrappedView.scrollView.finishInfiniteScroll()
wrappedView.scrollView.removeInfiniteScroll()
}
private func createRefreshControl() {
let refreshControl = UIRefreshControl()
refreshControl.rx.controlEvent(.valueChanged)
.bind { [weak self] in
self?.reload()
}
.disposed(by: disposeBag)
wrappedView.scrollView.support.setRefreshControl(refreshControl)
}
private func removeRefreshControl() {
wrappedView.scrollView.support.setRefreshControl(nil)
}
private func bindViewModelStates() {
typealias State = PaginationLoadingViewModel<Cursor>.LoadingStateType
paginationViewModel.stateDriver
.flatMap { [applicationCurrentyActive] state -> Driver<State> in
if applicationCurrentyActive.value {
return .just(state)
} else {
return applicationCurrentyActive
.asObservable()
.filter { $0 }
.delay(0.5, scheduler: MainScheduler.instance)
.asDriver(onErrorJustReturn: true)
.map { _ in state }
}
}
.drive(onNext: { [weak self] state in
switch state {
case .initial:
self?.onInitialState()
case .loading(let after):
self?.onLoadingState(afterState: after)
case .loadingMore(let after):
self?.onLoadingMoreState(afterState: after)
case .results(let newItems, let from, let after):
self?.onResultsState(newItems: newItems, from: from, afterState: after)
case .error(let error, let after):
self?.onErrorState(error: error, afterState: after)
case .empty:
self?.delegate?.clearView()
self?.onEmptyState()
case .exhausted:
self?.onExhaustedState()
}
})
.disposed(by: disposeBag)
}
private func removeCurrentPlaceholderView() {
wrappedView.backgroundView = nil
}
private func bindAppStateNotifications() {
let notificationCenter = NotificationCenter.default.rx
notificationCenter.notification(.UIApplicationWillResignActive)
.subscribe(onNext: { [weak self] _ in
self?.applicationCurrentyActive.value = false
})
.disposed(by: disposeBag)
notificationCenter.notification(.UIApplicationDidBecomeActive)
.subscribe(onNext: { [weak self] _ in
self?.applicationCurrentyActive.value = true
})
.disposed(by: disposeBag)
}
}

View File

@ -25,7 +25,7 @@ import TableKit
/// Empty cell class. Do not use it directly.
/// - see: `EmptyCellRow`
public final class EmptyCell: SeparatorCell, AppearanceConfigurable, ConfigurableCell {
public final class EmptyCell: SeparatorTableCell, AppearanceConfigurable, ConfigurableCell {
public struct Appearance {
let color: UIColor

View File

@ -24,9 +24,9 @@ import UIKit
/// Separator configuration. Supports positioning, color and height per each separator
public struct SeparatorConfiguration {
let color: UIColor
let insets: UIEdgeInsets?
let height: CGFloat
public let color: UIColor
public let insets: UIEdgeInsets?
public let height: CGFloat
/// Initialize configuration with parameters
/// - parameter color: Color must be provided

View File

@ -32,52 +32,12 @@ private enum Constants {
/// - in `configure(with:)` you must call `configureSeparator(with:)`
/// - separators are simple views, that located on `contentView`.
/// - if you hide that with another view that fully hide you can use that method `moveSeparators(to:)`
open class SeparatorCell: UITableViewCell {
// MARK: - Public
/// Configure separator with viewModel
/// - parameter separatorType: type of separators
public func configureSeparator(with separatorType: CellSeparatorType) {
switch separatorType {
case .none:
topView.isHidden = true
bottomView.isHidden = true
case .bottom(let configuration):
topView.isHidden = true
bottomView.isHidden = false
updateBottomSeparator(with: configuration)
setNeedsUpdateConstraints()
case .top(let configuration):
topView.isHidden = false
bottomView.isHidden = true
updateTopSeparator(with: configuration)
setNeedsUpdateConstraints()
case .full(let topConfiguration, let bottomConfiguration):
topView.isHidden = false
bottomView.isHidden = false
updateTopSeparator(with: topConfiguration)
updateBottomSeparator(with: bottomConfiguration)
setNeedsUpdateConstraints()
}
}
/// Move separator upward in hierarchy
public func bringSeparatorsToFront() {
contentView.bringSubview(toFront: topView)
contentView.bringSubview(toFront: bottomView)
}
/// Move separator backward in hierarchy
public func sendSeparatorsToBack() {
contentView.sendSubview(toBack: topView)
contentView.sendSubview(toBack: bottomView)
}
open class SeparatorTableCell: UITableViewCell, SeparatorCell {
// MARK: - Private
private var topView: UIView!
private var bottomView: UIView!
private(set) public var topView: UIView!
private(set) public var bottomView: UIView!
// top separator constraints
private var topViewLeftConstraint: NSLayoutConstraint!
@ -143,13 +103,13 @@ open class SeparatorCell: UITableViewCell {
return view
}
private func updateTopSeparator(with configuration: SeparatorConfiguration) {
public func updateTopSeparator(with configuration: SeparatorConfiguration) {
topView.backgroundColor = configuration.color
topSeparatorHeight = configuration.height
topSeparatorInsets = configuration.insets ?? .zero
}
private func updateBottomSeparator(with configuration: SeparatorConfiguration) {
public func updateBottomSeparator(with configuration: SeparatorConfiguration) {
bottomView.backgroundColor = configuration.color
bottomSeparatorHeight = configuration.height
bottomSeparatorInsets = configuration.insets ?? .zero
@ -158,30 +118,39 @@ open class SeparatorCell: UITableViewCell {
private func createConstraints() {
// height
topViewHeightConstraint = topView.heightAnchor.constraint(equalToConstant: topSeparatorHeight)
topViewHeightConstraint.isActive = true
bottomViewHeightConstraint = bottomView.heightAnchor.constraint(equalToConstant: bottomSeparatorHeight)
bottomViewHeightConstraint.isActive = true
// top separator
topViewTopConstraint = topView.topAnchor.constraint(equalTo: contentView.topAnchor)
topViewTopConstraint.isActive = true
topViewRightConstraint = contentView.rightAnchor.constraint(equalTo: topView.rightAnchor)
topViewRightConstraint.isActive = true
if let topView = topView {
topViewRightConstraint = contentView.rightAnchor.constraint(equalTo: topView.rightAnchor)
}
topViewLeftConstraint = topView.leftAnchor.constraint(equalTo: contentView.leftAnchor)
topViewLeftConstraint.isActive = true
// bottom separator
bottomViewRightConstraint = contentView.rightAnchor.constraint(equalTo: bottomView.rightAnchor)
bottomViewRightConstraint.isActive = true
if let bottomView = bottomView {
bottomViewRightConstraint = contentView.rightAnchor.constraint(equalTo: bottomView.rightAnchor)
}
bottomViewLeftConstraint = bottomView.leftAnchor.constraint(equalTo: contentView.leftAnchor)
bottomViewLeftConstraint.isActive = true
bottomViewBottomConstraint = bottomView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
bottomViewBottomConstraint.isActive = true
let allConstraints = [
topViewHeightConstraint,
bottomViewHeightConstraint,
topViewTopConstraint,
topViewRightConstraint,
topViewLeftConstraint,
bottomViewRightConstraint,
bottomViewLeftConstraint,
bottomViewBottomConstraint
].flatMap { $0 }
NSLayoutConstraint.activate(allConstraints)
}
open override func prepareForReuse() {

View File

@ -35,7 +35,7 @@ public final class SeparatorRowBox {
/// Initialize AnyBaseTableRow with tableRow
/// - parameter row: TableRow which `cell` conforms to SeparatorCell
public init<T>(row: TableRow<T>) where T: SeparatorCell {
public init<T>(row: TableRow<T>) where T: SeparatorTableCell {
self.row = row
setSeparatorHandler = row.set
}

View File

@ -55,11 +55,13 @@ open class XibView: UIView {
addSubview(view)
configure()
backgroundColor = .clear
configure(xibView: view)
}
/// Provide initial configuration. Called once
open func configure() {
open func configure(xibView: UIView) {
}

View File

@ -70,7 +70,7 @@ public extension Array {
// Subscript for safe access to element by index
subscript(safe index: Index) -> Element? {
return index < count ? self[index] : nil
return indices.contains(index) ? self[index] : nil
}
}

View File

@ -0,0 +1,39 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public extension FloatingPoint {
/// Converts degrees to radians
///
/// - Returns: radians
func degreesToRadians() -> Self {
return self * .pi / 180
}
/// Converts radians to degrees
///
/// - Returns: degrees
func radiansToDegrees() -> Self {
return self * 180 / .pi
}
}

View File

@ -22,9 +22,9 @@
import UIKit
public extension PaginationTableViewWrapperDelegate {
public extension PaginationWrapperDelegate {
func emptyPlaceholder(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>) -> UIView {
func emptyPlaceholder() -> UIView {
let placeholder = UIView()
let label = UILabel()
@ -38,8 +38,7 @@ public extension PaginationTableViewWrapperDelegate {
return placeholder
}
func errorPlaceholder(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>,
forError error: Error) -> UIView {
func errorPlaceholder(for error: Error) -> UIView {
let placeholder = UIView()
@ -54,8 +53,7 @@ public extension PaginationTableViewWrapperDelegate {
return placeholder
}
func initialLoadingIndicator(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>)
-> AnyLoadingIndicator {
func initialLoadingIndicator() -> AnyLoadingIndicator {
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
indicator.color = .gray
@ -63,15 +61,14 @@ public extension PaginationTableViewWrapperDelegate {
return AnyLoadingIndicator(indicator)
}
func loadingMoreIndicator(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>)
-> AnyLoadingIndicator {
func loadingMoreIndicator() -> AnyLoadingIndicator {
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
return AnyLoadingIndicator(indicator)
}
func retryLoadMoreButton(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>) -> UIButton {
func retryLoadMoreButton() -> UIButton {
let retryButton = UIButton(type: .custom)
retryButton.backgroundColor = .lightGray
retryButton.setTitle("Retry load more", for: .normal)
@ -79,7 +76,7 @@ public extension PaginationTableViewWrapperDelegate {
return retryButton
}
func retryLoadMoreButtonHeight(forPaginationWrapper wrapper: PaginationTableViewWrapper<Cursor, Self>) -> CGFloat {
func retryLoadMoreButtonHeight() -> CGFloat {
return 44
}

View File

@ -0,0 +1,46 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
extension UICollectionView: ScrollViewHolder {
public var scrollView: UIScrollView {
return self
}
}
extension UICollectionView: BackgroundViewHolder {}
extension UICollectionView: FooterViewHolder {
public var footerView: UIView? {
get {
return nil
}
set {
// nothing
}
}
}

View File

@ -0,0 +1,46 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
extension UITableView: ScrollViewHolder {
public var scrollView: UIScrollView {
return self
}
}
extension UITableView: BackgroundViewHolder {}
extension UITableView: FooterViewHolder {
public var footerView: UIView? {
get {
return tableFooterView
}
set {
tableFooterView = newValue
}
}
}

View File

@ -0,0 +1,37 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
extension PrimitiveSequence where E: Collection, Trait == SingleTrait {
func exhaustedIfEmpty() -> PrimitiveSequence<Trait, E> {
return map {
if $0.isEmpty {
throw CursorError.exhausted
} else {
return $0
}
}
}
}

View File

@ -0,0 +1,42 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
extension Observable: DataSourceProtocol {
public typealias ResultType = Element
}
extension PrimitiveSequence: DataSourceProtocol {
public typealias ResultType = Element
}
extension SharedSequence: DataSourceProtocol {
public typealias ResultType = Element
}

View File

@ -24,7 +24,7 @@ import TableKit
private let configureSeparatorActionId = "TableRowConfigureSeparatorActionId"
public extension TableRow where CellType: SeparatorCell {
public extension TableRow where CellType: SeparatorTableCell {
func with(separatorType: CellSeparatorType) -> Self {
set(separatorType: separatorType)
@ -44,7 +44,7 @@ public extension TableRow where CellType: SeparatorCell {
}
public extension TableRow where CellType: SeparatorCell {
public extension TableRow where CellType: SeparatorTableCell {
/// TableRow typed as SeparatorRowBox
var separatorRowBox: SeparatorRowBox {

View File

@ -96,7 +96,7 @@ public extension UIColor {
hexStr = hexString
}
let charactersCount = hexStr.characters.count
let charactersCount = hexStr.count
switch charactersCount {
case 3, 4:

View File

@ -44,7 +44,7 @@ public extension UIImage {
/// Creates an image from a UIView.
///
/// - Parameter fromView: The source view.
/// - Returns: A new instance of UIImage or nil if something goes wrong.
/// - Returns: A new instance of UIImage.
static func imageFrom(view: UIView) -> UIImage {
let operation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size)
@ -54,17 +54,15 @@ public extension UIImage {
/// Render current template UIImage into new image using given color.
///
/// - Parameter color: Color to fill template image.
/// - Returns: A new UIImage rendered with given color.
/// - Returns: A new UIImage rendered with given color or original image if something goes wrong.
func renderTemplate(withColor color: UIColor) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let operation = TemplateDrawingOperation(image: image,
imageSize: size,
color: color.cgColor)
return operation.imageFromNewRenderer(scale: scale)
}
let operation = TemplateDrawingOperation(image: image,
imageSize: size,
color: color.cgColor)
return operation.imageFromNewRenderer(scale: scale)
}
/// Creates a new image with rounded corners and border.
@ -74,49 +72,45 @@ public extension UIImage {
/// - borderWidth: The size of the border.
/// - color: The color of the border.
/// - extendSize: Extend result image size and don't overlap source image by border.
/// - Returns: A new image with rounded corners.
/// - Returns: A new image with rounded corners or original image if something goes wrong.
func roundCorners(cornerRadius: CGFloat,
borderWidth: CGFloat,
color: UIColor,
extendSize: Bool = false) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: cornerRadius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: cornerRadius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
/// Creates a new circle image.
///
/// - Returns: A new circled image.
/// - Returns: A new circled image or original image if something goes wrong.
func roundCornersToCircle() -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let radius = CGFloat(min(size.width, size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
let radius = CGFloat(min(size.width, size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
/// Creates a new circle image with a border.
@ -125,33 +119,31 @@ public extension UIImage {
/// - borderWidth: The size of the border.
/// - borderColor: The color of the border.
/// - extendSize: Extend result image size and don't overlap source image by border (default = false).
/// - Returns: A new image with rounded corners or nil if something goes wrong.
/// - Returns: A new image with rounded corners or original image if something goes wrong.
func roundCornersToCircle(borderWidth: CGFloat,
borderColor: UIColor,
extendSize: Bool = false) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let radius = CGFloat(min(size.width, size.height) / 2)
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
let radius = CGFloat(min(size.width, size.height) / 2)
let roundOperation = RoundDrawingOperation(image: image,
imageSize: size,
radius: radius)
guard let roundImage = roundOperation.imageFromNewRenderer(scale: scale).cgImage else {
return self
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewRenderer(scale: scale)
}
/// Creates a resized copy of an image.
@ -161,62 +153,85 @@ public extension UIImage {
/// - contentMode: The way to handle the content in the new size.
/// - cropToImageBounds: Should output image size match resized image size.
/// Note: If passed true with ResizeMode.scaleAspectFit content mode it will give the original image.
/// - Returns: A new image scaled to new size.
/// - Returns: A new image scaled to new size or original image if something goes wrong.
func resize(newSize: CGSize,
contentMode: ResizeMode = .scaleToFill,
cropToImageBounds: Bool = false) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let operation = ResizeDrawingOperation(image: image,
imageSize: size,
preferredNewSize: newSize,
resizeMode: contentMode,
cropToImageBounds: cropToImageBounds)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
let operation = ResizeDrawingOperation(image: image,
imageSize: size,
preferredNewSize: newSize,
resizeMode: contentMode,
cropToImageBounds: cropToImageBounds)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
/// Adds an alpha channel if UIImage doesn't already have one.
///
/// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one.
func applyAlpha() -> UIImage {
guard let image = cgImage, !image.hasAlpha else {
return self
return withCGImage { image in
guard !image.hasAlpha else {
return self
}
let operation = ImageDrawingOperation(image: image,
newSize: size,
opaque: false)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
let operation = ImageDrawingOperation(image: image,
newSize: size,
opaque: false)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
/// Creates a copy of the image with border of the given size added around its edges.
///
/// - Parameter padding: The padding amount.
/// - Returns: A new padded image or nil if something goes wrong.
/// - Returns: A new padded image or original image if something goes wrong.
func applyPadding(_ padding: CGFloat) -> UIImage {
guard let image = cgImage else {
return self
return withCGImage { image in
let operation = PaddingDrawingOperation(image: image, imageSize: size, padding: padding)
return operation.imageFromNewRenderer(scale: scale).redraw()
}
}
let operation = PaddingDrawingOperation(image: image, imageSize: size, padding: padding)
/// Creates a copy of the image rotated by the given amount of degrees.
///
/// - Parameters:
/// - degrees: The number of degrees.
/// - clockwise: Should rotate image clockwise.
/// - Returns: A new rotated image or original image if something goes wrong.
func rotate(degrees: CGFloat, clockwise: Bool = true) -> UIImage {
return withCGImage { image in
let radians = degrees.degreesToRadians()
return operation.imageFromNewRenderer(scale: scale).redraw()
let operation = RotateDrawingOperation(image: image,
imageSize: size,
radians: radians,
clockwise: clockwise)
return operation.imageFromNewRenderer(scale: scale)
}
}
/// Workaround to fix flipped image rendering (by Y)
private func redraw() -> UIImage {
return withCGImage { image in
let operation = ImageDrawingOperation(image: image, newSize: size)
return operation.imageFromNewRenderer(scale: scale)
}
}
private func withCGImage(_ actionClosure: (CGImage) -> UIImage) -> UIImage {
guard let image = cgImage else {
return self
}
let operation = ImageDrawingOperation(image: image, newSize: size)
return operation.imageFromNewRenderer(scale: scale)
return actionClosure(image)
}
}

View File

@ -44,9 +44,15 @@ public extension Support where Base: UIImage {
/// - Parameter fromView: The source view.
/// - Returns: A new instance of UIImage or nil if something goes wrong.
static func imageFrom(view: UIView) -> Support<UIImage>? {
let layerDrawingOperation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size)
let operation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size)
return layerDrawingOperation.imageFromNewContext(scale: UIScreen.main.scale)?.support.flipY()
guard let rotatedImage = operation.imageFromNewContext(scale: UIScreen.main.scale) else {
return nil
}
let flipOperation = rotatedImage.cgImage?.flipYOperation(size: rotatedImage.size)
return flipOperation?.imageFromNewContext(scale: rotatedImage.scale)?.support
}
/// Render current template UIImage into new image using given color.
@ -54,15 +60,19 @@ public extension Support where Base: UIImage {
/// - Parameter color: Color to fill template image.
/// - Returns: A new UIImage rendered with given color or nil if something goes wrong.
func renderTemplate(withColor color: UIColor) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let operation = TemplateDrawingOperation(image: image,
imageSize: base.size,
color: color.cgColor)
guard let templateImage = operation.imageFromNewContext(scale: base.scale) else {
return nil
}
let flipOperation = templateImage.cgImage?.flipYOperation(size: templateImage.size)
return flipOperation?.imageFromNewContext(scale: templateImage.scale)
}
let operation = TemplateDrawingOperation(image: image,
imageSize: base.size,
color: color.cgColor)
return operation.imageFromNewContext(scale: base.scale)?.support
}
/// Creates a new image with rounded corners and border.
@ -78,43 +88,39 @@ public extension Support where Base: UIImage {
color: UIColor,
extendSize: Bool = false) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: cornerRadius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)
}
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: cornerRadius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: color.cgColor,
radius: cornerRadius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)?.support
}
/// Creates a new circle image.
///
/// - Returns: A new circled image or nil if something goes wrong.
func roundCornersToCircle() -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
return operation.imageFromNewContext(scale: base.scale)
}
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let operation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
return operation.imageFromNewContext(scale: base.scale)?.support
}
/// Creates a new circle image with a border.
@ -128,28 +134,26 @@ public extension Support where Base: UIImage {
borderColor: UIColor,
extendSize: Bool = false) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)
}
let radius = CGFloat(min(base.size.width, base.size.height) / 2)
let roundOperation = RoundDrawingOperation(image: image,
imageSize: base.size,
radius: radius)
guard let roundImage = roundOperation.cgImageFromNewContext(scale: base.scale) else {
return nil
}
let borderOperation = BorderDrawingOperation(image: roundImage,
imageSize: base.size,
border: borderWidth,
color: borderColor.cgColor,
radius: radius,
extendSize: extendSize)
return borderOperation.imageFromNewContext(scale: base.scale)?.support
}
/// Creates a resized copy of an image.
@ -164,32 +168,28 @@ public extension Support where Base: UIImage {
contentMode: ResizeMode = .scaleToFill,
cropToImageBounds: Bool = false) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let operation = ResizeDrawingOperation(image: image,
imageSize: base.size,
preferredNewSize: newSize,
resizeMode: contentMode,
cropToImageBounds: cropToImageBounds)
return operation.imageFromNewContext(scale: base.scale)
}
let operation = ResizeDrawingOperation(image: image,
imageSize: base.size,
preferredNewSize: newSize,
resizeMode: contentMode,
cropToImageBounds: cropToImageBounds)
return operation.imageFromNewContext(scale: base.scale)?.support
}
/// Adds an alpha channel if UIImage doesn't already have one.
///
/// - Returns: A copy of the given image, adding an alpha channel if it doesn't already have one.
func applyAlpha() -> Support<UIImage>? {
guard let image = base.cgImage, !image.hasAlpha else {
return Support<UIImage>(base)
return withCGImage { image in
let operation = ImageDrawingOperation(image: image,
newSize: base.size,
opaque: false)
return operation.imageFromNewContext(scale: base.scale)
}
let operation = ImageDrawingOperation(image: image,
newSize: base.size,
opaque: false)
return operation.imageFromNewContext(scale: base.scale)?.support
}
/// Creates a copy of the image with border of the given size added around its edges.
@ -197,29 +197,58 @@ public extension Support where Base: UIImage {
/// - Parameter padding: The padding amount.
/// - Returns: A new padded image or nil if something goes wrong.
func applyPadding(_ padding: CGFloat) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
return withCGImage { image in
let operation = PaddingDrawingOperation(image: image,
imageSize: base.size,
padding: padding)
return operation.imageFromNewContext(scale: base.scale)
}
let operation = PaddingDrawingOperation(image: image,
imageSize: base.size,
padding: padding)
return operation.imageFromNewContext(scale: base.scale)?.support
}
private func flipY() -> Support<UIImage>? {
/// Creates a copy of the image rotated by the given amount of degrees.
///
/// - Parameters:
/// - degrees: The number of degrees.
/// - clockwise: Should rotate image clockwise.
/// - Returns: A new rotated image or nil if something goes wrong.
func rotate(degrees: CGFloat, clockwise: Bool = true) -> Support<UIImage>? {
return withCGImage { image in
let radians = degrees.degreesToRadians()
let operation = RotateDrawingOperation(image: image,
imageSize: base.size,
radians: radians,
clockwise: clockwise)
guard let rotatedImage = operation.imageFromNewContext(scale: base.scale) else {
return nil
}
let flipOperation = rotatedImage.cgImage?.flipYOperation(size: rotatedImage.size)
return flipOperation?.imageFromNewContext(scale: rotatedImage.scale)
}
}
private func withCGImage(_ actionClosure: (CGImage) -> UIImage?) -> Support<UIImage>? {
guard let image = base.cgImage else {
return Support<UIImage>(base)
}
let flipOperation = ImageDrawingOperation(image: image,
newSize: base.size,
origin: .zero,
opaque: false,
flipY: true)
return actionClosure(image)?.support
}
return flipOperation.imageFromNewContext(scale: base.scale)?.support
}
private extension CGImage {
func flipYOperation(size: CGSize) -> ImageDrawingOperation {
return ImageDrawingOperation(image: self,
newSize: size,
origin: .zero,
opaque: false,
flipY: true)
}
}

View File

@ -0,0 +1,52 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public extension SeparatorCell {
/// Configure separator with viewModel
/// - parameter separatorType: type of separators
func configureSeparator(with separatorType: CellSeparatorType) {
switch separatorType {
case .none:
topView.isHidden = true
bottomView.isHidden = true
case .bottom(let configuration):
topView.isHidden = true
bottomView.isHidden = false
updateBottomSeparator(with: configuration)
case .top(let configuration):
topView.isHidden = false
bottomView.isHidden = true
updateTopSeparator(with: configuration)
topView.superview?.setNeedsUpdateConstraints()
case .full(let topConfiguration, let bottomConfiguration):
topView.isHidden = false
bottomView.isHidden = false
updateTopSeparator(with: topConfiguration)
updateBottomSeparator(with: bottomConfiguration)
bottomView.superview?.setNeedsUpdateConstraints()
}
}
}

View File

@ -0,0 +1,51 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public extension SeparatorCell where Self: UITableViewCell {
/// Move separator upward in hierarchy
func bringSeparatorsToFront() {
topView.bringToFront(in: contentView)
bottomView.bringToFront(in: contentView)
}
/// Move separator backward in hierarchy
func sendSeparatorsToBack() {
topView.sendToBack(in: contentView)
bottomView.sendToBack(in: contentView)
}
}
private extension UIView {
func bringToFront(in view: UIView) {
view.bringSubview(toFront: self)
}
func sendToBack(in view: UIView) {
view.sendSubview(toBack: self)
}
}

View File

@ -0,0 +1,36 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
public protocol TotalCountCursorConfiguration: class, ResettableType {
associatedtype ListingType
associatedtype ElementType
typealias ResultTuple = (results: [ElementType], totalCount: Int)
func nextBatchObservable() -> Single<ListingType>
func getResult(from listing: ListingType) -> ResultTuple
}

View File

@ -0,0 +1,27 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public protocol DataSourceProtocol {
associatedtype ResultType
}

View File

@ -0,0 +1,62 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public enum GeneralLoadingState<DS: DataSourceProtocol> {
case initial
case loading
case result(newResult: DS.ResultType, from: DS)
case error(error: Error)
case empty
}
extension GeneralLoadingState: LoadingState {
public typealias DataSourceType = DS
public static var initialState: GeneralLoadingState<DS> {
return .initial
}
public static var emptyState: GeneralLoadingState<DS> {
return .empty
}
public static func loadingState(after: GeneralLoadingState<DS>) -> GeneralLoadingState<DS> {
return .loading
}
public static func resultState(result: DS.ResultType,
from: DS,
after: GeneralLoadingState<DS>) -> GeneralLoadingState<DS> {
return .result(newResult: result, from: from)
}
public static func errorState(error: Error,
after: GeneralLoadingState<DS>) -> GeneralLoadingState<DS> {
return .error(error: error)
}
}

View File

@ -0,0 +1,26 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public final class GeneralLoadingViewModel<T>: LoadingViewModel<GeneralLoadingViewModelConfiguration<T>> {
}

View File

@ -0,0 +1,73 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
public final class GeneralLoadingViewModelConfiguration<T>: LoadingConfiguration {
public typealias DataSourceType = Single<T>
public typealias EmptyResultChecker = (T) -> Bool
public typealias LoadingStateType = GeneralLoadingState<DataSourceType>
private let stateVariable = Variable<LoadingStateType>(.initialState)
private var currentRequestDisposable: Disposable?
public let scheduler = SerialDispatchQueueScheduler(qos: .default)
public let dataSource: DataSourceType
public let emptyResultChecker: EmptyResultChecker
public init(dataSource: DataSourceType, emptyResultChecker: @escaping EmptyResultChecker) {
self.dataSource = dataSource
self.emptyResultChecker = emptyResultChecker
}
public var loadingObservable: DataSourceType {
return dataSource
}
public func isEmptyResult(result: T) -> Bool {
return emptyResultChecker(result)
}
public var stateDriver: Driver<LoadingStateType> {
return stateVariable.asDriver()
}
public var state: GeneralLoadingState<DataSourceType> {
get {
return stateVariable.value
}
set {
stateVariable.value = newValue
}
}
public func resetDataSource() {
currentRequestDisposable?.dispose()
}
public func storeCurrentRequestDisposable(_ disposable: Disposable) {
currentRequestDisposable = disposable
}
}

View File

@ -0,0 +1,47 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
public protocol LoadingConfiguration: class {
associatedtype DataSourceType: DataSourceProtocol
associatedtype LoadingStateType: LoadingState
var dataSource: DataSourceType { get }
var scheduler: SerialDispatchQueueScheduler { get }
var loadingObservable: Single<DataSourceType.ResultType> { get }
var stateDriver: Driver<LoadingStateType> { get }
var state: LoadingStateType { get set }
func resetDataSource()
func storeCurrentRequestDisposable(_ disposable: Disposable)
func isEmptyResult(result: DataSourceType.ResultType) -> Bool
}

View File

@ -0,0 +1,38 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxCocoa
public protocol LoadingProtocol {
associatedtype DataSourceType: DataSourceProtocol
associatedtype LoadingStateType: LoadingState
associatedtype LoadingConfigurationType: LoadingConfiguration
var stateDriver: Driver<LoadingStateType> { get }
init(configuration: LoadingConfigurationType)
func reload()
func retry()
}

View File

@ -0,0 +1,34 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public protocol LoadingState {
associatedtype DataSourceType: DataSourceProtocol
static var initialState: Self { get }
static var emptyState: Self { get }
static func loadingState(after: Self) -> Self
static func resultState(result: DataSourceType.ResultType, from: DataSourceType, after: Self) -> Self
static func errorState(error: Error, after: Self) -> Self
}

View File

@ -0,0 +1,90 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
public class LoadingViewModel<VMC: LoadingConfiguration>: LoadingProtocol
where VMC.DataSourceType == VMC.LoadingStateType.DataSourceType {
public typealias DataSourceType = VMC.DataSourceType
public typealias LoadingStateType = VMC.LoadingStateType
public typealias LoadingConfigurationType = VMC
private let configuration: VMC
public var stateDriver: Driver<LoadingStateType> {
return configuration.stateDriver
}
public required init(configuration: VMC) {
self.configuration = configuration
}
public func reload() {
load(isRetry: false)
}
public func retry() {
load(isRetry: true)
}
private func load(isRetry: Bool) {
configuration.resetDataSource()
if isRetry {
configuration.state = .initialState
}
configuration.state = .loadingState(after: configuration.state)
let currentRequestDisposable = configuration.loadingObservable
.observeOn(configuration.scheduler)
.subscribe(onSuccess: { [weak self] result in
self?.onGot(result: result)
}, onError: { [weak self] error in
self?.onGot(error: error)
})
configuration.storeCurrentRequestDisposable(currentRequestDisposable)
}
func onGot(error: Error) {
configuration.state = .errorState(error: error,
after: configuration.state)
}
func onGot(result: DataSourceType.ResultType, from dataSource: DataSourceType) {
if configuration.isEmptyResult(result: result) {
configuration.state = .emptyState
} else {
configuration.state = .resultState(result: result,
from: dataSource,
after: configuration.state)
}
}
private func onGot(result: DataSourceType.ResultType) {
onGot(result: result, from: configuration.dataSource)
}
}

View File

@ -0,0 +1,64 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
public indirect enum PaginationLoadingState<DS: DataSourceProtocol> {
case initial
case loading(after: PaginationLoadingState)
case loadingMore(after: PaginationLoadingState)
case results(newItems: DS.ResultType, from: DS, after: PaginationLoadingState)
case error(error: Error, after: PaginationLoadingState)
case empty
case exhausted
}
extension PaginationLoadingState: LoadingState {
public typealias DataSourceType = DS
public static var initialState: PaginationLoadingState<DS> {
return .initial
}
public static var emptyState: PaginationLoadingState<DS> {
return .empty
}
public static func loadingState(after: PaginationLoadingState<DS>) -> PaginationLoadingState<DS> {
return .loading(after: after)
}
public static func resultState(result: DS.ResultType,
from: DataSourceType,
after: PaginationLoadingState<DS>) -> PaginationLoadingState<DS> {
return .results(newItems: result, from: from, after: after)
}
public static func errorState(error: Error,
after: PaginationLoadingState<DS>) -> PaginationLoadingState<DS> {
return .error(error: error, after: after)
}
}

View File

@ -0,0 +1,108 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
public typealias ResettableCursorType = CursorType & ResettableType
public typealias ResettableCursorDataSource = ResettableCursorType & DataSourceProtocol
public final class PaginationLoadingViewModel<C: ResettableCursorDataSource>:
LoadingViewModel<PaginationLoadingViewModelConfiguration<C>>
where C.ResultType == [C.Element] {
private enum LoadType {
case reload
case retry
case next
}
private let configuration: PaginationLoadingViewModelConfiguration<C>
public required init(configuration: PaginationLoadingViewModelConfiguration<C>) {
self.configuration = configuration
super.init(configuration: configuration)
}
override public func reload() {
load(.reload)
}
override public func retry() {
load(.retry)
}
public func loadMore() {
load(.next)
}
private func load(_ loadType: LoadType) {
switch loadType {
case .reload, .retry:
configuration.resetDataSource()
configuration.state = .loading(after: configuration.state)
case .next:
if case .exhausted = configuration.state {
fatalError("You shouldn't call load(.next) after got .exhausted state!")
}
configuration.state = .loadingMore(after: configuration.state)
}
let currentDataSource = configuration.dataSource
let currentRequestDisposable = configuration.loadingObservable
.subscribeOn(configuration.scheduler)
.subscribe(onSuccess: { [weak self] newItems in
self?.onGot(result: newItems, from: currentDataSource)
}, onError: { [weak self] error in
self?.onGot(error: error)
})
configuration.storeCurrentRequestDisposable(currentRequestDisposable)
}
override func onGot(result: DataSourceType.ResultType, from dataSource: DataSourceType) {
super.onGot(result: result, from: dataSource)
if !configuration.isEmptyResult(result: result) && dataSource.exhausted {
configuration.state = .exhausted
}
}
override func onGot(error: Error) {
if case .exhausted? = error as? CursorError, case .loading(let after) = configuration.state {
switch after {
case .initial, .empty: // cursor exhausted after creation
configuration.state = .empty
default:
super.onGot(error: error)
}
} else {
super.onGot(error: error)
}
}
}

View File

@ -0,0 +1,72 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import RxSwift
import RxCocoa
public final class PaginationLoadingViewModelConfiguration<C: ResettableCursorDataSource>: LoadingConfiguration
where C.ResultType == [C.Element] {
public typealias DataSourceType = C
public typealias LoadingStateType = PaginationLoadingState<DataSourceType>
public private(set) var dataSource: DataSourceType
private let stateVariable = Variable<LoadingStateType>(.initialState)
public let scheduler = SerialDispatchQueueScheduler(qos: .default)
private var currentRequestDisposable: Disposable?
public init(dataSource: DataSourceType) {
self.dataSource = dataSource
}
public var loadingObservable: Single<C.ResultType> {
return dataSource.loadNextBatch()
}
public func isEmptyResult(result: C.ResultType) -> Bool {
return result.isEmpty
}
public var stateDriver: Driver<LoadingStateType> {
return stateVariable.asDriver()
}
public var state: LoadingStateType {
get {
return stateVariable.value
}
set {
stateVariable.value = newValue
}
}
public func resetDataSource() {
currentRequestDisposable?.dispose()
dataSource = dataSource.reset()
}
public func storeCurrentRequestDisposable(_ disposable: Disposable) {
currentRequestDisposable = disposable
}
}

View File

@ -0,0 +1,43 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public protocol ScrollViewHolder {
var scrollView: UIScrollView { get }
}
public protocol BackgroundViewHolder {
var backgroundView: UIView? { get set }
}
public protocol FooterViewHolder {
var footerView: UIView? { get set }
}
public typealias PaginationWrappable = ScrollViewHolder & BackgroundViewHolder & FooterViewHolder

View File

@ -0,0 +1,85 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
/// PaginationWrapper delegate used for pagination results handling and
/// customization of bound states (loading, empty, error, etc.).
public protocol PaginationWrapperDelegate: class {
associatedtype DataSourceType: DataSourceProtocol
/// Delegate method that handles loading new chunk of data.
///
/// - Parameters:
/// - newItems: New items.
/// - cursor: Cursor used to load items
func paginationWrapper(didLoad newItems: DataSourceType.ResultType,
using dataSource: DataSourceType)
/// Delegate method that handles reloading or initial loading of data.
///
/// - Parameters:
/// - allItems: New items.
/// - cursor: Cursor used to load items
func paginationWrapper(didReload allItems: DataSourceType.ResultType,
using dataSource: DataSourceType)
/// Delegate method that returns placeholder view for empty state.
///
/// - Returns: Configured instace of UIView.
func emptyPlaceholder() -> UIView
/// Delegate method that returns placeholder view for error state.
///
/// - Parameters:
/// - error: Error that occured due data loading.
/// - Returns: Configured instace of UIView.
func errorPlaceholder(for error: Error) -> UIView
/// Delegate method that returns loading idicator for initial loading state.
/// This indicator will appear at center of the placeholders container.
///
/// - Returns: Configured instace of AnyLoadingIndicator.
func initialLoadingIndicator() -> AnyLoadingIndicator
/// Delegate method that returns loading idicator for initial loading state.
///
/// - Returns: Configured instace of AnyLoadingIndicator.
func loadingMoreIndicator() -> AnyLoadingIndicator
/// Delegate method that returns instance of UIButton for "retry load more" action.
///
/// - Returns: Configured instace of AnyLoadingIndicator.
func retryLoadMoreButton() -> UIButton
/// Delegate method that returns preferred height for "retry load more" button.
///
/// - Returns: Preferred height of "retry load more" button.
func retryLoadMoreButtonHeight() -> CGFloat
func retryLoadMoreButtonIsAboutToShow()
func retryLoadMoreButtonIsAboutToHide()
// Delegate method, used to clear view if placeholder is shown.
func clearView()
}

View File

@ -0,0 +1,43 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public protocol TopSeparatorCell {
var topView: UIView! { get }
func updateTopSeparator(with configuration: SeparatorConfiguration)
}
public protocol BottomSeparatorCell {
var bottomView: UIView! { get }
func updateBottomSeparator(with configuration: SeparatorConfiguration)
}
public protocol SeparatorCell: TopSeparatorCell, BottomSeparatorCell {
//
}

View File

@ -0,0 +1,60 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import CoreGraphics
struct RotateDrawingOperation: DrawingOperation {
private let image: CGImage
private let imageSize: CGSize
private let radians: CGFloat
private let translateRect: CGRect
public init(image: CGImage, imageSize: CGSize, radians: CGFloat, clockwise: Bool = true) {
self.image = image
self.imageSize = imageSize
self.radians = clockwise ? radians : -radians
let transform = CGAffineTransform(rotationAngle: radians)
let imageRect = CGRect(origin: .zero, size: imageSize)
translateRect = CGRect(origin: .zero, size: imageRect.applying(transform).size)
}
public var contextSize: CGContextSize {
return translateRect.size.ceiledContextSize
}
public func apply(in context: CGContext) {
context.translateBy(x: translateRect.midX, y: translateRect.midY)
context.rotate(by: radians)
context.scaleBy(x: 1.0, y: -1.0)
let imageLocation = CGRect(origin: CGPoint(x: -imageSize.width / 2, y: -imageSize.height / 2),
size: imageSize)
context.draw(image, in: imageLocation)
}
}

View File

@ -0,0 +1,70 @@
//
// Copyright (c) 2017 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the Software), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
public struct AnyPaginationWrappableView: BackgroundViewHolder, FooterViewHolder {
private typealias ViewSetter = (UIView?) -> Void
public var footerView: UIView? {
get {
return footerViewBacking
}
set {
footerViewSetter(newValue)
}
}
public var backgroundView: UIView? {
get {
return backgroundViewBacking
}
set {
backgroundViewSetter(newValue)
}
}
let scrollView: UIScrollView
private let backgroundViewBacking: UIView?
private let footerViewBacking: UIView?
private let backgroundViewSetter: ViewSetter
private let footerViewSetter: ViewSetter
public init<View>(view: View) where View: PaginationWrappable {
self.scrollView = view.scrollView
self.backgroundViewBacking = view.backgroundView
self.footerViewBacking = view.footerView
var localView = view
self.backgroundViewSetter = {
localView.backgroundView = $0
}
self.footerViewSetter = {
localView.footerView = $0
}
}
}