diff --git a/CHANGELOG.md b/CHANGELOG.md index bba6536a..b6e86452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ # Changelog -### 0.9.18 +### 0.9.19 - **Add**: `hexString` property for `UIColor` that returns hex representation of color as string. +### 0.9.18 +- **Add**: `CustomizableButtonView` container class that provides great customization. +- **Add**: `CustomizableButtonViewModel` viewModel class for `CustomizableButtonView` configuration. +- **Add**: `CustomizableButton` class that is a `CustomizableButtonView` subview and gives it a button functionality. + ### 0.9.17 - **Fix**: SpinnerView infinity animation. diff --git a/LeadKit.podspec b/LeadKit.podspec index 7944d1c1..7bec78c7 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "0.9.17" + s.version = "0.9.19" 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" @@ -35,6 +35,7 @@ Pod::Spec.new do |s| "Sources/Classes/Views/CollectionViewWrapperView/*", "Sources/Classes/Views/TableViewWrapperView/*", "Sources/Classes/Views/BasePlaceholderView/*", + "Sources/Classes/Views/CustomizableButton/*", "Sources/Classes/Search/*", "Sources/Enums/Search/*", "Sources/Extensions/CABasicAnimation/*", @@ -73,6 +74,7 @@ Pod::Spec.new do |s| "Sources/Classes/Views/SeparatorCell/*", "Sources/Classes/Views/EmptyCell/*", "Sources/Classes/Views/LabelTableViewCell/*", + "Sources/Classes/Views/CustomizableButton/*", "Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift", "Sources/Classes/Search/*", "Sources/Structures/Drawing/CALayerDrawingOperation.swift", diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index ff3ceba1..0df4a177 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -453,6 +453,9 @@ 6B5B64BACFF8C5487FB0939D /* TableKitViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5B66503F2C42D009DEA011 /* TableKitViewModel.swift */; }; 6B5B6EF1577C8CC06E4CCF1B /* Array+RowExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5B62E7942E5AEE68A95449 /* Array+RowExtensions.swift */; }; 6B5B6F0BFA22832C47142BAD /* TableKitViewModel+Extenstions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B5B61443DDAB82927448CAA /* TableKitViewModel+Extenstions.swift */; }; + 72005A1E2266226800ECE090 /* CustomizableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72005A1B2266226800ECE090 /* CustomizableButton.swift */; }; + 72005A1F2266226800ECE090 /* CustomizableButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72005A1C2266226800ECE090 /* CustomizableButtonViewModel.swift */; }; + 72005A202266226800ECE090 /* CustomizableButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72005A1D2266226800ECE090 /* CustomizableButtonView.swift */; }; 7295473F21E661E6009558E7 /* TitleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7295473E21E661E6009558E7 /* TitleType.swift */; }; 7295474221E6628C009558E7 /* UINavigationItem+Support.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7295474121E6628C009558E7 /* UINavigationItem+Support.swift */; }; 7295474421E66328009558E7 /* UIViewController+UpdateNavigationItemTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7295474321E66328009558E7 /* UIViewController+UpdateNavigationItemTitle.swift */; }; @@ -729,6 +732,9 @@ 6B5B61443DDAB82927448CAA /* TableKitViewModel+Extenstions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TableKitViewModel+Extenstions.swift"; sourceTree = ""; }; 6B5B62E7942E5AEE68A95449 /* Array+RowExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+RowExtensions.swift"; sourceTree = ""; }; 6B5B66503F2C42D009DEA011 /* TableKitViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableKitViewModel.swift; sourceTree = ""; }; + 72005A1B2266226800ECE090 /* CustomizableButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizableButton.swift; sourceTree = ""; }; + 72005A1C2266226800ECE090 /* CustomizableButtonViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizableButtonViewModel.swift; sourceTree = ""; }; + 72005A1D2266226800ECE090 /* CustomizableButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizableButtonView.swift; sourceTree = ""; }; 7295473E21E661E6009558E7 /* TitleType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleType.swift; sourceTree = ""; }; 7295474121E6628C009558E7 /* UINavigationItem+Support.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationItem+Support.swift"; sourceTree = ""; }; 7295474321E66328009558E7 /* UIViewController+UpdateNavigationItemTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+UpdateNavigationItemTitle.swift"; sourceTree = ""; }; @@ -890,6 +896,7 @@ 671461D41EB3396E00EAB194 /* Views */ = { isa = PBXGroup; children = ( + 72005A1A2266226800ECE090 /* CustomizableButton */, 677B06B6211873E7006C947D /* BasePlaceholderView */, 67DB77672108714A001CB56B /* CollectionViewWrapperView */, 673CF42A2063DE3A00C329F6 /* DefaultPlaceholders */, @@ -1907,6 +1914,16 @@ path = TableKitViewModel; sourceTree = ""; }; + 72005A1A2266226800ECE090 /* CustomizableButton */ = { + isa = PBXGroup; + children = ( + 72005A1B2266226800ECE090 /* CustomizableButton.swift */, + 72005A1C2266226800ECE090 /* CustomizableButtonViewModel.swift */, + 72005A1D2266226800ECE090 /* CustomizableButtonView.swift */, + ); + path = CustomizableButton; + sourceTree = ""; + }; 72AECC68224A979D00D12E7C /* Search */ = { isa = PBXGroup; children = ( @@ -2344,6 +2361,7 @@ files = ( 671463481EB3396E00EAB194 /* ResettableType.swift in Sources */, EFBE57D01EC35EF20040E00A /* Array+Extensions.swift in Sources */, + 72005A202266226800ECE090 /* CustomizableButtonView.swift in Sources */, 6792623C206EB0EC00308E62 /* CellSeparatorType+Extensions.swift in Sources */, 671462E41EB3396E00EAB194 /* UIColor+Hex.swift in Sources */, 67EB7FF12061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift in Sources */, @@ -2361,6 +2379,7 @@ 671463541EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */, 72AECC6B224A979D00D12E7C /* BaseSearchViewController.swift in Sources */, 673CF4112063ABD100C329F6 /* GeneralDataLoadingState+Extensions.swift in Sources */, + 72005A1E2266226800ECE090 /* CustomizableButton.swift in Sources */, 673CF42C2063DE5900C329F6 /* TextPlaceholderView.swift in Sources */, 67ED2BDE20B44DEB00508B3E /* InitializableView.swift in Sources */, 671463601EB3396E00EAB194 /* SupportProtocol.swift in Sources */, @@ -2387,6 +2406,7 @@ 36DAAF512007CC920090BE0D /* UITableView+Extensions.swift in Sources */, 671463841EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, 6774528D20625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */, + 72005A1F2266226800ECE090 /* CustomizableButtonViewModel.swift in Sources */, 677B06C4211884F3006C947D /* BaseTextAttributes.swift in Sources */, 675E0AA921072FF400CDC143 /* BaseScrollContentController.swift in Sources */, 671462D01EB3396E00EAB194 /* UIScrollView+Support.swift in Sources */, diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButton.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButton.swift new file mode 100644 index 00000000..6c3b9db7 --- /dev/null +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButton.swift @@ -0,0 +1,109 @@ +// +// Copyright (c) 2019 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 RxCocoa +import RxSwift + +/// class that is a CustomizableButtonView subview and gives it a button functionality +open class CustomizableButton: UIButton { + + // MARK: - Constants + + private let defaultBackgroundColor = UIColor.white + + // MARK: - Background + + private var backgroundColors: [UIControl.State: UIColor] = [:] { + didSet { + updateBackgroundColor() + } + } + + func set(backgroundColors: [UIControl.State: UIColor]) { + backgroundColors.forEach { setBackgroundColor($1, for: $0) } + } + + func setBackgroundColor(_ color: UIColor, for state: UIControl.State) { + backgroundColors[state] = color + } + + func backgroundColor(for state: UIControl.State) -> UIColor? { + return backgroundColors[state] + } + + private func updateBackgroundColor() { + if isEnabled { + if isHighlighted { + updateBackgroundColor(to: .highlighted) + } else { + updateBackgroundColor(to: .normal) + } + } else { + updateBackgroundColor(to: .disabled) + } + } + + private func updateBackgroundColor(to state: UIControl.State) { + if let stateColor = backgroundColor(for: state) { + backgroundColor = stateColor + } else if state != .normal, let normalStateColor = backgroundColor(for: .normal) { + backgroundColor = normalStateColor + } else { + backgroundColor = defaultBackgroundColor + } + } + + // MARK: - Title + + func set(titleColors: [UIControl.State: UIColor]) { + titleColors.forEach { setTitleColor($1, for: $0) } + } + + func set(titles: [UIControl.State: String]) { + titles.forEach { setTitle($1, for: $0) } + } + + func set(attributtedTitles: [UIControl.State: NSAttributedString]) { + attributtedTitles.forEach { setAttributedTitle($1, for: $0) } + } + + // MARK: - Images + + func set(images: [UIControl.State: UIImage]) { + images.forEach { setImage($1, for: $0) } + } + + // MARK: - State + + override open var isEnabled: Bool { + didSet { + updateBackgroundColor() + } + } + + override open var isHighlighted: Bool { + didSet { + updateBackgroundColor() + } + } +} diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift new file mode 100644 index 00000000..7c0b1e8e --- /dev/null +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift @@ -0,0 +1,346 @@ +// +// Copyright (c) 2019 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 typealias Spinner = UIView & Animatable + +public struct CustomizableButtonState: OptionSet { + + // MARK: - OptionSet conformance + + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + // MARK: - States + + public static let highlighted = CustomizableButtonState(rawValue: 1 << 1) + public static let normal = CustomizableButtonState(rawValue: 1 << 2) + public static let enabled = CustomizableButtonState(rawValue: 1 << 3) + public static let disabled = CustomizableButtonState(rawValue: 1 << 4) + public static let loading = CustomizableButtonState(rawValue: 1 << 5) + + // MARK: - Properties + + public var isLoading: Bool { + return contains(.loading) + } +} + +/// container class that acts like a button and provides great customization +open class CustomizableButtonView: UIView, InitializableView { + + // MARK: - Stored Properties + + private let disposeBag = DisposeBag() + private let button = CustomizableButton() + public var tapOnDisabledButton: VoidBlock? + + public var shadowView = UIView() { + willSet { + shadowView.removeFromSuperview() + } + didSet { + insertSubview(shadowView, at: 0) + configureShadowViewConstraints() + } + } + + public var spinnerView: Spinner? { + willSet { + if newValue == nil { + removeSpinner() + } + } + didSet { + if spinnerView != nil { + addSpinner() + } + } + } + + public var appearance = Appearance() { + didSet { + configureAppearance() + configureConstraints() + } + } + + public var buttonIsDisabledWhileLoading = false + public var hidesLabelWhenLoading = false + + // MARK: - Computed Properties + + public var tapObservable: Observable { + return button.rx.tap.asObservable() + } + + override open var forFirstBaselineLayout: UIView { + return button.forFirstBaselineLayout + } + + override open var forLastBaselineLayout: UIView { + return button.forLastBaselineLayout + } + + override open func touchesBegan(_ touches: Set, with event: UIEvent?) { + if var touchPoint = touches.first?.location(in: self) { + touchPoint = convert(touchPoint, to: self) + if button.frame.contains(touchPoint) && !button.isEnabled { + tapOnDisabledButton?() + } + } + super.touchesBegan(touches, with: event) + } + + // MARK: - Initialization + + override public init(frame: CGRect) { + super.init(frame: frame) + initializeView() + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initializeView() + } + + // MARK: - UI + + override open func layoutSubviews() { + super.layoutSubviews() + if shadowView.layer.cornerRadius == 0 { + shadowView.layer.shadowPath = UIBezierPath(rect: button.bounds).cgPath + } + } + + private func set(active: Bool) { + button.isEnabled = buttonIsDisabledWhileLoading || !active + + if hidesLabelWhenLoading { + button.titleLabel?.layer.opacity = active ? 0 : 1 + } + + spinnerView?.isHidden = !active + + if active { + spinnerView?.startAnimating() + } else { + spinnerView?.stopAnimating() + } + } + + private func addSpinner() { + if let spinner = spinnerView { + addSubview(spinner) + configureSpinnerConstraints() + spinner.isHidden = true + } + } + + private func removeSpinner() { + if spinnerView != nil { + self.spinnerView?.removeFromSuperview() + } + } + + // MARK: - Layout + + private func configureConstraints() { + button.pinToSuperview(with: appearance.buttonInsets) + configureShadowViewConstraints() + } + + private func configureSpinnerConstraints() { + guard let spinnerView = spinnerView else { + return + } + spinnerView.translatesAutoresizingMaskIntoConstraints = false + var constraints = [NSLayoutConstraint]() + switch appearance.spinnerPosition { + case .center: + constraints = [ + spinnerView.centerXAnchor.constraint(equalTo: button.centerXAnchor), + spinnerView.centerYAnchor.constraint(equalTo: button.centerYAnchor) + ] + case .leftToText(let offset): + if let buttonLabel = button.titleLabel { + constraints = [ + spinnerView.centerYAnchor.constraint(equalTo: buttonLabel.centerYAnchor), + spinnerView.trailingAnchor.constraint(equalTo: buttonLabel.leadingAnchor, constant: -offset) + ] + } + case .rightToText(let offset): + if let buttonLabel = button.titleLabel { + constraints = [ + spinnerView.centerYAnchor.constraint(equalTo: buttonLabel.centerYAnchor), + spinnerView.leadingAnchor.constraint(equalTo: buttonLabel.trailingAnchor, constant: offset) + ] + } + } + + NSLayoutConstraint.activate(constraints) + } + + private func configureShadowViewConstraints() { + shadowView.constaintToEdges(of: button, with: .zero) + } + + // MARK: - Initializable View + + public func addViews() { + addSubviews(shadowView, button) + } + + public func configureAppearance() { + button.titleLabel?.numberOfLines = appearance.numberOfLines + button.titleLabel?.font = appearance.buttonFont + + button.set(titles: appearance.buttonStateTitles) + button.set(attributtedTitles: appearance.buttonStateAttributtedTitles) + button.set(titleColors: appearance.buttonTitleStateColors) + button.set(images: appearance.buttonStateIcons) + button.set(backgroundColors: appearance.buttonBackgroundStateColors) + + let offset = appearance.buttonIconOffset + button.imageEdgeInsets = UIEdgeInsets(top: offset.vertical, + left: offset.horizontal, + bottom: -offset.vertical, + right: -offset.horizontal) + + if let cornerRadius = appearance.buttonCornerRadius { + button.layer.cornerRadius = cornerRadius + } else { + button.layer.cornerRadius = 0 + } + + button.titleLabel?.isHidden = true + } +} + +private extension UIView { + func constaintToEdges(of view: UIView, with offset: UIEdgeInsets) { + translatesAutoresizingMaskIntoConstraints = false + let constraints = [ + leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: offset.left), + trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: offset.right), + topAnchor.constraint(equalTo: view.topAnchor, constant: offset.top), + bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: offset.bottom), + ] + NSLayoutConstraint.activate(constraints) + } +} + +extension CustomizableButtonView: ConfigurableView { + public func configure(with viewModel: CustomizableButtonViewModel) { + viewModel.stateDriver.drive(stateBinder).disposed(by: disposeBag) + viewModel.bind(tapObservable: tapObservable).disposed(by: disposeBag) + + appearance = viewModel.appearance + } + + private var stateBinder: Binder { + return Binder(self) { base, value in + base.configureButton(withState: value) + base.onStateChange(value) + } + } + + open func onStateChange(_ state: CustomizableButtonState) { + /// override in subclass + } + + open func configureButton(withState state: CustomizableButtonState) { + button.isEnabled = state.contains(.enabled) && !state.contains(.disabled) + button.isHighlighted = state.contains(.highlighted) && !state.contains(.normal) + set(active: state.contains(.loading)) + } +} + +public extension CustomizableButtonView { + struct Appearance { + + var buttonFont: UIFont + + var buttonStateTitles: [UIControl.State: String] + var buttonStateAttributtedTitles: [UIControl.State: NSAttributedString] + var buttonTitleStateColors: [UIControl.State: UIColor] + var buttonBackgroundStateColors: [UIControl.State: UIColor] + var buttonStateIcons: [UIControl.State: UIImage] + + var buttonIconOffset: UIOffset + var buttonInsets: UIEdgeInsets + + var buttonCornerRadius: CGFloat? + + var spinnerPosition: SpinnerPosition + + var numberOfLines: Int + + public init(buttonFont: UIFont = .systemFont(ofSize: 15), + buttonStateTitles: [UIControl.State: String] = [:], + buttonStateAttributtedTitles: [UIControl.State: NSAttributedString] = [:], + buttonTitleStateColors: [UIControl.State: UIColor] = [:], + buttonBackgroundStateColors: [UIControl.State: UIColor] = [:], + buttonStateIcons: [UIControl.State: UIImage] = [:], + buttonIconOffset: UIOffset = .zero, + buttonInsets: UIEdgeInsets = .zero, + buttonCornerRadius: CGFloat? = nil, + spinnerPosition: SpinnerPosition = .center, + numberOfLines: Int = 0) { + + self.buttonFont = buttonFont + + self.buttonStateTitles = buttonStateTitles + self.buttonStateAttributtedTitles = buttonStateAttributtedTitles + self.buttonTitleStateColors = buttonTitleStateColors + self.buttonBackgroundStateColors = buttonBackgroundStateColors + self.buttonStateIcons = buttonStateIcons + + self.buttonIconOffset = buttonIconOffset + self.buttonInsets = buttonInsets + + self.buttonCornerRadius = buttonCornerRadius + + self.spinnerPosition = spinnerPosition + + self.numberOfLines = numberOfLines + } + + } + + enum SpinnerPosition { + case center + case leftToText(offset: CGFloat) + case rightToText(offset: CGFloat) + } +} + +extension UIControl.State: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(Int(rawValue)) + } +} diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift new file mode 100644 index 00000000..bcd6e230 --- /dev/null +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift @@ -0,0 +1,54 @@ +// +// Copyright (c) 2019 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 +import RxSwift + +/// viewModel class for CustomizableButtonView configuration +open class CustomizableButtonViewModel { + + public typealias Appearance = CustomizableButtonView.Appearance + + private let stateRelay = BehaviorRelay(value: CustomizableButtonState.enabled) + private let tapRelay = BehaviorRelay(value: ()) + public let appearance: Appearance + + public init(appearance: Appearance) { + self.appearance = appearance + } + + open var stateDriver: Driver { + return stateRelay.asDriver() + } + + func bind(tapObservable: Observable) -> Disposable { + return tapObservable.bind(to: tapRelay) + } + + public var tapDriver: Driver { + return tapRelay.asDriver() + } + + public func updateState(with newState: CustomizableButtonState) { + stateRelay.accept(newState) + } +} diff --git a/Sources/Info-iOS.plist b/Sources/Info-iOS.plist index a15de67d..a8ca2d1d 100644 --- a/Sources/Info-iOS.plist +++ b/Sources/Info-iOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.18 + 0.9.19 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Sources/Info-tvOS.plist b/Sources/Info-tvOS.plist index a15de67d..a8ca2d1d 100644 --- a/Sources/Info-tvOS.plist +++ b/Sources/Info-tvOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.18 + 0.9.19 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Sources/Info-watchOS.plist b/Sources/Info-watchOS.plist index a15de67d..a8ca2d1d 100644 --- a/Sources/Info-watchOS.plist +++ b/Sources/Info-watchOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.9.18 + 0.9.19 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/build-scripts b/build-scripts index d4a7dce0..014dba7a 160000 --- a/build-scripts +++ b/build-scripts @@ -1 +1 @@ -Subproject commit d4a7dce0d7f17617efb3dffa4ca7f64ec8a85e9f +Subproject commit 014dba7afd678d41dabe1bab9962ba27b629c707