Merge branch 'master' into feature/refresh_control

# Conflicts:
#	CHANGELOG.md
#	LeadKit.podspec
This commit is contained in:
Vlad 2020-09-04 12:25:23 +03:00
commit 79d8f2a05e
22 changed files with 83 additions and 401 deletions

View File

@ -1 +0,0 @@
5.0

View File

@ -1,8 +1,17 @@
# Changelog
### 0.9.45
### 0.10.2
- **Add**: `RefreshControl` - a basic UIRefreshControl with fixed refresh action.
### 0.10.1
- **Update**: Third party dependencies: `Alamofire` 5.2.2, `RxAlamofire` 5.6.1
### 0.10.0
- **Update**: Third party dependencies: `RxSwift` (and all sub-dependencies) to 5.1.0, `Alamofire` 5.0, `SnapKit` 5.0
- **Refactored**: NetworkManager to use new Alamofire API
- **API BreakingChanges**: NetworkServiceConfiguration no longer accepts `ServerTrustPolicy`, it is now replaced by an instance of a `ServerTrustEvaluating` protocol. Full description and default implementations can be found at Alamofire [sources](https://github.com/Alamofire/Alamofire/blob/master/Source/ServerTrustEvaluation.swift). Since new evaluation is used, evaluation against self-signed certificates will now throw an AfError and abort any outcoming request. To support self-signed certificates use `DisabledTrustEvaluator` for specified host in configuration.
- **Removed**: UIImage+SupportExtensions, UIScrollView+Support
### 0.9.44
- **Add**: `TIFoundationUtils` - set of helpers for Foundation framework classes.

View File

@ -1,7 +1,7 @@
github "malcommac/SwiftDate"
github "Alamofire/Alamofire"
github "RxSwiftCommunity/RxAlamofire" ~> 4.5
github "RxSwiftCommunity/RxAlamofire" ~> 5.6.0
github "TouchInstinct/TableKit"
github "ReactiveX/RxSwift" ~> 4.5
github "ReactiveX/RxSwift" ~> 5.1.0
github "pronebird/UIScrollView-InfiniteScroll"
github "SnapKit/SnapKit" ~> 4.2
github "SnapKit/SnapKit" ~> 5.0

View File

@ -1,7 +1,7 @@
github "Alamofire/Alamofire" "4.9.1"
github "ReactiveX/RxSwift" "4.5.0"
github "RxSwiftCommunity/RxAlamofire" "4.5.0"
github "SnapKit/SnapKit" "4.2.0"
github "Alamofire/Alamofire" "5.2.2"
github "ReactiveX/RxSwift" "5.1.1"
github "RxSwiftCommunity/RxAlamofire" "v5.6.1"
github "SnapKit/SnapKit" "5.0.1"
github "TouchInstinct/TableKit" "2.10008.1"
github "malcommac/SwiftDate" "6.1.0"
github "pronebird/UIScrollView-InfiniteScroll" "1.1.0"

View File

@ -1,12 +1,13 @@
Pod::Spec.new do |s|
s.name = "LeadKit"
s.version = "0.9.45"
s.version = "0.10.2"
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"
s.author = "Touch Instinct"
s.source = { :git => "https://github.com/TouchInstinct/LeadKit.git", :tag => s.version }
s.platform = :ios, '9.0'
s.platform = :ios, '10.0'
s.swift_versions = ['5.0']
s.subspec 'UIColorHex' do |ss|
ss.ios.deployment_target = '8.0'
@ -17,9 +18,9 @@ Pod::Spec.new do |s|
end
s.subspec 'Core' do |ss|
ss.ios.deployment_target = '9.0'
ss.tvos.deployment_target = '9.0'
ss.watchos.deployment_target = '2.0'
ss.ios.deployment_target = '10.0'
ss.tvos.deployment_target = '10.0'
ss.watchos.deployment_target = '3.0'
ss.source_files = "Sources/**/*.swift"
ss.watchos.exclude_files = [
@ -43,7 +44,6 @@ Pod::Spec.new do |s|
"Sources/Extensions/NetworkService/NetworkService+RxLoadImage.swift",
"Sources/Extensions/DataLoading/GeneralDataLoading/GeneralDataLoadingController+DefaultImplementation.swift",
"Sources/Extensions/DataLoading/PaginationDataLoading/*",
"Sources/Extensions/Support/UIScrollView+Support.swift",
"Sources/Extensions/Support/UINavigationItem+Support.swift",
"Sources/Extensions/TableKit/**/*.swift",
"Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift",
@ -80,7 +80,6 @@ Pod::Spec.new do |s|
"Sources/Structures/Drawing/CALayerDrawingOperation.swift",
"Sources/Enums/Search/*",
"Sources/Extensions/DataLoading/PaginationDataLoading/*",
"Sources/Extensions/Support/UIScrollView+Support.swift",
"Sources/Extensions/Support/UINavigationItem+Support.swift",
"Sources/Extensions/TableKit/**/*.swift",
"Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift",
@ -93,13 +92,13 @@ Pod::Spec.new do |s|
"Sources/Structures/DataLoading/PaginationDataLoading/*"
]
ss.dependency "RxSwift", '~> 4'
ss.dependency "RxCocoa", '~> 4'
ss.dependency "RxAlamofire", '~> 4'
ss.dependency "RxSwift", '~> 5.1.0'
ss.dependency "RxCocoa", '~> 5.1.0'
ss.dependency "RxAlamofire", '~> 5.6.0'
ss.dependency "SwiftDate", '~> 6'
ss.ios.dependency "TableKit", '~> 2.8'
ss.ios.dependency "SnapKit", '~> 4.0.0'
ss.ios.dependency "SnapKit", '~> 5.0.0'
ss.ios.dependency "UIScrollView-InfiniteScroll", '~> 1.1.0'
end

View File

@ -95,7 +95,6 @@
671462C81EB3396E00EAB194 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461FC1EB3396E00EAB194 /* String+Localization.swift */; };
671462CA1EB3396E00EAB194 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461FC1EB3396E00EAB194 /* String+Localization.swift */; };
671462CB1EB3396E00EAB194 /* String+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461FC1EB3396E00EAB194 /* String+Localization.swift */; };
671462D01EB3396E00EAB194 /* UIScrollView+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461FF1EB3396E00EAB194 /* UIScrollView+Support.swift */; };
671462D41EB3396E00EAB194 /* TableDirector+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462011EB3396E00EAB194 /* TableDirector+Extensions.swift */; };
671462D81EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462031EB3396E00EAB194 /* TimeInterval+DateComponents.swift */; };
671462DA1EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462031EB3396E00EAB194 /* TimeInterval+DateComponents.swift */; };
@ -105,8 +104,6 @@
671462E71EB3396E00EAB194 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462091EB3396E00EAB194 /* UIColor+Hex.swift */; };
671462EC1EB3396E00EAB194 /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6714620D1EB3396E00EAB194 /* UIImage+Extensions.swift */; };
671462EF1EB3396E00EAB194 /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6714620D1EB3396E00EAB194 /* UIImage+Extensions.swift */; };
671462F01EB3396E00EAB194 /* UIImage+SupportExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6714620E1EB3396E00EAB194 /* UIImage+SupportExtensions.swift */; };
671462F31EB3396E00EAB194 /* UIImage+SupportExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6714620E1EB3396E00EAB194 /* UIImage+SupportExtensions.swift */; };
671462FC1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462131EB3396E00EAB194 /* UIView+XibNameProtocol.swift */; };
671462FF1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462131EB3396E00EAB194 /* UIView+XibNameProtocol.swift */; };
671463001EB3396E00EAB194 /* UIView+LoadFromNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671462141EB3396E00EAB194 /* UIView+LoadFromNib.swift */; };
@ -589,12 +586,10 @@
671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+DeferredJust.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>"; };
671461FF1EB3396E00EAB194 /* UIScrollView+Support.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Support.swift"; sourceTree = "<group>"; };
671462011EB3396E00EAB194 /* TableDirector+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TableDirector+Extensions.swift"; sourceTree = "<group>"; };
671462031EB3396E00EAB194 /* TimeInterval+DateComponents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeInterval+DateComponents.swift"; sourceTree = "<group>"; };
671462091EB3396E00EAB194 /* UIColor+Hex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Hex.swift"; sourceTree = "<group>"; };
6714620D1EB3396E00EAB194 /* UIImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = "<group>"; };
6714620E1EB3396E00EAB194 /* UIImage+SupportExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+SupportExtensions.swift"; sourceTree = "<group>"; };
671462131EB3396E00EAB194 /* UIView+XibNameProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+XibNameProtocol.swift"; sourceTree = "<group>"; };
671462141EB3396E00EAB194 /* UIView+LoadFromNib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+LoadFromNib.swift"; sourceTree = "<group>"; };
671462151EB3396E00EAB194 /* UIView+LoadingIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+LoadingIndicator.swift"; sourceTree = "<group>"; };
@ -1110,7 +1105,6 @@
isa = PBXGroup;
children = (
7295474121E6628C009558E7 /* UINavigationItem+Support.swift */,
671461FF1EB3396E00EAB194 /* UIScrollView+Support.swift */,
);
path = Support;
sourceTree = "<group>";
@ -1145,7 +1139,6 @@
isa = PBXGroup;
children = (
6714620D1EB3396E00EAB194 /* UIImage+Extensions.swift */,
6714620E1EB3396E00EAB194 /* UIImage+SupportExtensions.swift */,
);
path = UIImage;
sourceTree = "<group>";
@ -2470,7 +2463,6 @@
671462841EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */,
EFBE57DB1EC361620040E00A /* UIView+Layout.swift in Sources */,
6714634C1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */,
671462F01EB3396E00EAB194 /* UIImage+SupportExtensions.swift in Sources */,
6741CEAF20E242A500FEC4D9 /* TableViewHolder+ScrollViewHolder.swift in Sources */,
67CAF8C620652E2A00527085 /* TextFieldViewModel.swift in Sources */,
671462681EB3396E00EAB194 /* NetworkService.swift in Sources */,
@ -2493,7 +2485,6 @@
72005A1F2266226800ECE090 /* CustomizableButtonViewModel.swift in Sources */,
677B06C4211884F3006C947D /* BaseTextAttributes.swift in Sources */,
675E0AA921072FF400CDC143 /* BaseScrollContentController.swift in Sources */,
671462D01EB3396E00EAB194 /* UIScrollView+Support.swift in Sources */,
671463901EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */,
A658E54D1F8CD7790093527A /* TableRow+SeparatorsExtensions.swift in Sources */,
85A5D49522AA975000C7D254 /* Decimal+Rounding.swift in Sources */,
@ -2814,7 +2805,6 @@
671463631EB3396E00EAB194 /* SupportProtocol.swift in Sources */,
671462871EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */,
6714634F1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */,
671462F31EB3396E00EAB194 /* UIImage+SupportExtensions.swift in Sources */,
6714626B1EB3396E00EAB194 /* NetworkService.swift in Sources */,
67E352612119B7570035BDDB /* BasePlaceholerView.swift in Sources */,
673CF43A2063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift in Sources */,

View File

@ -162,7 +162,7 @@ final public class PaginationWrapper<Cursor: ResettableRxDataSourceCursor, Deleg
removeAllPlaceholderView()
wrappedView.scrollView.support.refreshControl?.endRefreshing()
wrappedView.scrollView.refreshControl?.endRefreshing()
addInfiniteScroll(withHandler: true)
} else if case .loadingMore = afterState {
@ -176,7 +176,7 @@ final public class PaginationWrapper<Cursor: ResettableRxDataSourceCursor, Deleg
private func onErrorState(error: Error, afterState: LoadingState) {
if case .initialLoading = afterState {
defer {
wrappedView.scrollView.support.refreshControl?.endRefreshing()
wrappedView.scrollView.refreshControl?.endRefreshing()
}
delegate?.clearData()
@ -222,7 +222,7 @@ final public class PaginationWrapper<Cursor: ResettableRxDataSourceCursor, Deleg
private func onEmptyState() {
defer {
wrappedView.scrollView.support.refreshControl?.endRefreshing()
wrappedView.scrollView.refreshControl?.endRefreshing()
}
delegate?.clearData()
@ -297,7 +297,7 @@ final public class PaginationWrapper<Cursor: ResettableRxDataSourceCursor, Deleg
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshAction), for: .valueChanged)
wrappedView.scrollView.support.setRefreshControl(refreshControl)
wrappedView.scrollView.refreshControl = refreshControl
}
@objc private func refreshAction() {
@ -309,7 +309,7 @@ final public class PaginationWrapper<Cursor: ResettableRxDataSourceCursor, Deleg
}
private func removeRefreshControl() {
wrappedView.scrollView.support.setRefreshControl(nil)
wrappedView.scrollView.refreshControl = nil
}
private func bindViewModelStates() {

View File

@ -45,7 +45,7 @@ open class BaseSearchViewModel<Item, ItemViewModel>: GeneralDataLoadingViewModel
}
open var searchDebounceInterval: RxTimeInterval {
return 1
return .seconds(1)
}
open var searchResultsDriver: Driver<[ItemViewModel]> {

View File

@ -144,7 +144,7 @@ public extension Observable {
///
/// - Parameter networkService: NetworkService to operate on it
/// - Returns: The source sequence with the side-effecting behavior applied.
func counterTracking(for networkService: NetworkService) -> Observable<Observable.E> {
func counterTracking(for networkService: NetworkService) -> Observable<Observable.Element> {
return `do`(onSubscribe: {
networkService.increaseRequestCounter()
}, onDispose: {

View File

@ -23,7 +23,7 @@
import Alamofire
/// Session Manager stored in NetworkService
open class SessionManager: Alamofire.SessionManager {
open class SessionManager: Alamofire.Session {
/// Response with HTTP URL Response and target object
public typealias ModelResponse<T> = (response: HTTPURLResponse, model: T)
@ -38,25 +38,40 @@ open class SessionManager: Alamofire.SessionManager {
public let mappingQueue: DispatchQueue
public init(configuration: URLSessionConfiguration,
serverTrustPolicyManager: ServerTrustPolicyManager,
serverTrustManager: ServerTrustManager,
acceptableStatusCodes: Set<Int>,
mappingQueue: DispatchQueue) {
self.acceptableStatusCodes = acceptableStatusCodes
self.mappingQueue = mappingQueue
super.init(configuration: configuration, serverTrustPolicyManager: serverTrustPolicyManager)
let delegate = SessionDelegate()
let delegateQueue = OperationQueue()
delegateQueue.underlyingQueue = mappingQueue
let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue)
super.init(session: session,
delegate: delegate,
rootQueue: mappingQueue,
serverTrustManager: serverTrustManager)
}
public init?(session: URLSession,
delegate: SessionDelegate,
serverTrustPolicyManager: ServerTrustPolicyManager,
acceptableStatusCodes: Set<Int>,
mappingQueue: DispatchQueue) {
public init(session: URLSession,
delegate: SessionDelegate,
serverTrustManager: ServerTrustManager,
acceptableStatusCodes: Set<Int>,
mappingQueue: DispatchQueue) {
self.acceptableStatusCodes = acceptableStatusCodes
self.mappingQueue = mappingQueue
super.init(session: session, delegate: delegate, serverTrustPolicyManager: serverTrustPolicyManager)
session.delegateQueue.underlyingQueue = mappingQueue
super.init(session: session,
delegate: delegate,
rootQueue: mappingQueue,
serverTrustManager: serverTrustManager)
}
}

View File

@ -109,8 +109,9 @@ public extension BehaviorRelay {
/// - getFieldClosure: Closure for getting field string reprerentation from data model.
/// - mergeFieldClosure: Closure for merging new field value into data model.
/// - Returns: DataModelFieldBinding instance.
func fieldBinding(getFieldClosure: @escaping DataModelFieldBinding<E>.GetFieldClosure,
mergeFieldClosure: @escaping DataModelFieldBinding<E>.MergeFieldClosure) -> DataModelFieldBinding<E> {
func fieldBinding(getFieldClosure: @escaping DataModelFieldBinding<Element>.GetFieldClosure,
mergeFieldClosure: @escaping DataModelFieldBinding<Element>.MergeFieldClosure)
-> DataModelFieldBinding<Element> {
return DataModelFieldBinding(modelRelay: self,
getFieldClosure: getFieldClosure,
@ -123,7 +124,7 @@ public extension BehaviorRelay where Element == String? {
/// Creates DataModelFieldBinding configured with behaviour relay itself.
///
/// - Returns: DataModelFieldBinding instance.
func fieldBinding() -> DataModelFieldBinding<E> {
func fieldBinding() -> DataModelFieldBinding<Element> {
return DataModelFieldBinding(modelRelay: self)
}
}

View File

@ -70,25 +70,25 @@ public extension Reactive where Base: DataRequest {
}
private func response(onQueue queue: DispatchQueue) -> Observable<(HTTPURLResponse, Data)> {
return responseResult(queue: queue, responseSerializer: DataRequest.dataResponseSerializer())
return responseResult(queue: queue, responseSerializer: DataResponseSerializer())
}
}
public extension ObservableType where E == DataRequest {
public extension ObservableType where Element == DataRequest {
/// Method that validates status codes and catch network errors
///
/// - Parameter statusCodes: set of status codes to validate
/// - Returns: Observable on self
func validate(statusCodes: Set<Int>) -> Observable<E> {
func validate(statusCodes: Set<Int>) -> Observable<Element> {
return map { $0.validate(statusCode: statusCodes) }
.catchAsRequestError()
}
}
private extension ObservableType where E == ServerResponse {
private extension ObservableType where Element == ServerResponse {
func tryMapResult<R>(_ transform: @escaping (E) throws -> R) -> Observable<R> {
func tryMapResult<R>(_ transform: @escaping (Element) throws -> R) -> Observable<R> {
return map {
do {
return try transform($0)
@ -98,7 +98,7 @@ private extension ObservableType where E == ServerResponse {
}
}
func tryMapObservableResult<R>(_ transform: @escaping (E) throws -> Observable<R>) -> Observable<R> {
func tryMapObservableResult<R>(_ transform: @escaping (Element) throws -> Observable<R>) -> Observable<R> {
return flatMap { response, result -> Observable<R> in
do {
return try transform((response, result))
@ -114,10 +114,10 @@ private extension ObservableType where E == ServerResponse {
private extension ObservableType {
func catchAsRequestError(with request: DataRequest? = nil) -> Observable<E> {
func catchAsRequestError(with request: DataRequest? = nil) -> Observable<Element> {
return catchError { error in
let resultError: RequestError
let response = request?.delegate.data
let response = request?.data
switch error {
case let requestError as RequestError:

View File

@ -50,7 +50,7 @@ public extension Reactive where Base: SessionManager {
_ url: URLConvertible,
parameters: [Any]? = nil,
encoding: JSONEncoding = .default,
headers: [String: String]? = nil)
headers: HTTPHeaders? = nil)
-> Observable<DataRequest> {
return Observable.deferred {

View File

@ -22,7 +22,6 @@
import UIKit
@available(iOS 10.0, tvOS 10.0, *)
public extension UIImage {
/// Creates an image filled by given color.
@ -235,7 +234,6 @@ public extension UIImage {
}
}
@available(iOS 10.0, tvOS 10.0, *)
internal extension DrawingOperation {
func imageFromNewRenderer(scale: CGFloat) -> UIImage {

View File

@ -1,283 +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
public extension Support where Base: UIImage {
/// Creates an image filled by given color.
///
/// - Parameters:
/// - color: The color to fill
/// - size: The size of an new image.
/// - Returns: A new instanse of UIImage with given size and color or nil if something goes wrong.
static func imageWith(color: UIColor, size: CGSize) -> Support<UIImage>? {
let width = Int(ceil(size.width))
let height = Int(ceil(size.height))
let operation = SolidFillDrawingOperation(color: color.cgColor, width: width, height: height)
return operation.imageFromNewContext(scale: UIScreen.main.scale)?.support
}
/// Creates an image from a UIView.
///
/// - 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 operation = CALayerDrawingOperation(layer: view.layer, size: view.bounds.size)
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.
///
/// - 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>? {
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)
}
}
/// Creates a new image with rounded corners and border.
///
/// - Parameters:
/// - cornerRadius: The corner radius.
/// - 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 or nil if something goes wrong.
func roundCorners(cornerRadius: CGFloat,
borderWidth: CGFloat,
color: UIColor,
extendSize: Bool = false) -> Support<UIImage>? {
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)
}
}
/// Creates a new circle image.
///
/// - Returns: A new circled image or nil if something goes wrong.
func roundCornersToCircle() -> Support<UIImage>? {
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)
}
}
/// Creates a new circle image with a border.
///
/// - Parameters:
/// - 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.
func roundCornersToCircle(borderWidth: CGFloat,
borderColor: UIColor,
extendSize: Bool = false) -> Support<UIImage>? {
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)
}
}
/// Creates a resized copy of an image.
///
/// - Parameters:
/// - newSize: The new size of the image.
/// - 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.
func resize(newSize: CGSize,
contentMode: ResizeMode = .scaleToFill,
cropToImageBounds: Bool = false) -> Support<UIImage>? {
return withCGImage { image in
let operation = ResizeDrawingOperation(image: image,
imageSize: base.size,
preferredNewSize: newSize,
resizeMode: contentMode,
cropToImageBounds: cropToImageBounds)
return operation.imageFromNewContext(scale: base.scale)
}
}
/// 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>? {
return withCGImage { image in
let operation = ImageDrawingOperation(image: image,
newSize: base.size,
opaque: false)
return operation.imageFromNewContext(scale: base.scale)
}
}
/// 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.
func applyPadding(_ padding: CGFloat) -> Support<UIImage>? {
return withCGImage { image in
let operation = PaddingDrawingOperation(image: image,
imageSize: base.size,
padding: padding)
return operation.imageFromNewContext(scale: base.scale)
}
}
/// 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)
}
return actionClosure(image)?.support
}
}
private extension CGImage {
func flipYOperation(size: CGSize) -> ImageDrawingOperation {
return ImageDrawingOperation(image: self,
newSize: size,
origin: .zero,
opaque: false,
flipY: true)
}
}
private extension DrawingOperation {
func cgImageFromNewContext(scale: CGFloat) -> CGImage? {
let ctxSize = contextSize
let intScale = Int(scale)
let context = CGContext.create(width: ctxSize.width * intScale,
height: ctxSize.height * intScale,
bitmapInfo: opaque ? .opaqueBitmapInfo : .alphaBitmapInfo)
guard let ctx = context else {
return nil
}
ctx.scaleBy(x: scale, y: scale)
apply(in: ctx)
return ctx.makeImage()
}
func imageFromNewContext(scale: CGFloat) -> UIImage? {
guard let image = cgImageFromNewContext(scale: scale) else {
return nil
}
return UIImage(cgImage: image, scale: scale, orientation: .up)
}
}

View File

@ -51,7 +51,7 @@ public extension ObservableType {
/// - handler: closure that recieves serialized response
/// - Returns: Observable on caller
func handleMappingError<T: Decodable>(with decoder: JSONDecoder = JSONDecoder(),
handler: @escaping ParameterClosure<T>) -> Observable<E> {
handler: @escaping ParameterClosure<T>) -> Observable<Element> {
return self.do(onError: { error in
guard let errorModel = try error.handleMappingError(with: decoder) as T? else {
return

View File

@ -29,7 +29,7 @@ public extension ObservableType {
/// - Parameter elementFactory: Element factory function to invoke for each observer
/// that subscribes to the resulting sequence.
/// - Returns: An observable sequence whose observers trigger an invocation of the given element factory function.
static func deferredJust(_ elementFactory: @escaping () throws -> E) -> Observable<E> {
static func deferredJust(_ elementFactory: @escaping () throws -> Element) -> Observable<Element> {
return .create { observer in
do {
observer.onNext(try elementFactory())

View File

@ -42,7 +42,7 @@ public extension ObservableType {
/// Cast all emitted elements to optional type.
///
/// - Returns: An observable sequence whose elements are equals to optional type of element.
func asOptional() -> Observable<E?> {
func asOptional() -> Observable<Element?> {
return map { $0 }
}
}

View File

@ -63,5 +63,6 @@ public extension Sequence {
array.sorted { $0.idx < $1.idx }
.flatMap { $0.results }
}
.asObservable()
}
}

View File

@ -1,47 +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
public extension Support where Base: UIScrollView {
var refreshControl: UIRefreshControl? {
if #available(iOS 10.0, *) {
return base.refreshControl
} else {
return base.subviews.first { $0 is UIRefreshControl } as? UIRefreshControl
}
}
func setRefreshControl(_ newRefreshControl: UIRefreshControl?) {
if #available(iOS 10.0, *) {
base.refreshControl = newRefreshControl
} else {
if let newControl = newRefreshControl {
refreshControl?.removeFromSuperview()
base.addSubview(newControl)
} else {
refreshControl?.removeFromSuperview()
}
}
}
}

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>0.9.28</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@ -37,7 +37,7 @@ public struct NetworkServiceConfiguration {
public let additionalHttpHeaders: HTTPHeaders
/// Server trust policies.
public var serverTrustPolicies: [String: ServerTrustPolicy]
public var serverTrustPolicies: [String: ServerTrustEvaluating]
/// HTTP response status codes regarded as non-erroneous
public var acceptableStatusCodes: Set<Int> = Set(200..<300)
@ -48,20 +48,19 @@ public struct NetworkServiceConfiguration {
public init(baseUrl: String,
timeoutInterval: TimeInterval = 20,
encoding: ParameterEncoding = URLEncoding.default,
additionalHttpHeaders: HTTPHeaders = [:],
trustPolicies: [String: ServerTrustPolicy] = [:]) {
additionalHttpHeaders: [String: String] = [:],
trustPolicies: [String: ServerTrustEvaluating] = [:]) {
self.baseUrl = baseUrl
self.timeoutInterval = timeoutInterval
self.encoding = encoding
self.additionalHttpHeaders = additionalHttpHeaders.merging(SessionManager.defaultHTTPHeaders) { current, _ in current }
self.additionalHttpHeaders = HTTPHeaders(additionalHttpHeaders)
sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.timeoutIntervalForResource = timeoutInterval
sessionConfiguration.httpAdditionalHeaders = additionalHttpHeaders
let updatedPolicies = Dictionary(uniqueKeysWithValues: trustPolicies.map { ($0.key.asHost, $0.value) })
serverTrustPolicies = trustPolicies.isEmpty ? [baseUrl.asHost: .disableEvaluation] : updatedPolicies
serverTrustPolicies = Dictionary(uniqueKeysWithValues: trustPolicies.map { ($0.key.asHost, $0.value) })
}
}
@ -70,7 +69,8 @@ public extension NetworkServiceConfiguration {
/// SessionManager constructed with given parameters (session configuration and trust policies)
var sessionManager: SessionManager {
return SessionManager(configuration: sessionConfiguration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies),
serverTrustManager: ServerTrustManager(allHostsMustBeEvaluated: !serverTrustPolicies.isEmpty,
evaluators: serverTrustPolicies),
acceptableStatusCodes: acceptableStatusCodes,
mappingQueue: .global())
}