From 56535b58352928e799930eec5a86f2bc3e711c8c Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Thu, 4 May 2017 13:42:24 +0300 Subject: [PATCH 1/6] Single load cursor and use RxSwift.Single --- LeadKit/LeadKit.xcodeproj/project.pbxproj | 12 +++- .../Classes/Cursors/FixedPageCursor.swift | 28 ++++---- .../Sources/Classes/Cursors/MapCursor.swift | 2 +- .../Classes/Cursors/SingleLoadCursor.swift | 68 +++++++++++++++++++ .../Classes/Cursors/StaticCursor.swift | 22 +++--- .../PaginationTableViewWrapper.swift | 4 +- .../Pagination/PaginationViewModel.swift | 2 +- LeadKit/Sources/Protocols/CursorType.swift | 2 +- 8 files changed, 107 insertions(+), 33 deletions(-) create mode 100644 LeadKit/Sources/Classes/Cursors/SingleLoadCursor.swift diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index 4ccfe7a5..b2637d47 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -355,6 +355,10 @@ 67CDEE421EB3AD1C00895905 /* NetworkService+RxLoadImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CDEE411EB3AD1C00895905 /* NetworkService+RxLoadImage.swift */; }; 67CDEE431EB3AD1C00895905 /* NetworkService+RxLoadImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CDEE411EB3AD1C00895905 /* NetworkService+RxLoadImage.swift */; }; 67CDEE441EB3AD1C00895905 /* NetworkService+RxLoadImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CDEE411EB3AD1C00895905 /* NetworkService+RxLoadImage.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 */; }; BA6C6DB45950382041948FC5 /* Pods_LeadKit_LeadKit_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFE9323150A9760008093F73 /* Pods_LeadKit_LeadKit_iOS.framework */; }; D6EE55093E404DEA62B03DDF /* Pods_LeadKit_LeadKit_watchOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8590CA7831555C295C5DC572 /* Pods_LeadKit_LeadKit_watchOS.framework */; }; DEE25FE98D40ED1C168F384A /* Pods_LeadKit_LeadKit_iOS_Extensions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 887F99C5326BD220C2811BD6 /* Pods_LeadKit_LeadKit_iOS_Extensions.framework */; }; @@ -513,6 +517,7 @@ 67952DDC1EB3280900B3BA1A /* Info-iOS-Extensions.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS-Extensions.plist"; sourceTree = ""; }; 67952DDE1EB3285A00B3BA1A /* Info-iOS-Extensions.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS-Extensions.plist"; sourceTree = ""; }; 67CDEE411EB3AD1C00895905 /* NetworkService+RxLoadImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NetworkService+RxLoadImage.swift"; sourceTree = ""; }; + 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLoadCursor.swift; sourceTree = ""; }; 78405D3B3D3C3E17456877FF /* Pods_LeadKit_iOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit_iOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8590CA7831555C295C5DC572 /* Pods_LeadKit_LeadKit_watchOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit_LeadKit_watchOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 887F99C5326BD220C2811BD6 /* Pods_LeadKit_LeadKit_iOS_Extensions.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit_LeadKit_iOS_Extensions.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -618,6 +623,7 @@ 671461C81EB3396E00EAB194 /* FixedPageCursor.swift */, 671461C91EB3396E00EAB194 /* MapCursor.swift */, 671461CA1EB3396E00EAB194 /* StaticCursor.swift */, + 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */, ); path = Cursors; sourceTree = ""; @@ -945,6 +951,7 @@ 671462221EB3396E00EAB194 /* Protocols */ = { isa = PBXGroup; children = ( + 671463A11EB33FF600EAB194 /* Animatable.swift */, 671462231EB3396E00EAB194 /* BaseViewModel.swift */, 671462241EB3396E00EAB194 /* ConfigurableController.swift */, 671462251EB3396E00EAB194 /* CursorType.swift */, @@ -963,7 +970,6 @@ 671462321EB3396E00EAB194 /* ViewHeightProtocol.swift */, 671462331EB3396E00EAB194 /* ViewModelProtocol.swift */, 671462341EB3396E00EAB194 /* XibNameProtocol.swift */, - 671463A11EB33FF600EAB194 /* Animatable.swift */, ); path = Protocols; sourceTree = ""; @@ -1909,6 +1915,7 @@ 671462B81EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */, 671463741EB3396E00EAB194 /* BorderDrawingOperation.swift in Sources */, 6714633C1EB3396E00EAB194 /* LoadingIndicator.swift in Sources */, + 67E6C2351EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */, 671463181EB3396E00EAB194 /* UIWindow+Extensions.swift in Sources */, 671462541EB3396E00EAB194 /* App.swift in Sources */, 6714635C1EB3396E00EAB194 /* StoryboardProtocol.swift in Sources */, @@ -1951,6 +1958,7 @@ 671462CE1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */, 671462821EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */, 671462C61EB3396E00EAB194 /* String+Extensions.swift in Sources */, + 67E6C2371EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */, 671463561EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */, 671462AA1EB3396E00EAB194 /* ImmutableMappable+ObservableMappable.swift in Sources */, 671463621EB3396E00EAB194 /* SupportProtocol.swift in Sources */, @@ -2090,6 +2098,7 @@ 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 /* ViewModelProtocol.swift in Sources */, @@ -2120,6 +2129,7 @@ 671462CD1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */, 671462811EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */, 671462C51EB3396E00EAB194 /* String+Extensions.swift in Sources */, + 67E6C2361EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */, 671463551EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */, 671462A91EB3396E00EAB194 /* ImmutableMappable+ObservableMappable.swift in Sources */, 671463611EB3396E00EAB194 /* SupportProtocol.swift in Sources */, diff --git a/LeadKit/Sources/Classes/Cursors/FixedPageCursor.swift b/LeadKit/Sources/Classes/Cursors/FixedPageCursor.swift index b7e2f407..2d04a61e 100644 --- a/LeadKit/Sources/Classes/Cursors/FixedPageCursor.swift +++ b/LeadKit/Sources/Classes/Cursors/FixedPageCursor.swift @@ -49,25 +49,23 @@ public class FixedPageCursor: CursorType { return cursor[index] } - public func loadNextBatch() -> Observable<[Cursor.Element]> { - return Observable.deferred { - if self.exhausted { - throw CursorError.exhausted - } + public func loadNextBatch() -> Single<[Cursor.Element]> { + if exhausted { + return .error(CursorError.exhausted) + } - let restOfLoaded = self.cursor.count - self.count + let restOfLoaded = cursor.count - count - if restOfLoaded >= self.pageSize || self.cursor.exhausted { - let startIndex = self.count - self.count += min(restOfLoaded, self.pageSize) + if restOfLoaded >= pageSize || cursor.exhausted { + let startIndex = count + count += min(restOfLoaded, pageSize) - return .just(self.cursor[startIndex..: CursorType { return elements[index] } - public func loadNextBatch() -> Observable<[T]> { + public func loadNextBatch() -> Single<[T]> { return cursor.loadNextBatch().map { newItems in let transformedNewItems = newItems.flatMap(self.transform) self.elements += transformedNewItems diff --git a/LeadKit/Sources/Classes/Cursors/SingleLoadCursor.swift b/LeadKit/Sources/Classes/Cursors/SingleLoadCursor.swift new file mode 100644 index 00000000..595392f9 --- /dev/null +++ b/LeadKit/Sources/Classes/Cursors/SingleLoadCursor.swift @@ -0,0 +1,68 @@ +// +// 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 + +/// Cursor implementation for single load operation +public class SingleLoadCursor: ResettableCursorType { + + private let loadingObservable: Single<[Element]> + + private var content: [Element] = [] + + /// Initializer for array content type + /// + /// - Parameter loadingObservable: Single observable with element of [Element] type + public init(loadingObservable: Single<[Element]>) { + self.loadingObservable = loadingObservable + } + + public required init(initialFrom other: SingleLoadCursor) { + self.loadingObservable = other.loadingObservable + } + + public private(set) var exhausted = false + + public var count: Int { + return content.count + } + + public subscript(index: Int) -> Element { + return content[index] + } + + public func loadNextBatch() -> Single<[Element]> { + if exhausted { + return .error(CursorError.exhausted) + } + + return loadingObservable.do(onNext: { [weak self] newItems in + self?.onGot(result: newItems) + }) + } + + private func onGot(result: [Element]) { + content = result + exhausted = true + } + +} diff --git a/LeadKit/Sources/Classes/Cursors/StaticCursor.swift b/LeadKit/Sources/Classes/Cursors/StaticCursor.swift index 9bf9eac0..60289d52 100644 --- a/LeadKit/Sources/Classes/Cursors/StaticCursor.swift +++ b/LeadKit/Sources/Classes/Cursors/StaticCursor.swift @@ -29,7 +29,7 @@ public class StaticCursor: ResettableCursorType { /// Initializer for array content type /// - /// - Parameter content: array with elements of Elemet type + /// - Parameter content: array with elements of Element type public init(content: [Element]) { self.content = content } @@ -46,18 +46,16 @@ public class StaticCursor: ResettableCursorType { return content[index] } - public func loadNextBatch() -> Observable<[Element]> { - return Observable.deferred { - if self.exhausted { - throw CursorError.exhausted - } - - self.count = self.content.count - - self.exhausted = true - - return .just(self.content) + public func loadNextBatch() -> Single<[Element]> { + if exhausted { + return .error(CursorError.exhausted) } + + count = content.count + + exhausted = true + + return .just(content) } } diff --git a/LeadKit/Sources/Classes/Pagination/PaginationTableViewWrapper.swift b/LeadKit/Sources/Classes/Pagination/PaginationTableViewWrapper.swift index 7a55ab28..6c6b8d72 100644 --- a/LeadKit/Sources/Classes/Pagination/PaginationTableViewWrapper.swift +++ b/LeadKit/Sources/Classes/Pagination/PaginationTableViewWrapper.swift @@ -236,7 +236,7 @@ where Delegate.Cursor == Cursor { retryButton.frame = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: retryButtonHeigth) retryButton.rx.controlEvent(.touchUpInside) - .bindNext { [weak self] in + .bind { [weak self] in self?.paginationViewModel.load(.next) } .addDisposableTo(disposeBag) @@ -274,7 +274,7 @@ where Delegate.Cursor == Cursor { private func createRefreshControl() { let refreshControl = UIRefreshControl() refreshControl.rx.controlEvent(.valueChanged) - .bindNext { [weak self] in + .bind { [weak self] in self?.reload() } .addDisposableTo(disposeBag) diff --git a/LeadKit/Sources/Classes/Pagination/PaginationViewModel.swift b/LeadKit/Sources/Classes/Pagination/PaginationViewModel.swift index 3efb4ea5..59d3fa03 100644 --- a/LeadKit/Sources/Classes/Pagination/PaginationViewModel.swift +++ b/LeadKit/Sources/Classes/Pagination/PaginationViewModel.swift @@ -110,7 +110,7 @@ public final class PaginationViewModel { currentRequest = currentCursor.loadNextBatch() .subscribeOn(internalScheduler) - .subscribe(onNext: { [weak self] newItems in + .subscribe(onSuccess: { [weak self] newItems in self?.onGot(newItems: newItems, using: currentCursor) }, onError: { [weak self] error in self?.onGot(error: error) diff --git a/LeadKit/Sources/Protocols/CursorType.swift b/LeadKit/Sources/Protocols/CursorType.swift index 3de50906..ce9388de 100644 --- a/LeadKit/Sources/Protocols/CursorType.swift +++ b/LeadKit/Sources/Protocols/CursorType.swift @@ -38,6 +38,6 @@ public protocol CursorType { /// Loads next batch of results /// /// - Returns: Observable of LoadResultType - func loadNextBatch() -> Observable<[Element]> + func loadNextBatch() -> Single<[Element]> } From 3d6af86bdc7f648e8965032f0472d22509e5a0d9 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Fri, 5 May 2017 15:32:53 +0300 Subject: [PATCH 2/6] Easily create spinner (AnyLoadingIndicator) from UIImage --- LeadKit.podspec | 4 +- LeadKit/LeadKit.xcodeproj/project.pbxproj | 32 ++++ .../Sources/Classes/Views/SpinnerView.swift | 147 ++++++++++++++++++ .../CABasicAnimation+Rotation.swift | 43 +++++ .../Extensions/UIImage/UIImage+Spinner.swift | 46 ++++++ .../Extensions/UIView/UIView+Rotation.swift | 15 +- 6 files changed, 276 insertions(+), 11 deletions(-) create mode 100644 LeadKit/Sources/Classes/Views/SpinnerView.swift create mode 100644 LeadKit/Sources/Extensions/CABasicAnimation/CABasicAnimation+Rotation.swift create mode 100644 LeadKit/Sources/Extensions/UIImage/UIImage+Spinner.swift diff --git a/LeadKit.podspec b/LeadKit.podspec index d6c93c04..c7cf06de 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "0.5.0" + s.version = "0.5.1" s.summary = "iOS framework with a bunch of tools for rapid development" s.homepage = "https://github.com/TouchInstinct/LeadKit" s.license = "Apache License, Version 2.0" @@ -42,6 +42,8 @@ Pod::Spec.new do |s| ss.watchos.exclude_files = [ "LeadKit/Sources/Classes/Pagination/PaginationTableViewWrapper.swift", "LeadKit/Sources/Classes/Views/XibView.swift", + "LeadKit/Sources/Classes/Views/SpinnerView.swift", + "LeadKit/Sources/Extensions/CABasicAnimation/*", "LeadKit/Sources/Extensions/CGFloat/CGFloat+Pixels.swift", "LeadKit/Sources/Extensions/NetworkService/NetworkService+ActivityIndicator.swift", "LeadKit/Sources/Extensions/NetworkService/NetworkService+RxLoadImage.swift", diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index b2637d47..de65ca2d 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -10,6 +10,9 @@ 2D6A0E6105F4A9BF22BF4BB1 /* Pods_LeadKit_iOS_ExtensionsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C88ED8C9373F85C06697849 /* Pods_LeadKit_iOS_ExtensionsTests.framework */; }; 2D96F18874B9519F5AD74003 /* Pods_LeadKit_LeadKit_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F8D0002B21A4F31981F1ED /* Pods_LeadKit_LeadKit_tvOS.framework */; }; 3614FEACB9E8313C87F7C393 /* Pods_LeadKit_tvOSTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DB1CCAB1EAAACD3AC42C795 /* Pods_LeadKit_tvOSTests.framework */; }; + 67051ADB1EBC7C36008EADC0 /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67051ADA1EBC7C36008EADC0 /* SpinnerView.swift */; }; + 67051ADC1EBC7C36008EADC0 /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67051ADA1EBC7C36008EADC0 /* SpinnerView.swift */; }; + 67051ADD1EBC7C36008EADC0 /* SpinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67051ADA1EBC7C36008EADC0 /* SpinnerView.swift */; }; 671462441EB3396E00EAB194 /* Mutex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461C61EB3396E00EAB194 /* Mutex.swift */; }; 671462451EB3396E00EAB194 /* Mutex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461C61EB3396E00EAB194 /* Mutex.swift */; }; 671462461EB3396E00EAB194 /* Mutex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461C61EB3396E00EAB194 /* Mutex.swift */; }; @@ -351,6 +354,12 @@ 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 */; }; 67952DDD1EB3281300B3BA1A /* LeadKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 67186B201EB247A200CFAFFB /* LeadKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 67A1FF8F1EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A1FF8E1EBCA09B00D6C89F /* UIImage+Spinner.swift */; }; + 67A1FF901EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A1FF8E1EBCA09B00D6C89F /* UIImage+Spinner.swift */; }; + 67A1FF911EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A1FF8E1EBCA09B00D6C89F /* UIImage+Spinner.swift */; }; + 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 */; }; 67CDEE401EB369BF00895905 /* ConfigurableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462241EB3396E00EAB194 /* ConfigurableController.swift */; }; 67CDEE421EB3AD1C00895905 /* NetworkService+RxLoadImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CDEE411EB3AD1C00895905 /* NetworkService+RxLoadImage.swift */; }; 67CDEE431EB3AD1C00895905 /* NetworkService+RxLoadImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CDEE411EB3AD1C00895905 /* NetworkService+RxLoadImage.swift */; }; @@ -404,6 +413,7 @@ 563DDE9CACD515FDCB5A2FFF /* Pods-LeadKit-LeadKit tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeadKit-LeadKit tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-LeadKit-LeadKit tvOS/Pods-LeadKit-LeadKit tvOS.release.xcconfig"; sourceTree = ""; }; 56C11305E2B44404FFFD12AA /* Pods_LeadKit_watchOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit_watchOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 65B19DB0B65A1EE1A1E2C907 /* Pods-LeadKit tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LeadKit tvOSTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LeadKit tvOSTests/Pods-LeadKit tvOSTests.debug.xcconfig"; sourceTree = ""; }; + 67051ADA1EBC7C36008EADC0 /* SpinnerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpinnerView.swift; sourceTree = ""; }; 671461C61EB3396E00EAB194 /* Mutex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mutex.swift; sourceTree = ""; }; 671461C81EB3396E00EAB194 /* FixedPageCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FixedPageCursor.swift; sourceTree = ""; }; 671461C91EB3396E00EAB194 /* MapCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapCursor.swift; sourceTree = ""; }; @@ -516,6 +526,8 @@ 67952DCD1EB327B400B3BA1A /* LeadKit iOS ExtensionsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LeadKit iOS ExtensionsTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 67952DDC1EB3280900B3BA1A /* Info-iOS-Extensions.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS-Extensions.plist"; sourceTree = ""; }; 67952DDE1EB3285A00B3BA1A /* Info-iOS-Extensions.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-iOS-Extensions.plist"; sourceTree = ""; }; + 67A1FF8E1EBCA09B00D6C89F /* UIImage+Spinner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Spinner.swift"; sourceTree = ""; }; + 67A1FF931EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CABasicAnimation+Rotation.swift"; sourceTree = ""; }; 67CDEE411EB3AD1C00895905 /* NetworkService+RxLoadImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NetworkService+RxLoadImage.swift"; sourceTree = ""; }; 67E6C2341EBB32F5007842A6 /* SingleLoadCursor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLoadCursor.swift; sourceTree = ""; }; 78405D3B3D3C3E17456877FF /* Pods_LeadKit_iOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit_iOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -659,6 +671,7 @@ isa = PBXGroup; children = ( 671461D51EB3396E00EAB194 /* XibView.swift */, + 67051ADA1EBC7C36008EADC0 /* SpinnerView.swift */, ); path = Views; sourceTree = ""; @@ -677,6 +690,7 @@ isa = PBXGroup; children = ( 671461DB1EB3396E00EAB194 /* Alamofire */, + 67A1FF921EBCA64A00D6C89F /* CABasicAnimation */, 671461DE1EB3396E00EAB194 /* CGContext */, 671461E01EB3396E00EAB194 /* CGFloat */, 671461E21EB3396E00EAB194 /* CGImage */, @@ -888,6 +902,7 @@ children = ( 6714620D1EB3396E00EAB194 /* UIImage+Extensions.swift */, 6714620E1EB3396E00EAB194 /* UIImage+SupportExtensions.swift */, + 67A1FF8E1EBCA09B00D6C89F /* UIImage+Spinner.swift */, ); path = UIImage; sourceTree = ""; @@ -1084,6 +1099,14 @@ path = Tests; sourceTree = ""; }; + 67A1FF921EBCA64A00D6C89F /* CABasicAnimation */ = { + isa = PBXGroup; + children = ( + 67A1FF931EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift */, + ); + path = CABasicAnimation; + sourceTree = ""; + }; 78CFEE201C5C456B00F50370 = { isa = PBXGroup; children = ( @@ -1861,6 +1884,7 @@ 671462441EB3396E00EAB194 /* Mutex.swift in Sources */, 671463401EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */, 671462641EB3396E00EAB194 /* PaginationViewModel.swift in Sources */, + 67A1FF8F1EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */, 671462901EB3396E00EAB194 /* CGImage+Crop.swift in Sources */, 671462FC1EB3396E00EAB194 /* UIView+DefaultXibName.swift in Sources */, 671463841EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, @@ -1869,7 +1893,9 @@ 6714630C1EB3396E00EAB194 /* UIViewController+DefaultStoryboardIdentifier.swift in Sources */, 671462981EB3396E00EAB194 /* CGSize+Resize.swift in Sources */, 671462F81EB3396E00EAB194 /* UIView+DefaultReuseIdentifier.swift in Sources */, + 67051ADB1EBC7C36008EADC0 /* SpinnerView.swift in Sources */, 671463581EB3396E00EAB194 /* StoryboardIdentifierProtocol.swift in Sources */, + 67A1FF941EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */, 671463301EB3396E00EAB194 /* CursorType.swift in Sources */, 6714624C1EB3396E00EAB194 /* MapCursor.swift in Sources */, 671463241EB3396E00EAB194 /* Any+TypeName.swift in Sources */, @@ -2067,6 +2093,7 @@ 6714636F1EB3396E00EAB194 /* XibNameProtocol.swift in Sources */, 671462A71EB3396E00EAB194 /* IndexPath+ImmutableIndexPath.swift in Sources */, 671462BF1EB3396E00EAB194 /* StoryboardProtocol+DefaultBundle.swift in Sources */, + 67A1FF971EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */, 671462A31EB3396E00EAB194 /* Double+Rounding.swift in Sources */, 6714625F1EB3396E00EAB194 /* LogFormatter.swift in Sources */, 6714630B1EB3396E00EAB194 /* UIView+Rotation.swift in Sources */, @@ -2082,6 +2109,7 @@ 6714628B1EB3396E00EAB194 /* CGFloat+Pixels.swift in Sources */, 671462971EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */, 671463671EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */, + 67A1FF911EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */, 6714624B1EB3396E00EAB194 /* FixedPageCursor.swift in Sources */, 671462CB1EB3396E00EAB194 /* String+Localization.swift in Sources */, 671462BB1EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */, @@ -2095,6 +2123,7 @@ 6714633B1EB3396E00EAB194 /* EstimatedViewHeightProtocol.swift in Sources */, 6714632F1EB3396E00EAB194 /* ConfigurableController.swift in Sources */, 6714628F1EB3396E00EAB194 /* CGImage+Alpha.swift in Sources */, + 67051ADD1EBC7C36008EADC0 /* SpinnerView.swift in Sources */, 671462771EB3396E00EAB194 /* LeadKitError.swift in Sources */, 671462DB1EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */, 6714638F1EB3396E00EAB194 /* SolidFillDrawingOperation.swift in Sources */, @@ -2127,6 +2156,7 @@ 671463491EB3396E00EAB194 /* ResettableType.swift in Sources */, 671462E51EB3396E00EAB194 /* UIColor+Hex.swift in Sources */, 671462CD1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */, + 67A1FF951EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */, 671462811EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */, 671462C51EB3396E00EAB194 /* String+Extensions.swift in Sources */, 67E6C2361EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */, @@ -2144,6 +2174,7 @@ 671463411EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */, 671462651EB3396E00EAB194 /* PaginationViewModel.swift in Sources */, 671462911EB3396E00EAB194 /* CGImage+Crop.swift in Sources */, + 67051ADC1EBC7C36008EADC0 /* SpinnerView.swift in Sources */, 671462FD1EB3396E00EAB194 /* UIView+DefaultXibName.swift in Sources */, 671463851EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, 671462D11EB3396E00EAB194 /* UIScrollView+Support.swift in Sources */, @@ -2195,6 +2226,7 @@ 671463191EB3396E00EAB194 /* UIWindow+Extensions.swift in Sources */, 671462551EB3396E00EAB194 /* App.swift in Sources */, 6714635D1EB3396E00EAB194 /* StoryboardProtocol.swift in Sources */, + 67A1FF901EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */, 671462F51EB3396E00EAB194 /* UIStoryboard+InstantiateViewController.swift in Sources */, 671463791EB3396E00EAB194 /* CALayerDrawingOperation.swift in Sources */, 671463391EB3396E00EAB194 /* EstimatedViewHeightProtocol.swift in Sources */, diff --git a/LeadKit/Sources/Classes/Views/SpinnerView.swift b/LeadKit/Sources/Classes/Views/SpinnerView.swift new file mode 100644 index 00000000..9abf695f --- /dev/null +++ b/LeadKit/Sources/Classes/Views/SpinnerView.swift @@ -0,0 +1,147 @@ +// +// 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 + +class SpinnerView: UIView, Animatable, LoadingIndicator { + + private(set) var animating: Bool = false + private var startTime = CFTimeInterval(0) + private var stopTime = CFTimeInterval(0) + + private weak var imageView: UIImageView? + + private let animationDuration: CFTimeInterval + private let animationRepeatCount: Float + private let clockwiseAnimation: Bool + + private let preferredSize: CGSize + + init(image: UIImage, + animationDuration: CFTimeInterval = 1, + animationRepeatCount: Float = Float.infinity, + clockwiseAnimation: Bool = true) { + + self.animationDuration = animationDuration + self.animationRepeatCount = animationRepeatCount + self.clockwiseAnimation = clockwiseAnimation + + self.preferredSize = image.size + + super.init(frame: CGRect(origin: .zero, size: preferredSize)) + + let imageView = UIImageView(image: image) + imageView.frame = bounds + imageView.autoresizingMask = [ + .flexibleLeftMargin, + .flexibleRightMargin, + .flexibleTopMargin, + .flexibleBottomMargin + ] + + addSubview(imageView) + + self.imageView = imageView + + NotificationCenter.default.addObserver(self, + selector: #selector(SpinnerView.restartAnimationIfNeeded), + name: .UIApplicationWillEnterForeground, + object: nil) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func didMoveToWindow() { + super.didMoveToWindow() + + if window != nil { + restartAnimationIfNeeded() + } + } + + // MARK: Animatable + + func startAnimating() { + guard !animating else { + return + } + + animating = true + + imageView?.isHidden = false + + addAnimation() + } + + func stopAnimating() { + guard animating else { + return + } + + animating = false + + imageView?.isHidden = true + + removeAnimation() + } + + // MARK: private stuff + + private func addAnimation() { + guard let imageView = imageView else { + return + } + + let anim = CABasicAnimation.zRotationAnimationWith(duration: animationDuration, + repeatCount: animationRepeatCount, + clockwise: clockwiseAnimation) + anim.timeOffset = stopTime - startTime + + imageView.layer.add(anim, forKey: CABasicAnimation.rotationKeyPath) + + startTime = imageView.layer.convertTime(CACurrentMediaTime(), from: nil) + } + + private func removeAnimation() { + guard let imageView = imageView else { + return + } + + imageView.layer.removeAnimation(forKey: CABasicAnimation.rotationKeyPath) + + stopTime = imageView.layer.convertTime(CACurrentMediaTime(), from: nil) + } + + @objc private func restartAnimationIfNeeded() { + if animating { + removeAnimation() + addAnimation() + } + } + +} diff --git a/LeadKit/Sources/Extensions/CABasicAnimation/CABasicAnimation+Rotation.swift b/LeadKit/Sources/Extensions/CABasicAnimation/CABasicAnimation+Rotation.swift new file mode 100644 index 00000000..eb4baccf --- /dev/null +++ b/LeadKit/Sources/Extensions/CABasicAnimation/CABasicAnimation+Rotation.swift @@ -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 QuartzCore + +extension CABasicAnimation { + + static let rotationKeyPath = "transform.rotation.z" + + static func zRotationAnimationWith(duration: CFTimeInterval = 1, + repeatCount: Float = Float.infinity, + clockwise: Bool = true) -> CABasicAnimation { + + let animation = CABasicAnimation(keyPath: CABasicAnimation.rotationKeyPath) + let direction = clockwise ? 1.0 : -1.0 + animation.toValue = NSNumber(value: .pi * 2 * direction) + animation.duration = duration + animation.isCumulative = true + animation.repeatCount = repeatCount + + return animation + } + +} diff --git a/LeadKit/Sources/Extensions/UIImage/UIImage+Spinner.swift b/LeadKit/Sources/Extensions/UIImage/UIImage+Spinner.swift new file mode 100644 index 00000000..697fb929 --- /dev/null +++ b/LeadKit/Sources/Extensions/UIImage/UIImage+Spinner.swift @@ -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 + +public extension UIImage { + + /// Creates AnyLoadingIndicator that rotates as spinner around Z axis. + /// + /// - Parameters: + /// - animationDuration: Duration of one full 360 degrees rotation. One second is default. + /// - animationRepeatCount: How many times the spin should be done. If not provided, the view will spin forever. + /// - clockwiseAnimation: Direction of the rotation. Default is clockwise (true). + /// - Returns: Instance of AnyLoadingIndicator. + func asSpinner(animationDuration: CFTimeInterval = 1, + animationRepeatCount: Float = Float.infinity, + clockwiseAnimation: Bool = true) -> AnyLoadingIndicator { + + let spinner = SpinnerView(image: self, + animationDuration: animationDuration, + animationRepeatCount: animationRepeatCount, + clockwiseAnimation: clockwiseAnimation) + + return AnyLoadingIndicator(spinner) + } + +} diff --git a/LeadKit/Sources/Extensions/UIView/UIView+Rotation.swift b/LeadKit/Sources/Extensions/UIView/UIView+Rotation.swift index 1613eb18..6d029824 100644 --- a/LeadKit/Sources/Extensions/UIView/UIView+Rotation.swift +++ b/LeadKit/Sources/Extensions/UIView/UIView+Rotation.swift @@ -24,8 +24,6 @@ import UIKit public extension UIView { - private static let rotationKeyPath = "transform.rotation.z" - /** Starts rotating the view around Z axis. @@ -34,18 +32,15 @@ public extension UIView { - parameter clockwise: Direction of the rotation. Default is clockwise (true). */ public func startZRotation(duration: CFTimeInterval = 1, repeatCount: Float = Float.infinity, clockwise: Bool = true) { - let animation = CABasicAnimation(keyPath: UIView.rotationKeyPath) - let direction = clockwise ? 1.0 : -1.0 - animation.toValue = NSNumber(value: .pi * 2 * direction) - animation.duration = duration - animation.isCumulative = true - animation.repeatCount = repeatCount - layer.add(animation, forKey: UIView.rotationKeyPath) + let animation = CABasicAnimation.zRotationAnimationWith(duration: duration, + repeatCount: repeatCount, + clockwise: clockwise) + layer.add(animation, forKey: CABasicAnimation.rotationKeyPath) } /// Stop rotating the view around Z axis. public func stopZRotation() { - layer.removeAnimation(forKey: UIView.rotationKeyPath) + layer.removeAnimation(forKey: CABasicAnimation.rotationKeyPath) } } From 7814b67ce3c1a08246da3aff1e1c64d18a31325f Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Fri, 5 May 2017 18:13:47 +0300 Subject: [PATCH 3/6] remove toast and extension compilation flag --- LeadKit.podspec | 6 --- LeadKit/LeadKit.xcodeproj/project.pbxproj | 7 +-- LeadKit/Podfile | 1 - .../Classes/Services/NetworkService.swift | 28 ++---------- .../NetworkService+RxLoadImage.swift | 12 +----- .../Observable+ToastErrorLogging.swift | 43 ------------------- 6 files changed, 6 insertions(+), 91 deletions(-) delete mode 100644 LeadKit/Sources/Extensions/Observable/Observable+ToastErrorLogging.swift diff --git a/LeadKit.podspec b/LeadKit.podspec index c7cf06de..a9957e7e 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -47,7 +47,6 @@ Pod::Spec.new do |s| "LeadKit/Sources/Extensions/CGFloat/CGFloat+Pixels.swift", "LeadKit/Sources/Extensions/NetworkService/NetworkService+ActivityIndicator.swift", "LeadKit/Sources/Extensions/NetworkService/NetworkService+RxLoadImage.swift", - "LeadKit/Sources/Extensions/Observable/Observable+ToastErrorLogging.swift", "LeadKit/Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift", "LeadKit/Sources/Extensions/StoryboardProtocol/*", "LeadKit/Sources/Extensions/Support/UIScrollView+Support.swift", @@ -71,7 +70,6 @@ Pod::Spec.new do |s| "LeadKit/Sources/Classes/Pagination/PaginationTableViewWrapper.swift", "LeadKit/Sources/Structures/Drawing/CALayerDrawingOperation.swift", "LeadKit/Sources/Extensions/NetworkService/NetworkService+ActivityIndicator.swift", - "LeadKit/Sources/Extensions/Observable/Observable+ToastErrorLogging.swift", "LeadKit/Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift", "LeadKit/Sources/Extensions/Support/UIScrollView+Support.swift", "LeadKit/Sources/Extensions/TableDirector/TableDirector+Extensions.swift", @@ -83,7 +81,6 @@ Pod::Spec.new do |s| ss.dependency "RxAlamofire", '3.0.2' ss.dependency "ObjectMapper", '~> 2.2' - ss.ios.dependency "Toast-Swift", '~> 2.0.0' ss.ios.dependency "TableKit", '~> 2.3.1' ss.ios.dependency "UIScrollView-InfiniteScroll", '~> 1.0.0' end @@ -96,13 +93,10 @@ Pod::Spec.new do |s| ss.exclude_files = [ "LeadKit/Sources/Classes/Pagination/PaginationTableViewWrapper.swift", "LeadKit/Sources/Extensions/NetworkService/NetworkService+ActivityIndicator.swift", - "LeadKit/Sources/Extensions/Observable/Observable+ToastErrorLogging.swift", "LeadKit/Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift", "LeadKit/Sources/Extensions/TableDirector/TableDirector+Extensions.swift", ] - ss.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'LEADKIT_EXTENSION_TARGET' } - ss.dependency "CocoaLumberjack/Swift", '~> 3.1.0' ss.dependency "RxSwift", '3.4.0' ss.dependency "RxCocoa", '3.4.0' diff --git a/LeadKit/LeadKit.xcodeproj/project.pbxproj b/LeadKit/LeadKit.xcodeproj/project.pbxproj index de65ca2d..8ca89f2c 100644 --- a/LeadKit/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit/LeadKit.xcodeproj/project.pbxproj @@ -116,7 +116,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 */; }; - 671462B01EB3396E00EAB194 /* Observable+ToastErrorLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F21EB3396E00EAB194 /* Observable+ToastErrorLogging.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 */; }; @@ -441,7 +440,6 @@ 671461ED1EB3396E00EAB194 /* IndexPath+ImmutableIndexPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IndexPath+ImmutableIndexPath.swift"; sourceTree = ""; }; 671461EF1EB3396E00EAB194 /* ImmutableMappable+ObservableMappable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ImmutableMappable+ObservableMappable.swift"; sourceTree = ""; }; 671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+DeferredJust.swift"; sourceTree = ""; }; - 671461F21EB3396E00EAB194 /* Observable+ToastErrorLogging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+ToastErrorLogging.swift"; sourceTree = ""; }; 671461F41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PaginationTableViewWrapperDelegate+DefaultImplementation.swift"; sourceTree = ""; }; 671461F61EB3396E00EAB194 /* Sequence+ConcurrentMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sequence+ConcurrentMap.swift"; sourceTree = ""; }; 671461F81EB3396E00EAB194 /* StoryboardProtocol+DefaultBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "StoryboardProtocol+DefaultBundle.swift"; sourceTree = ""; }; @@ -801,7 +799,6 @@ isa = PBXGroup; children = ( 671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */, - 671461F21EB3396E00EAB194 /* Observable+ToastErrorLogging.swift */, ); path = Observable; sourceTree = ""; @@ -1934,7 +1931,6 @@ 671463141EB3396E00EAB194 /* UIViewController+TopVisibleViewController.swift in Sources */, 671462881EB3396E00EAB194 /* CGFloat+Pixels.swift in Sources */, 671462941EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */, - 671462B01EB3396E00EAB194 /* Observable+ToastErrorLogging.swift in Sources */, 671463641EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */, 671462481EB3396E00EAB194 /* FixedPageCursor.swift in Sources */, 671462C81EB3396E00EAB194 /* String+Localization.swift in Sources */, @@ -2535,7 +2531,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "ru.touchin.LeadKit-iOS-Extensions"; PRODUCT_NAME = LeadKit; SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG LEADKIT_EXTENSION_TARGET"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 3.0; }; name = Debug; @@ -2561,7 +2557,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "ru.touchin.LeadKit-iOS-Extensions"; PRODUCT_NAME = LeadKit; SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = LEADKIT_EXTENSION_TARGET; SWIFT_VERSION = 3.0; }; name = Release; diff --git a/LeadKit/Podfile b/LeadKit/Podfile index 8c45902f..25498f6e 100644 --- a/LeadKit/Podfile +++ b/LeadKit/Podfile @@ -12,7 +12,6 @@ abstract_target 'LeadKit' do use_frameworks! - pod "Toast-Swift", '~> 2.0.0' pod "TableKit", '~> 2.3.1' pod "UIScrollView-InfiniteScroll", '~> 1.0.0' diff --git a/LeadKit/Sources/Classes/Services/NetworkService.swift b/LeadKit/Sources/Classes/Services/NetworkService.swift index 32538669..fcd5fa50 100644 --- a/LeadKit/Sources/Classes/Services/NetworkService.swift +++ b/LeadKit/Sources/Classes/Services/NetworkService.swift @@ -53,18 +53,8 @@ open class NetworkService { public func rxRequest(with parameters: ApiRequestParameters) -> Observable<(response: HTTPURLResponse, model: T)> where T.ModelType == T { - let responseObservable = sessionManager.rx.responseObservableModel(requestParameters: parameters) - .counterTracking(for: self) as Observable<(response: HTTPURLResponse, model: T)> - - #if os(iOS) - #if LEADKIT_EXTENSION_TARGET - return responseObservable - #else - return responseObservable.showErrorsInToastInDebugMode() - #endif - #else - return responseObservable - #endif + return sessionManager.rx.responseObservableModel(requestParameters: parameters) + .counterTracking(for: self) } /// Perform reactive request to get mapped ImmutableMappable model and http response @@ -74,18 +64,8 @@ open class NetworkService { public func rxRequest(with parameters: ApiRequestParameters) -> Observable<(response: HTTPURLResponse, model: T)> { - let responseObservable = sessionManager.rx.responseModel(requestParameters: parameters) - .counterTracking(for: self) as Observable<(response: HTTPURLResponse, model: T)> - - #if os(iOS) - #if LEADKIT_EXTENSION_TARGET - return responseObservable - #else - return responseObservable.showErrorsInToastInDebugMode() - #endif - #else - return responseObservable - #endif + return sessionManager.rx.responseModel(requestParameters: parameters) + .counterTracking(for: self) } fileprivate func increaseRequestCounter() { diff --git a/LeadKit/Sources/Extensions/NetworkService/NetworkService+RxLoadImage.swift b/LeadKit/Sources/Extensions/NetworkService/NetworkService+RxLoadImage.swift index 9c042a7d..23b9b0db 100644 --- a/LeadKit/Sources/Extensions/NetworkService/NetworkService+RxLoadImage.swift +++ b/LeadKit/Sources/Extensions/NetworkService/NetworkService+RxLoadImage.swift @@ -32,22 +32,12 @@ public extension NetworkService { public func rxLoadImage(url: String) -> Observable<(HTTPURLResponse, UIImage?)> { let request = RxAlamofire.requestData(.get, url, headers: [:]) - let requestObservable = request + return request .observeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .map { (response, data) -> (HTTPURLResponse, UIImage?) in (response, UIImage(data: data)) } .counterTracking(for: self) - - #if os(iOS) - #if LEADKIT_EXTENSION_TARGET - return requestObservable - #else - return requestObservable.showErrorsInToastInDebugMode() - #endif - #else - return requestObservable - #endif } } diff --git a/LeadKit/Sources/Extensions/Observable/Observable+ToastErrorLogging.swift b/LeadKit/Sources/Extensions/Observable/Observable+ToastErrorLogging.swift deleted file mode 100644 index ee4a326f..00000000 --- a/LeadKit/Sources/Extensions/Observable/Observable+ToastErrorLogging.swift +++ /dev/null @@ -1,43 +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 Toast_Swift - -public extension Observable { - - /// Method which shows toast with localized description of error in DEBUG mode - /// - /// - Returns: The source sequence with the side-effecting behavior applied. - func showErrorsInToastInDebugMode() -> Observable { - #if DEBUG - return `do`(onError: { (error) in - DispatchQueue.main.async { - UIApplication.shared.keyWindow?.makeToast(error.localizedDescription) - } - }) - #else - return self - #endif - } - -} From e289214e218ddc4cca6a567c4eecc910d4d4a3a2 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Sat, 6 May 2017 10:13:20 +0300 Subject: [PATCH 4/6] remove unused property --- LeadKit/Sources/Classes/Views/SpinnerView.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/LeadKit/Sources/Classes/Views/SpinnerView.swift b/LeadKit/Sources/Classes/Views/SpinnerView.swift index 9abf695f..c3bfe043 100644 --- a/LeadKit/Sources/Classes/Views/SpinnerView.swift +++ b/LeadKit/Sources/Classes/Views/SpinnerView.swift @@ -34,8 +34,6 @@ class SpinnerView: UIView, Animatable, LoadingIndicator { private let animationRepeatCount: Float private let clockwiseAnimation: Bool - private let preferredSize: CGSize - init(image: UIImage, animationDuration: CFTimeInterval = 1, animationRepeatCount: Float = Float.infinity, @@ -45,9 +43,7 @@ class SpinnerView: UIView, Animatable, LoadingIndicator { self.animationRepeatCount = animationRepeatCount self.clockwiseAnimation = clockwiseAnimation - self.preferredSize = image.size - - super.init(frame: CGRect(origin: .zero, size: preferredSize)) + super.init(frame: CGRect(origin: .zero, size: image.size)) let imageView = UIImageView(image: image) imageView.frame = bounds From 72c0d4fc6ecd6f3428d8348cc8019796652b689f Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 10 May 2017 17:31:04 +0300 Subject: [PATCH 5/6] fix tests --- LeadKit/Tests/CursorTests.swift | 20 +++----------------- LeadKit/Tests/Cursors/StubCursor.swift | 9 ++++----- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/LeadKit/Tests/CursorTests.swift b/LeadKit/Tests/CursorTests.swift index e03519b2..9e425804 100644 --- a/LeadKit/Tests/CursorTests.swift +++ b/LeadKit/Tests/CursorTests.swift @@ -42,7 +42,7 @@ class CursorTests: XCTestCase { let cursorExpectation = expectation(description: "Stub cursor expectation") entityCursor.loadNextBatch() - .subscribe(onNext: { _ in + .subscribe(onSuccess: { _ in cursorExpectation.fulfill() }) .addDisposableTo(disposeBag) @@ -55,29 +55,15 @@ class CursorTests: XCTestCase { let fixedPageCursor = FixedPageCursor(cursor: stubCursor, pageSize: 16) let cursorExpectation = expectation(description: "Fixed page cursor expectation") - let cursorExpectationError = expectation(description: "Fixed page cursor error expectation") fixedPageCursor.loadNextBatch() - .subscribe(onNext: { loadedItems in + .subscribe(onSuccess: { loadedItems in XCTAssertEqual(loadedItems.count, 15) cursorExpectation.fulfill() }) .addDisposableTo(disposeBag) - fixedPageCursor.loadNextBatch() - .subscribe(onNext: { _ in - XCTFail("Cursor should be exhausted!") - }, onError: { error in - switch try? cast(error) as CursorError { - case .exhausted?: - cursorExpectationError.fulfill() - default: - XCTFail("Cursor should be exhausted!") - } - }) - .addDisposableTo(disposeBag) - waitForExpectations(timeout: 10, handler: nil) } @@ -89,7 +75,7 @@ class CursorTests: XCTestCase { let cursorExpectation = expectation(description: "Fixed page cursor expectation") fixedPageCursor.loadNextBatch() - .subscribe(onNext: { loadedItems in + .subscribe(onSuccess: { loadedItems in XCTAssertEqual(loadedItems.count, 8) cursorExpectation.fulfill() diff --git a/LeadKit/Tests/Cursors/StubCursor.swift b/LeadKit/Tests/Cursors/StubCursor.swift index 3a8241ca..28210ff1 100644 --- a/LeadKit/Tests/Cursors/StubCursor.swift +++ b/LeadKit/Tests/Cursors/StubCursor.swift @@ -55,10 +55,10 @@ class StubCursor: ResettableCursorType { self.requestDelay = other.requestDelay } - func loadNextBatch() -> Observable<[Post]> { - return Observable.create { observer -> Disposable in + func loadNextBatch() -> Single<[Post]> { + return Single.create { observer -> Disposable in if self.exhausted { - observer.onError(CursorError.exhausted) + observer(.error(CursorError.exhausted)) } else { DispatchQueue.global().asyncAfter(deadline: .now() + self.requestDelay, execute: { let countBefore = self.count @@ -69,8 +69,7 @@ class StubCursor: ResettableCursorType { self.posts = Array((self.posts + newPosts)[0.. Date: Wed, 10 May 2017 17:46:20 +0300 Subject: [PATCH 6/6] PR notes #1 --- .../Classes/Cursors/FixedPageCursor.swift | 26 ++++++++++--------- .../Classes/Cursors/SingleLoadCursor.swift | 14 +++++----- .../Classes/Cursors/StaticCursor.swift | 18 +++++++------ 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/LeadKit/Sources/Classes/Cursors/FixedPageCursor.swift b/LeadKit/Sources/Classes/Cursors/FixedPageCursor.swift index 2d04a61e..22a6a1b8 100644 --- a/LeadKit/Sources/Classes/Cursors/FixedPageCursor.swift +++ b/LeadKit/Sources/Classes/Cursors/FixedPageCursor.swift @@ -50,22 +50,24 @@ public class FixedPageCursor: CursorType { } public func loadNextBatch() -> Single<[Cursor.Element]> { - if exhausted { - return .error(CursorError.exhausted) - } + return Single.deferred { + if self.exhausted { + return .error(CursorError.exhausted) + } - let restOfLoaded = cursor.count - count + let restOfLoaded = self.cursor.count - self.count - if restOfLoaded >= pageSize || cursor.exhausted { - let startIndex = count - count += min(restOfLoaded, pageSize) + if restOfLoaded >= self.pageSize || self.cursor.exhausted { + let startIndex = self.count + self.count += min(restOfLoaded, self.pageSize) - return .just(cursor[startIndex..: ResettableCursorType { } public func loadNextBatch() -> Single<[Element]> { - if exhausted { - return .error(CursorError.exhausted) - } + return Single.deferred { + if self.exhausted { + return .error(CursorError.exhausted) + } - return loadingObservable.do(onNext: { [weak self] newItems in - self?.onGot(result: newItems) - }) + return self.loadingObservable.do(onNext: { [weak self] newItems in + self?.onGot(result: newItems) + }) + } } private func onGot(result: [Element]) { diff --git a/LeadKit/Sources/Classes/Cursors/StaticCursor.swift b/LeadKit/Sources/Classes/Cursors/StaticCursor.swift index 60289d52..144cba0c 100644 --- a/LeadKit/Sources/Classes/Cursors/StaticCursor.swift +++ b/LeadKit/Sources/Classes/Cursors/StaticCursor.swift @@ -47,15 +47,17 @@ public class StaticCursor: ResettableCursorType { } public func loadNextBatch() -> Single<[Element]> { - if exhausted { - return .error(CursorError.exhausted) + return Single.deferred { + if self.exhausted { + return .error(CursorError.exhausted) + } + + self.count = self.content.count + + self.exhausted = true + + return .just(self.content) } - - count = content.count - - exhausted = true - - return .just(content) } }