Compare commits
31 Commits
master
...
feature/ge
| Author | SHA1 | Date |
|---|---|---|
|
|
de0d556bae | |
|
|
7ab28068ad | |
|
|
38eb97d60f | |
|
|
99ef62dfea | |
|
|
b5f36019ae | |
|
|
2c59c0ff90 | |
|
|
bd5fc5bfb1 | |
|
|
0677931dd3 | |
|
|
85b1a119e2 | |
|
|
b3f963a524 | |
|
|
91b6cc8f3c | |
|
|
149467cd4d | |
|
|
5c66ad9c69 | |
|
|
5f001c75f0 | |
|
|
f685b8555d | |
|
|
8b17fd5d17 | |
|
|
e76f557d2e | |
|
|
9a47fff654 | |
|
|
b0e3463581 | |
|
|
3cdae4b398 | |
|
|
5a6a8e77a2 | |
|
|
a9e3ded0c2 | |
|
|
dd170e0873 | |
|
|
ba94c670b3 | |
|
|
03cc92553c | |
|
|
334105c7af | |
|
|
54b3b22d72 | |
|
|
80c416f247 | |
|
|
cf6f8e9268 | |
|
|
02dfc709c3 | |
|
|
d33a865b3e |
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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() {
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public extension UIColor {
|
|||
hexStr = hexString
|
||||
}
|
||||
|
||||
let charactersCount = hexStr.characters.count
|
||||
let charactersCount = hexStr.count
|
||||
|
||||
switch charactersCount {
|
||||
case 3, 4:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>> {
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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 {
|
||||
//
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue