From 00383039b77764f380ada70aef2f1c512c765bc5 Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 16 Apr 2019 18:08:10 +0300 Subject: [PATCH 01/17] Build scripts were updated --- build-scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-scripts b/build-scripts index 05ed714e..d4a7dce0 160000 --- a/build-scripts +++ b/build-scripts @@ -1 +1 @@ -Subproject commit 05ed714e9f9dd228662a98cc909d1e8175e02e71 +Subproject commit d4a7dce0d7f17617efb3dffa4ca7f64ec8a85e9f From 8d0320fe266d15ef4093d398220b258c3e7545e6 Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 16 Apr 2019 18:08:28 +0300 Subject: [PATCH 02/17] Customizable button was added --- LeadKit.xcodeproj/project.pbxproj | 29 +- .../Views/BigBossButton/BigBossButton.swift | 108 +++++ .../BigBossButton/BigBossButtonView.swift | 379 ++++++++++++++++++ .../BigBossButtonViewModel.swift | 53 +++ 4 files changed, 560 insertions(+), 9 deletions(-) create mode 100644 Sources/Classes/Views/BigBossButton/BigBossButton.swift create mode 100644 Sources/Classes/Views/BigBossButton/BigBossButtonView.swift create mode 100644 Sources/Classes/Views/BigBossButton/BigBossButtonViewModel.swift diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index dc42048a..ec19843d 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 /* BigBossButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72005A1B2266226800ECE090 /* BigBossButton.swift */; }; + 72005A1F2266226800ECE090 /* BigBossButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72005A1C2266226800ECE090 /* BigBossButtonViewModel.swift */; }; + 72005A202266226800ECE090 /* BigBossButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72005A1D2266226800ECE090 /* BigBossButtonView.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 */; }; @@ -726,6 +729,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 /* BigBossButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BigBossButton.swift; sourceTree = ""; }; + 72005A1C2266226800ECE090 /* BigBossButtonViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BigBossButtonViewModel.swift; sourceTree = ""; }; + 72005A1D2266226800ECE090 /* BigBossButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BigBossButtonView.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 = ""; }; @@ -844,7 +850,6 @@ 6741CEC520E2434900FEC4D9 /* Controllers */, 72AECC68224A979D00D12E7C /* Search */, 6774527E2062566D0024EEEF /* DataLoading */, - 72D213A222048162003B4292 /* Search */, 671461D21EB3396E00EAB194 /* Services */, 671461D41EB3396E00EAB194 /* Views */, ); @@ -884,6 +889,7 @@ 671461D41EB3396E00EAB194 /* Views */ = { isa = PBXGroup; children = ( + 72005A1A2266226800ECE090 /* BigBossButton */, 677B06B6211873E7006C947D /* BasePlaceholderView */, 67DB77672108714A001CB56B /* CollectionViewWrapperView */, 673CF42A2063DE3A00C329F6 /* DefaultPlaceholders */, @@ -902,7 +908,6 @@ 671461D61EB3396E00EAB194 /* Enums */ = { isa = PBXGroup; children = ( - 72039CDE220899D000875DD4 /* Search */, 67274767206CCB6F00725163 /* Views */, 72AECC6D224A97B100D12E7C /* Search */, 6774528A20625C860024EEEF /* DataLoading */, @@ -1138,7 +1143,6 @@ 678D269C20692BFF00B05B93 /* Views */, 671462341EB3396E00EAB194 /* XibNameProtocol.swift */, 6B5B625D01107D0A6F4E14D2 /* TableKit */, - 72527D26222E934100CA26BE /* OptionalType.swift */, ); path = Protocols; sourceTree = ""; @@ -1902,6 +1906,16 @@ path = TableKitViewModel; sourceTree = ""; }; + 72005A1A2266226800ECE090 /* BigBossButton */ = { + isa = PBXGroup; + children = ( + 72005A1B2266226800ECE090 /* BigBossButton.swift */, + 72005A1C2266226800ECE090 /* BigBossButtonViewModel.swift */, + 72005A1D2266226800ECE090 /* BigBossButtonView.swift */, + ); + path = BigBossButton; + sourceTree = ""; + }; 72AECC68224A979D00D12E7C /* Search */ = { isa = PBXGroup; children = ( @@ -2329,6 +2343,7 @@ files = ( 671463481EB3396E00EAB194 /* ResettableType.swift in Sources */, EFBE57D01EC35EF20040E00A /* Array+Extensions.swift in Sources */, + 72005A202266226800ECE090 /* BigBossButtonView.swift in Sources */, 6792623C206EB0EC00308E62 /* CellSeparatorType+Extensions.swift in Sources */, 671462E41EB3396E00EAB194 /* UIColor+Hex.swift in Sources */, 67EB7FF12061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift in Sources */, @@ -2341,12 +2356,12 @@ 6727478F206CD88600725163 /* DateFormattingService.swift in Sources */, 678D26A420692BFF00B05B93 /* TextFieldViewModelEvents.swift in Sources */, 671462801EB3396E00EAB194 /* DataRequest+Extensions.swift in Sources */, - 7284087622079A4600A20F47 /* SearchResultsViewController.swift in Sources */, 67EB7FF8206175F700BDD9FB /* PaginationWrappable.swift in Sources */, 67990AD6213EA6A50040D195 /* ContentLoadingViewModel+Extensions.swift in Sources */, 671463541EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */, 72AECC6B224A979D00D12E7C /* BaseSearchViewController.swift in Sources */, 673CF4112063ABD100C329F6 /* GeneralDataLoadingState+Extensions.swift in Sources */, + 72005A1E2266226800ECE090 /* BigBossButton.swift in Sources */, 673CF42C2063DE5900C329F6 /* TextPlaceholderView.swift in Sources */, 67ED2BDE20B44DEB00508B3E /* InitializableView.swift in Sources */, 671463601EB3396E00EAB194 /* SupportProtocol.swift in Sources */, @@ -2373,6 +2388,7 @@ 36DAAF512007CC920090BE0D /* UITableView+Extensions.swift in Sources */, 671463841EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, 6774528D20625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */, + 72005A1F2266226800ECE090 /* BigBossButtonViewModel.swift in Sources */, 677B06C4211884F3006C947D /* BaseTextAttributes.swift in Sources */, 675E0AA921072FF400CDC143 /* BaseScrollContentController.swift in Sources */, 671462D01EB3396E00EAB194 /* UIScrollView+Support.swift in Sources */, @@ -2409,7 +2425,6 @@ 6714627C1EB3396E00EAB194 /* SessionManager+Extensions.swift in Sources */, 671462D41EB3396E00EAB194 /* TableDirector+Extensions.swift in Sources */, 67E352572119ACF30035BDDB /* ViewTextConfigurable+Extensions.swift in Sources */, - 72527D27222E934100CA26BE /* OptionalType.swift in Sources */, 67990AC5213EA4DB0040D195 /* PlaceholderConfigurable.swift in Sources */, 6741CEBE20E242FA00FEC4D9 /* UIScrollView+RxBindings.swift in Sources */, 67E902572125B66E008EDF45 /* UIImageView+ExpandCollapseDisclosure.swift in Sources */, @@ -2445,10 +2460,8 @@ 6727477F206CD3BD00725163 /* ViewText+Extensions.swift in Sources */, 67EB7FEB2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift in Sources */, 671AD26C206A3E8500EAF887 /* Array+TotalCountCursorListingResult.swift in Sources */, - 72D213A422048180003B4292 /* BaseSearchViewController.swift in Sources */, 673CF4382063E7CE00C329F6 /* GeneralDataLoadingController+DefaultImplementation.swift in Sources */, B85B768720B1CF6700F837C4 /* Encodable+Extensions.swift in Sources */, - 72039CE0220899E600875DD4 /* SearchResultsViewControllerState.swift in Sources */, 67E9024B2125AEB4008EDF45 /* NSNumberConvertible.swift in Sources */, 673CF40B2063AB7C00C329F6 /* GeneralDataLoadingViewModel.swift in Sources */, 6713C23720AF0C4D00875921 /* NetworkOperationState.swift in Sources */, @@ -2477,7 +2490,6 @@ 6774527020624A2A0024EEEF /* PaginationWrapperUIDelegate+DefaultImplementation.swift in Sources */, 6727478A206CD83600725163 /* DateFormat.swift in Sources */, 67745280206256A20024EEEF /* RxDataLoadingModel.swift in Sources */, - 7284087422078EB800A20F47 /* BaseSearchViewModel.swift in Sources */, A6F32C081F6EBDAA00AC08EE /* String+LocalizedComponent.swift in Sources */, 671462881EB3396E00EAB194 /* CGFloat+Pixels.swift in Sources */, 671462941EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */, @@ -2790,7 +2802,6 @@ 67990AE8213EB4080040D195 /* ConfigurableView+Extensions.swift in Sources */, 677452A720625FA90024EEEF /* RxDataSource.swift in Sources */, 6741CEBC20E242D900FEC4D9 /* UIScrollView+ScrollViewHolder.swift in Sources */, - 72039CE222089A3D00875DD4 /* SearchResultsViewController.swift in Sources */, 6774527720624E820024EEEF /* DataLoadingModel.swift in Sources */, 67926239206EB0AE00308E62 /* CellSeparatorType.swift in Sources */, 678D267C20691D8200B05B93 /* DataModelFieldBinding.swift in Sources */, diff --git a/Sources/Classes/Views/BigBossButton/BigBossButton.swift b/Sources/Classes/Views/BigBossButton/BigBossButton.swift new file mode 100644 index 00000000..820cb63d --- /dev/null +++ b/Sources/Classes/Views/BigBossButton/BigBossButton.swift @@ -0,0 +1,108 @@ +// +// 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 + +open class BigBossButton: 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/BigBossButton/BigBossButtonView.swift b/Sources/Classes/Views/BigBossButton/BigBossButtonView.swift new file mode 100644 index 00000000..feef1ec3 --- /dev/null +++ b/Sources/Classes/Views/BigBossButton/BigBossButtonView.swift @@ -0,0 +1,379 @@ +// +// 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 BigBossButtonState: OptionSet { + + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + static let highlighted = BigBossButtonState(rawValue: 1 << 1) + static let unhighlighted = BigBossButtonState(rawValue: 1 << 2) + static let enabled = BigBossButtonState(rawValue: 1 << 3) + static let disabled = BigBossButtonState(rawValue: 1 << 4) + + static let loading = BigBossButtonState(rawValue: 1 << 5) + + var isLoading: Bool { + return self.contains(.loading) + } +} + +open class BigBossButtonView: UIView { + + private let disposeBag = DisposeBag() + + 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: - Stored Properties + + public var spinnerView: Spinner? { + willSet { + if newValue == nil { + removeSpinner() + } + } + + didSet { + if spinnerView != nil { + addSpinner() + } + } + } + + private let button = BigBossButton() + + public var shadowView = UIView() { + willSet { + shadowView.removeFromSuperview() + } + didSet { + insertSubview(shadowView, at: 0) + configureShadowViewConstraints() + } + } + + private var isEnabledObserver: NSKeyValueObservation? + private var isHighlightedObserver: NSKeyValueObservation? + + public var tapOnDisabledButton: VoidBlock? + + public var appearance = Appearance() { + didSet { + configureAppearance() + configureConstraints() + } + } + + // MARK: - Initialization + + override public init(frame: CGRect) { + super.init(frame: frame) + initializeView() + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initializeView() + } + + // MARK: - Drivers + + open var tapObservable: Observable { + return button.rx.tap.asObservable() + } + + // MARK: - Button State Observation + + // private func observeIsEnabled() -> NSKeyValueObservation { + // return button.observe(\BigBossButton.isEnabled, options: .new) { [weak self] _, isEnabled in + // + // guard let self = self else { + // return + // } + // + // if let isEnabled = isEnabled.newValue { + // var state = self.stateRelay.value + // state.subtract([.enabled, .disabled]) + // state.insert(isEnabled ? .enabled : .disabled) + // self.stateRelay.accept(state) + // } + // } + // } + // + // private func observeIsHighlighted() -> NSKeyValueObservation { + // return button.observe(\BigBossButton.isHighlighted, options: .new, + // changeHandler: { [weak self] _, isHighlighted in + // + // guard let self = self else { + // return + // } + // + // if let isHighlighted = isHighlighted.newValue { + // var state = self.stateRelay.value + // state.subtract([.highlighted, .unhighlighted]) + // state.insert(isHighlighted ? .highlighted : .unhighlighted) + // self.stateRelay.accept(state) + // } + // }) + // } + + // MARK: - UI + + override open func layoutSubviews() { + super.layoutSubviews() + shadowView.layer.shadowPath = UIBezierPath(rect: button.bounds).cgPath + } + + public var buttonIsDisabledWhileLoading = false + + private func set(active: Bool) { + button.isEnabled = buttonIsDisabledWhileLoading ? !active : true + if active { + spinnerView?.isHidden = false + spinnerView?.startAnimating() + } else { + spinnerView?.isHidden = true + spinnerView?.stopAnimating() + } + } + + private func addSpinner() { + if let spinner = spinnerView { + addSubview(spinner) + configureSpinnerConstraints() + } + } + + private func removeSpinner() { + if spinnerView != nil { + self.spinnerView?.removeFromSuperview() + self.spinnerView = nil + } + } + + // MARK: - Layout + + override open var forFirstBaselineLayout: UIView { + return button.forFirstBaselineLayout + } + + override open var forLastBaselineLayout: UIView { + return button.forLastBaselineLayout + } + + private func configureConstraints() { + button.constaintToEdges(of: self, with: appearance.buttonInsets) + configureShadowViewConstraints() + } + + private func configureSpinnerConstraints() { + switch appearance.spinnerPosition { + case .center: + spinnerView?.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true + spinnerView?.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true + case .leftToText(let offset): + if let buttonLabel = button.titleLabel { + spinnerView?.centerYAnchor.constraint(equalTo: buttonLabel.centerYAnchor).isActive = true + spinnerView?.trailingAnchor.constraint(equalTo: buttonLabel.leadingAnchor, + constant: -offset).isActive = true + } + case .rightToText(let offset): + if let buttonLabel = button.titleLabel { + spinnerView?.centerYAnchor.constraint(equalTo: buttonLabel.centerYAnchor).isActive = true + spinnerView?.leadingAnchor.constraint(equalTo: buttonLabel.trailingAnchor, + constant: offset).isActive = true + } + } + } + + private func configureShadowViewConstraints() { + shadowView.constaintToEdges(of: button, with: .zero) + } +} + +private extension UIView { + func constaintToEdges(of view: UIView, with offset: UIEdgeInsets) { + self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: offset.left).isActive = true + self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: offset.right).isActive = true + self.topAnchor.constraint(equalTo: view.topAnchor, constant: offset.top).isActive = true + self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: offset.bottom).isActive = true + } +} + +extension BigBossButtonView: InitializableView { + + public func addViews() { + addSubviews(shadowView, button) + } + + public func configureAppearance() { + 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 + } + } +} + +extension BigBossButtonView: ConfigurableView { + public func configure(with viewModel: BigBossButtonViewModel) { + button.titleLabel?.numberOfLines = 0 + viewModel.stateObservable + .skip(1) + .do(onNext: { [weak self] state in + self?.configureButton(withState: state) + self?.onStateChange(state) + }) + .subscribe() + .disposed(by: disposeBag) + + viewModel + .bind(tapObservable: tapObservable) + .disposed(by: disposeBag) + + appearance = viewModel.appearance + } + + open func onStateChange(_ state: BigBossButtonState) { + + } + + open func configureButton(withState state: BigBossButtonState) { + if state.contains(.enabled) { + button.isEnabled = true + } + + if state.contains(.disabled) { + button.isEnabled = false + } + + if state.contains(.highlighted) { + button.isHighlighted = true + } + + if state.contains(.unhighlighted) { + button.isHighlighted = false + } + + if state.contains(.loading) { + set(active: true) + } else { + set(active: false) + } + } +} + +public extension BigBossButtonView { + 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 buttonHeight: CGFloat + var buttonCornerRadius: CGFloat? + + var buttonShadowPadding: CGFloat + var spinnerPosition: SpinnerPosition + + 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, + buttonHeight: CGFloat = 50, + buttonCornerRadius: CGFloat? = nil, + buttonShadowPadding: CGFloat = 0, + spinnerPosition: SpinnerPosition = .center + ) { + + 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.buttonHeight = buttonHeight + self.buttonCornerRadius = buttonCornerRadius + + self.buttonShadowPadding = buttonShadowPadding + self.spinnerPosition = spinnerPosition + } + + } + + enum SpinnerPosition { + case center + case leftToText(offset: CGFloat) + case rightToText(offset: CGFloat) + } +} + +extension UIControl.State: Hashable { + //swiftlint:disable:next - inout_keyword + public func hash(into hasher: inout Hasher) { + hasher.combine(Int(rawValue)) + } +} diff --git a/Sources/Classes/Views/BigBossButton/BigBossButtonViewModel.swift b/Sources/Classes/Views/BigBossButton/BigBossButtonViewModel.swift new file mode 100644 index 00000000..13961811 --- /dev/null +++ b/Sources/Classes/Views/BigBossButton/BigBossButtonViewModel.swift @@ -0,0 +1,53 @@ +// +// 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 + +open class BigBossButtonViewModel { + + public typealias Appearance = BigBossButtonView.Appearance + + private let stateRelay = BehaviorRelay(value: BigBossButtonState.enabled) + private let tapRelay = BehaviorRelay(value: ()) + public let appearance: Appearance + + public init(appearance: Appearance) { + self.appearance = appearance + } + + open var stateObservable: Observable { + return stateRelay.asObservable() + } + + func bind(tapObservable: Observable) -> Disposable { + return tapObservable.bind(to: tapRelay) + } + + var tapDriver: Driver { + return tapRelay.asDriver() + } + + func updateState(with newState: BigBossButtonState) { + stateRelay.accept(newState) + } +} From 05ce8e9d4f01da5fa0d0c536b68cd34038268a45 Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 16 Apr 2019 18:10:26 +0300 Subject: [PATCH 03/17] Class was renamed --- LeadKit.xcodeproj/project.pbxproj | 24 +++++++++---------- ...sButton.swift => CustomizableButton.swift} | 2 +- ...iew.swift => CustomizableButtonView.swift} | 12 +++++----- ...wift => CustomizableButtonViewModel.swift} | 4 ++-- 4 files changed, 21 insertions(+), 21 deletions(-) rename Sources/Classes/Views/BigBossButton/{BigBossButton.swift => CustomizableButton.swift} (98%) rename Sources/Classes/Views/BigBossButton/{BigBossButtonView.swift => CustomizableButtonView.swift} (97%) rename Sources/Classes/Views/BigBossButton/{BigBossButtonViewModel.swift => CustomizableButtonViewModel.swift} (94%) diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index ec19843d..bfd80b9d 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -453,9 +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 /* BigBossButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72005A1B2266226800ECE090 /* BigBossButton.swift */; }; - 72005A1F2266226800ECE090 /* BigBossButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72005A1C2266226800ECE090 /* BigBossButtonViewModel.swift */; }; - 72005A202266226800ECE090 /* BigBossButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72005A1D2266226800ECE090 /* BigBossButtonView.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,9 +729,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 /* BigBossButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BigBossButton.swift; sourceTree = ""; }; - 72005A1C2266226800ECE090 /* BigBossButtonViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BigBossButtonViewModel.swift; sourceTree = ""; }; - 72005A1D2266226800ECE090 /* BigBossButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BigBossButtonView.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 = ""; }; @@ -1909,9 +1909,9 @@ 72005A1A2266226800ECE090 /* BigBossButton */ = { isa = PBXGroup; children = ( - 72005A1B2266226800ECE090 /* BigBossButton.swift */, - 72005A1C2266226800ECE090 /* BigBossButtonViewModel.swift */, - 72005A1D2266226800ECE090 /* BigBossButtonView.swift */, + 72005A1B2266226800ECE090 /* CustomizableButton.swift */, + 72005A1C2266226800ECE090 /* CustomizableButtonViewModel.swift */, + 72005A1D2266226800ECE090 /* CustomizableButtonView.swift */, ); path = BigBossButton; sourceTree = ""; @@ -2343,7 +2343,7 @@ files = ( 671463481EB3396E00EAB194 /* ResettableType.swift in Sources */, EFBE57D01EC35EF20040E00A /* Array+Extensions.swift in Sources */, - 72005A202266226800ECE090 /* BigBossButtonView.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,7 +2361,7 @@ 671463541EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */, 72AECC6B224A979D00D12E7C /* BaseSearchViewController.swift in Sources */, 673CF4112063ABD100C329F6 /* GeneralDataLoadingState+Extensions.swift in Sources */, - 72005A1E2266226800ECE090 /* BigBossButton.swift in Sources */, + 72005A1E2266226800ECE090 /* CustomizableButton.swift in Sources */, 673CF42C2063DE5900C329F6 /* TextPlaceholderView.swift in Sources */, 67ED2BDE20B44DEB00508B3E /* InitializableView.swift in Sources */, 671463601EB3396E00EAB194 /* SupportProtocol.swift in Sources */, @@ -2388,7 +2388,7 @@ 36DAAF512007CC920090BE0D /* UITableView+Extensions.swift in Sources */, 671463841EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, 6774528D20625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */, - 72005A1F2266226800ECE090 /* BigBossButtonViewModel.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/BigBossButton/BigBossButton.swift b/Sources/Classes/Views/BigBossButton/CustomizableButton.swift similarity index 98% rename from Sources/Classes/Views/BigBossButton/BigBossButton.swift rename to Sources/Classes/Views/BigBossButton/CustomizableButton.swift index 820cb63d..445d48f9 100644 --- a/Sources/Classes/Views/BigBossButton/BigBossButton.swift +++ b/Sources/Classes/Views/BigBossButton/CustomizableButton.swift @@ -24,7 +24,7 @@ import UIKit import RxCocoa import RxSwift -open class BigBossButton: UIButton { +open class CustomizableButton: UIButton { // MARK: - Constants diff --git a/Sources/Classes/Views/BigBossButton/BigBossButtonView.swift b/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift similarity index 97% rename from Sources/Classes/Views/BigBossButton/BigBossButtonView.swift rename to Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift index feef1ec3..7b109ce6 100644 --- a/Sources/Classes/Views/BigBossButton/BigBossButtonView.swift +++ b/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift @@ -45,7 +45,7 @@ public struct BigBossButtonState: OptionSet { } } -open class BigBossButtonView: UIView { +open class CustomizableButtonView: UIView { private let disposeBag = DisposeBag() @@ -75,7 +75,7 @@ open class BigBossButtonView: UIView { } } - private let button = BigBossButton() + private let button = CustomizableButton() public var shadowView = UIView() { willSet { @@ -235,7 +235,7 @@ private extension UIView { } } -extension BigBossButtonView: InitializableView { +extension CustomizableButtonView: InitializableView { public func addViews() { addSubviews(shadowView, button) @@ -262,8 +262,8 @@ extension BigBossButtonView: InitializableView { } } -extension BigBossButtonView: ConfigurableView { - public func configure(with viewModel: BigBossButtonViewModel) { +extension CustomizableButtonView: ConfigurableView { + public func configure(with viewModel: CustomizableButtonViewModel) { button.titleLabel?.numberOfLines = 0 viewModel.stateObservable .skip(1) @@ -310,7 +310,7 @@ extension BigBossButtonView: ConfigurableView { } } -public extension BigBossButtonView { +public extension CustomizableButtonView { struct Appearance { var buttonFont: UIFont diff --git a/Sources/Classes/Views/BigBossButton/BigBossButtonViewModel.swift b/Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift similarity index 94% rename from Sources/Classes/Views/BigBossButton/BigBossButtonViewModel.swift rename to Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift index 13961811..9c19e6fd 100644 --- a/Sources/Classes/Views/BigBossButton/BigBossButtonViewModel.swift +++ b/Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift @@ -23,9 +23,9 @@ import RxCocoa import RxSwift -open class BigBossButtonViewModel { +open class CustomizableButtonViewModel { - public typealias Appearance = BigBossButtonView.Appearance + public typealias Appearance = CustomizableButtonView.Appearance private let stateRelay = BehaviorRelay(value: BigBossButtonState.enabled) private let tapRelay = BehaviorRelay(value: ()) From 544d5bc248f3fdab92d30b566179bc29de5c3fcd Mon Sep 17 00:00:00 2001 From: Artur Date: Fri, 19 Apr 2019 17:58:39 +0300 Subject: [PATCH 04/17] Issues resolved --- .../CustomizableButtonView.swift | 153 +++++++----------- .../CustomizableButtonViewModel.swift | 4 +- 2 files changed, 62 insertions(+), 95 deletions(-) diff --git a/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift b/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift index 7b109ce6..3a3228b4 100644 --- a/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift @@ -27,40 +27,47 @@ public typealias Spinner = UIView & Animatable public struct BigBossButtonState: OptionSet { + // MARK: - OptionSet conformance + public let rawValue: Int public init(rawValue: Int) { self.rawValue = rawValue } + // MARK: - States + static let highlighted = BigBossButtonState(rawValue: 1 << 1) - static let unhighlighted = BigBossButtonState(rawValue: 1 << 2) + static let normal = BigBossButtonState(rawValue: 1 << 2) static let enabled = BigBossButtonState(rawValue: 1 << 3) static let disabled = BigBossButtonState(rawValue: 1 << 4) - static let loading = BigBossButtonState(rawValue: 1 << 5) + // MARK: - Properties + var isLoading: Bool { - return self.contains(.loading) + return contains(.loading) } } open class CustomizableButtonView: UIView { - private let disposeBag = DisposeBag() - - 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: - Stored Properties + private let disposeBag = DisposeBag() + private let button = BigBossButton() + 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 { @@ -75,23 +82,6 @@ open class CustomizableButtonView: UIView { } } - private let button = CustomizableButton() - - public var shadowView = UIView() { - willSet { - shadowView.removeFromSuperview() - } - didSet { - insertSubview(shadowView, at: 0) - configureShadowViewConstraints() - } - } - - private var isEnabledObserver: NSKeyValueObservation? - private var isHighlightedObserver: NSKeyValueObservation? - - public var tapOnDisabledButton: VoidBlock? - public var appearance = Appearance() { didSet { configureAppearance() @@ -99,6 +89,32 @@ open class CustomizableButtonView: UIView { } } + public var buttonIsDisabledWhileLoading = false + + // MARK: - Computed Properties + + open 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) { @@ -111,47 +127,6 @@ open class CustomizableButtonView: UIView { initializeView() } - // MARK: - Drivers - - open var tapObservable: Observable { - return button.rx.tap.asObservable() - } - - // MARK: - Button State Observation - - // private func observeIsEnabled() -> NSKeyValueObservation { - // return button.observe(\BigBossButton.isEnabled, options: .new) { [weak self] _, isEnabled in - // - // guard let self = self else { - // return - // } - // - // if let isEnabled = isEnabled.newValue { - // var state = self.stateRelay.value - // state.subtract([.enabled, .disabled]) - // state.insert(isEnabled ? .enabled : .disabled) - // self.stateRelay.accept(state) - // } - // } - // } - // - // private func observeIsHighlighted() -> NSKeyValueObservation { - // return button.observe(\BigBossButton.isHighlighted, options: .new, - // changeHandler: { [weak self] _, isHighlighted in - // - // guard let self = self else { - // return - // } - // - // if let isHighlighted = isHighlighted.newValue { - // var state = self.stateRelay.value - // state.subtract([.highlighted, .unhighlighted]) - // state.insert(isHighlighted ? .highlighted : .unhighlighted) - // self.stateRelay.accept(state) - // } - // }) - // } - // MARK: - UI override open func layoutSubviews() { @@ -159,8 +134,6 @@ open class CustomizableButtonView: UIView { shadowView.layer.shadowPath = UIBezierPath(rect: button.bounds).cgPath } - public var buttonIsDisabledWhileLoading = false - private func set(active: Bool) { button.isEnabled = buttonIsDisabledWhileLoading ? !active : true if active { @@ -188,16 +161,8 @@ open class CustomizableButtonView: UIView { // MARK: - Layout - override open var forFirstBaselineLayout: UIView { - return button.forFirstBaselineLayout - } - - override open var forLastBaselineLayout: UIView { - return button.forLastBaselineLayout - } - private func configureConstraints() { - button.constaintToEdges(of: self, with: appearance.buttonInsets) + button.pinToSuperview(with: appearance.buttonInsets) configureShadowViewConstraints() } @@ -265,13 +230,9 @@ extension CustomizableButtonView: InitializableView { extension CustomizableButtonView: ConfigurableView { public func configure(with viewModel: CustomizableButtonViewModel) { button.titleLabel?.numberOfLines = 0 - viewModel.stateObservable + viewModel.stateDriver .skip(1) - .do(onNext: { [weak self] state in - self?.configureButton(withState: state) - self?.onStateChange(state) - }) - .subscribe() + .drive(stateBinder) .disposed(by: disposeBag) viewModel @@ -281,8 +242,15 @@ extension CustomizableButtonView: ConfigurableView { appearance = viewModel.appearance } - open func onStateChange(_ state: BigBossButtonState) { + private var stateBinder: Binder { + return Binder(self) { base, value in + base.configureButton(withState: value) + base.onStateChange(value) + } + } + open func onStateChange(_ state: BigBossButtonState) { + /// override in subclass } open func configureButton(withState state: BigBossButtonState) { @@ -298,7 +266,7 @@ extension CustomizableButtonView: ConfigurableView { button.isHighlighted = true } - if state.contains(.unhighlighted) { + if state.contains(.normal) { button.isHighlighted = false } @@ -372,7 +340,6 @@ public extension CustomizableButtonView { } extension UIControl.State: Hashable { - //swiftlint:disable:next - inout_keyword public func hash(into hasher: inout Hasher) { hasher.combine(Int(rawValue)) } diff --git a/Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift b/Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift index 9c19e6fd..3af19baf 100644 --- a/Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift +++ b/Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift @@ -35,8 +35,8 @@ open class CustomizableButtonViewModel { self.appearance = appearance } - open var stateObservable: Observable { - return stateRelay.asObservable() + open var stateDriver: Driver { + return stateRelay.asDriver() } func bind(tapObservable: Observable) -> Disposable { From 223ca6a67e8f05bbe414b47bfc7caba672141279 Mon Sep 17 00:00:00 2001 From: Artur Date: Fri, 19 Apr 2019 18:00:36 +0300 Subject: [PATCH 05/17] Typo fixed --- .../Classes/Views/BigBossButton/CustomizableButtonView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift b/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift index 3a3228b4..f0409cc7 100644 --- a/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift @@ -55,7 +55,7 @@ open class CustomizableButtonView: UIView { // MARK: - Stored Properties private let disposeBag = DisposeBag() - private let button = BigBossButton() + private let button = CustomizableButton() public var tapOnDisabledButton: VoidBlock? public var shadowView = UIView() { From 08b87a07822847fe937036a72d7ed9830d989c60 Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 22 Apr 2019 19:42:18 +0300 Subject: [PATCH 06/17] Self code review --- LeadKit.xcodeproj/project.pbxproj | 6 +- .../CustomizableButton.swift | 0 .../CustomizableButtonView.swift | 56 +++++++++++-------- .../CustomizableButtonViewModel.swift | 4 +- 4 files changed, 38 insertions(+), 28 deletions(-) rename Sources/Classes/Views/{BigBossButton => CustomizableButton}/CustomizableButton.swift (100%) rename Sources/Classes/Views/{BigBossButton => CustomizableButton}/CustomizableButtonView.swift (85%) rename Sources/Classes/Views/{BigBossButton => CustomizableButton}/CustomizableButtonViewModel.swift (94%) diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index bfd80b9d..be56f2a6 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -889,7 +889,7 @@ 671461D41EB3396E00EAB194 /* Views */ = { isa = PBXGroup; children = ( - 72005A1A2266226800ECE090 /* BigBossButton */, + 72005A1A2266226800ECE090 /* CustomizableButton */, 677B06B6211873E7006C947D /* BasePlaceholderView */, 67DB77672108714A001CB56B /* CollectionViewWrapperView */, 673CF42A2063DE3A00C329F6 /* DefaultPlaceholders */, @@ -1906,14 +1906,14 @@ path = TableKitViewModel; sourceTree = ""; }; - 72005A1A2266226800ECE090 /* BigBossButton */ = { + 72005A1A2266226800ECE090 /* CustomizableButton */ = { isa = PBXGroup; children = ( 72005A1B2266226800ECE090 /* CustomizableButton.swift */, 72005A1C2266226800ECE090 /* CustomizableButtonViewModel.swift */, 72005A1D2266226800ECE090 /* CustomizableButtonView.swift */, ); - path = BigBossButton; + path = CustomizableButton; sourceTree = ""; }; 72AECC68224A979D00D12E7C /* Search */ = { diff --git a/Sources/Classes/Views/BigBossButton/CustomizableButton.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButton.swift similarity index 100% rename from Sources/Classes/Views/BigBossButton/CustomizableButton.swift rename to Sources/Classes/Views/CustomizableButton/CustomizableButton.swift diff --git a/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift similarity index 85% rename from Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift rename to Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift index f0409cc7..a21e3f9a 100644 --- a/Sources/Classes/Views/BigBossButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift @@ -37,15 +37,15 @@ public struct BigBossButtonState: OptionSet { // MARK: - States - static let highlighted = BigBossButtonState(rawValue: 1 << 1) - static let normal = BigBossButtonState(rawValue: 1 << 2) - static let enabled = BigBossButtonState(rawValue: 1 << 3) - static let disabled = BigBossButtonState(rawValue: 1 << 4) - static let loading = BigBossButtonState(rawValue: 1 << 5) + public static let highlighted = BigBossButtonState(rawValue: 1 << 1) + public static let normal = BigBossButtonState(rawValue: 1 << 2) + public static let enabled = BigBossButtonState(rawValue: 1 << 3) + public static let disabled = BigBossButtonState(rawValue: 1 << 4) + public static let loading = BigBossButtonState(rawValue: 1 << 5) // MARK: - Properties - var isLoading: Bool { + public var isLoading: Bool { return contains(.loading) } } @@ -90,10 +90,11 @@ open class CustomizableButtonView: UIView { } public var buttonIsDisabledWhileLoading = false + public var hidesLabelWhenLoading = false // MARK: - Computed Properties - open var tapObservable: Observable { + public var tapObservable: Observable { return button.rx.tap.asObservable() } @@ -131,11 +132,18 @@ open class CustomizableButtonView: UIView { override open func layoutSubviews() { super.layoutSubviews() - shadowView.layer.shadowPath = UIBezierPath(rect: button.bounds).cgPath + if shadowView.layer.cornerRadius == 0 { + shadowView.layer.shadowPath = UIBezierPath(rect: button.bounds).cgPath + } } private func set(active: Bool) { button.isEnabled = buttonIsDisabledWhileLoading ? !active : true + + if hidesLabelWhenLoading { + button.titleLabel?.layer.opacity = active ? 0 : 1 + } + if active { spinnerView?.isHidden = false spinnerView?.startAnimating() @@ -149,6 +157,7 @@ open class CustomizableButtonView: UIView { if let spinner = spinnerView { addSubview(spinner) configureSpinnerConstraints() + spinner.isHidden = true } } @@ -167,6 +176,7 @@ open class CustomizableButtonView: UIView { } private func configureSpinnerConstraints() { + spinnerView?.translatesAutoresizingMaskIntoConstraints = false switch appearance.spinnerPosition { case .center: spinnerView?.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true @@ -193,6 +203,7 @@ open class CustomizableButtonView: UIView { private extension UIView { func constaintToEdges(of view: UIView, with offset: UIEdgeInsets) { + self.translatesAutoresizingMaskIntoConstraints = false self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: offset.left).isActive = true self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: offset.right).isActive = true self.topAnchor.constraint(equalTo: view.topAnchor, constant: offset.top).isActive = true @@ -207,6 +218,7 @@ extension CustomizableButtonView: InitializableView { } public func configureAppearance() { + button.titleLabel?.font = appearance.buttonFont button.set(titles: appearance.buttonStateTitles) @@ -224,6 +236,8 @@ extension CustomizableButtonView: InitializableView { if let cornerRadius = appearance.buttonCornerRadius { button.layer.cornerRadius = cornerRadius } + + button.titleLabel?.isHidden = true } } @@ -231,7 +245,6 @@ extension CustomizableButtonView: ConfigurableView { public func configure(with viewModel: CustomizableButtonViewModel) { button.titleLabel?.numberOfLines = 0 viewModel.stateDriver - .skip(1) .drive(stateBinder) .disposed(by: disposeBag) @@ -292,24 +305,22 @@ public extension CustomizableButtonView { var buttonIconOffset: UIOffset var buttonInsets: UIEdgeInsets - var buttonHeight: CGFloat var buttonCornerRadius: CGFloat? var buttonShadowPadding: CGFloat var spinnerPosition: SpinnerPosition - 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, - buttonHeight: CGFloat = 50, - buttonCornerRadius: CGFloat? = nil, - buttonShadowPadding: CGFloat = 0, - spinnerPosition: SpinnerPosition = .center + 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, + buttonShadowPadding: CGFloat = 0, + spinnerPosition: SpinnerPosition = .center ) { self.buttonFont = buttonFont @@ -323,7 +334,6 @@ public extension CustomizableButtonView { self.buttonIconOffset = buttonIconOffset self.buttonInsets = buttonInsets - self.buttonHeight = buttonHeight self.buttonCornerRadius = buttonCornerRadius self.buttonShadowPadding = buttonShadowPadding diff --git a/Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift similarity index 94% rename from Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift rename to Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift index 3af19baf..abe641a3 100644 --- a/Sources/Classes/Views/BigBossButton/CustomizableButtonViewModel.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift @@ -43,11 +43,11 @@ open class CustomizableButtonViewModel { return tapObservable.bind(to: tapRelay) } - var tapDriver: Driver { + public var tapDriver: Driver { return tapRelay.asDriver() } - func updateState(with newState: BigBossButtonState) { + public func updateState(with newState: BigBossButtonState) { stateRelay.accept(newState) } } From b594edfc1399a35d503dcbb444b441f60b24bced Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 22 Apr 2019 19:50:50 +0300 Subject: [PATCH 07/17] Changelog updated --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a6e4eb2..ecfcbeeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### 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. From b82c547f060af5a1367e754517e26f66b24deb4d Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 22 Apr 2019 19:52:10 +0300 Subject: [PATCH 08/17] Podspec updated --- LeadKit.podspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LeadKit.podspec b/LeadKit.podspec index 7944d1c1..8e1c55cd 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -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", From f1aefb537830dd0b089a6a9a6dac6ee5cca3844b Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 22 Apr 2019 20:04:54 +0300 Subject: [PATCH 09/17] BigBossButtonState was renamed to CustomizableButtonState --- .../CustomizableButtonView.swift | 18 +++++++++--------- .../CustomizableButtonViewModel.swift | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift index a21e3f9a..0e041049 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift @@ -25,7 +25,7 @@ import RxCocoa public typealias Spinner = UIView & Animatable -public struct BigBossButtonState: OptionSet { +public struct CustomizableButtonState: OptionSet { // MARK: - OptionSet conformance @@ -37,11 +37,11 @@ public struct BigBossButtonState: OptionSet { // MARK: - States - public static let highlighted = BigBossButtonState(rawValue: 1 << 1) - public static let normal = BigBossButtonState(rawValue: 1 << 2) - public static let enabled = BigBossButtonState(rawValue: 1 << 3) - public static let disabled = BigBossButtonState(rawValue: 1 << 4) - public static let loading = BigBossButtonState(rawValue: 1 << 5) + 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 @@ -255,18 +255,18 @@ extension CustomizableButtonView: ConfigurableView { appearance = viewModel.appearance } - private var stateBinder: Binder { + private var stateBinder: Binder { return Binder(self) { base, value in base.configureButton(withState: value) base.onStateChange(value) } } - open func onStateChange(_ state: BigBossButtonState) { + open func onStateChange(_ state: CustomizableButtonState) { /// override in subclass } - open func configureButton(withState state: BigBossButtonState) { + open func configureButton(withState state: CustomizableButtonState) { if state.contains(.enabled) { button.isEnabled = true } diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift index abe641a3..4fb5f66b 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift @@ -27,7 +27,7 @@ open class CustomizableButtonViewModel { public typealias Appearance = CustomizableButtonView.Appearance - private let stateRelay = BehaviorRelay(value: BigBossButtonState.enabled) + private let stateRelay = BehaviorRelay(value: CustomizableButtonState.enabled) private let tapRelay = BehaviorRelay(value: ()) public let appearance: Appearance @@ -35,7 +35,7 @@ open class CustomizableButtonViewModel { self.appearance = appearance } - open var stateDriver: Driver { + open var stateDriver: Driver { return stateRelay.asDriver() } @@ -47,7 +47,7 @@ open class CustomizableButtonViewModel { return tapRelay.asDriver() } - public func updateState(with newState: BigBossButtonState) { + public func updateState(with newState: CustomizableButtonState) { stateRelay.accept(newState) } } From d8bb1ce6b01d1fe83eaa8dc7e656819c812c089e Mon Sep 17 00:00:00 2001 From: Artur Date: Mon, 22 Apr 2019 20:07:46 +0300 Subject: [PATCH 10/17] New line deleted --- .../Views/CustomizableButton/CustomizableButtonView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift index 0e041049..cdc46c2f 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift @@ -74,7 +74,6 @@ open class CustomizableButtonView: UIView { removeSpinner() } } - didSet { if spinnerView != nil { addSpinner() From a3f43a93e878b35ef3c0ceebc77abad8f6cafb26 Mon Sep 17 00:00:00 2001 From: Artur Azarau Date: Tue, 23 Apr 2019 11:30:27 +0300 Subject: [PATCH 11/17] Issues resolved --- .../CustomizableButtonView.swift | 105 ++++++++---------- build-scripts | 2 +- 2 files changed, 49 insertions(+), 58 deletions(-) diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift index cdc46c2f..a4ce43d0 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift @@ -50,7 +50,7 @@ public struct CustomizableButtonState: OptionSet { } } -open class CustomizableButtonView: UIView { +open class CustomizableButtonView: UIView, InitializableView { // MARK: - Stored Properties @@ -137,17 +137,17 @@ open class CustomizableButtonView: UIView { } private func set(active: Bool) { - button.isEnabled = buttonIsDisabledWhileLoading ? !active : true + button.isEnabled = buttonIsDisabledWhileLoading || !active if hidesLabelWhenLoading { button.titleLabel?.layer.opacity = active ? 0 : 1 } + spinnerView?.isHidden = !active + if active { - spinnerView?.isHidden = false spinnerView?.startAnimating() } else { - spinnerView?.isHidden = true spinnerView?.stopAnimating() } } @@ -175,49 +175,48 @@ open class CustomizableButtonView: UIView { } private func configureSpinnerConstraints() { - spinnerView?.translatesAutoresizingMaskIntoConstraints = false + guard let spinnerView = spinnerView else { + return + } + spinnerView.translatesAutoresizingMaskIntoConstraints = false + var constraints = [NSLayoutConstraint]() switch appearance.spinnerPosition { case .center: - spinnerView?.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true - spinnerView?.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true + constraints = [ + spinnerView.centerXAnchor.constraint(equalTo: button.centerXAnchor), + spinnerView.centerYAnchor.constraint(equalTo: button.centerYAnchor) + ] case .leftToText(let offset): if let buttonLabel = button.titleLabel { - spinnerView?.centerYAnchor.constraint(equalTo: buttonLabel.centerYAnchor).isActive = true - spinnerView?.trailingAnchor.constraint(equalTo: buttonLabel.leadingAnchor, - constant: -offset).isActive = true + constraints = [ + spinnerView.centerYAnchor.constraint(equalTo: buttonLabel.centerYAnchor), + spinnerView.trailingAnchor.constraint(equalTo: buttonLabel.leadingAnchor, constant: -offset) + ] } case .rightToText(let offset): if let buttonLabel = button.titleLabel { - spinnerView?.centerYAnchor.constraint(equalTo: buttonLabel.centerYAnchor).isActive = true - spinnerView?.leadingAnchor.constraint(equalTo: buttonLabel.trailingAnchor, - constant: offset).isActive = true + 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) } -} -private extension UIView { - func constaintToEdges(of view: UIView, with offset: UIEdgeInsets) { - self.translatesAutoresizingMaskIntoConstraints = false - self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: offset.left).isActive = true - self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: offset.right).isActive = true - self.topAnchor.constraint(equalTo: view.topAnchor, constant: offset.top).isActive = true - self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: offset.bottom).isActive = true - } -} - -extension CustomizableButtonView: InitializableView { + // 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) @@ -234,22 +233,28 @@ extension CustomizableButtonView: InitializableView { 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) { + self.translatesAutoresizingMaskIntoConstraints = false + self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: offset.left).isActive = true + self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: offset.right).isActive = true + self.topAnchor.constraint(equalTo: view.topAnchor, constant: offset.top).isActive = true + self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: offset.bottom).isActive = true + } +} + extension CustomizableButtonView: ConfigurableView { public func configure(with viewModel: CustomizableButtonViewModel) { - button.titleLabel?.numberOfLines = 0 - viewModel.stateDriver - .drive(stateBinder) - .disposed(by: disposeBag) - - viewModel - .bind(tapObservable: tapObservable) - .disposed(by: disposeBag) + viewModel.stateDriver.drive(stateBinder).disposed(by: disposeBag) + viewModel.bind(tapObservable: tapObservable).disposed(by: disposeBag) appearance = viewModel.appearance } @@ -266,27 +271,9 @@ extension CustomizableButtonView: ConfigurableView { } open func configureButton(withState state: CustomizableButtonState) { - if state.contains(.enabled) { - button.isEnabled = true - } - - if state.contains(.disabled) { - button.isEnabled = false - } - - if state.contains(.highlighted) { - button.isHighlighted = true - } - - if state.contains(.normal) { - button.isHighlighted = false - } - - if state.contains(.loading) { - set(active: true) - } else { - set(active: false) - } + button.isEnabled = state.contains(.enabled) && !state.contains(.disabled) + button.isHighlighted = state.contains(.highlighted) && !state.contains(.normal) + set(active: state.contains(.loading)) } } @@ -309,6 +296,8 @@ public extension CustomizableButtonView { var buttonShadowPadding: CGFloat var spinnerPosition: SpinnerPosition + var numberOfLines: Int + public init(buttonFont: UIFont = .systemFont(ofSize: 15), buttonStateTitles: [UIControl.State: String] = [:], buttonStateAttributtedTitles: [UIControl.State: NSAttributedString] = [:], @@ -319,8 +308,8 @@ public extension CustomizableButtonView { buttonInsets: UIEdgeInsets = .zero, buttonCornerRadius: CGFloat? = nil, buttonShadowPadding: CGFloat = 0, - spinnerPosition: SpinnerPosition = .center - ) { + spinnerPosition: SpinnerPosition = .center, + numberOfLines: Int = 0) { self.buttonFont = buttonFont @@ -337,6 +326,8 @@ public extension CustomizableButtonView { self.buttonShadowPadding = buttonShadowPadding self.spinnerPosition = spinnerPosition + + self.numberOfLines = numberOfLines } } diff --git a/build-scripts b/build-scripts index d4a7dce0..05ed714e 160000 --- a/build-scripts +++ b/build-scripts @@ -1 +1 @@ -Subproject commit d4a7dce0d7f17617efb3dffa4ca7f64ec8a85e9f +Subproject commit 05ed714e9f9dd228662a98cc909d1e8175e02e71 From c4798d601540a77d193efaf9d4b7d4c7a030d902 Mon Sep 17 00:00:00 2001 From: Artur Azarau Date: Tue, 23 Apr 2019 13:04:51 +0300 Subject: [PATCH 12/17] Issues resolved --- .../CustomizableButton/CustomizableButtonView.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift index a4ce43d0..a298555f 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift @@ -243,11 +243,14 @@ open class CustomizableButtonView: UIView, InitializableView { private extension UIView { func constaintToEdges(of view: UIView, with offset: UIEdgeInsets) { - self.translatesAutoresizingMaskIntoConstraints = false - self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: offset.left).isActive = true - self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: offset.right).isActive = true - self.topAnchor.constraint(equalTo: view.topAnchor, constant: offset.top).isActive = true - self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: offset.bottom).isActive = true + 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) } } From 469073a5e98e210f374c4018603a78bf4f6a4d8a Mon Sep 17 00:00:00 2001 From: Artur Azarau Date: Tue, 23 Apr 2019 13:11:20 +0300 Subject: [PATCH 13/17] Documentation for classes added --- .../Classes/Views/CustomizableButton/CustomizableButton.swift | 1 + .../Views/CustomizableButton/CustomizableButtonView.swift | 1 + .../Views/CustomizableButton/CustomizableButtonViewModel.swift | 1 + 3 files changed, 3 insertions(+) diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButton.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButton.swift index 445d48f9..6c3b9db7 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButton.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButton.swift @@ -24,6 +24,7 @@ import UIKit import RxCocoa import RxSwift +/// class that is a CustomizableButtonView subview and gives it a button functionality open class CustomizableButton: UIButton { // MARK: - Constants diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift index a298555f..9c07bb78 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift @@ -50,6 +50,7 @@ public struct CustomizableButtonState: OptionSet { } } +/// container class that acts like a button and provides great customization open class CustomizableButtonView: UIView, InitializableView { // MARK: - Stored Properties diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift index 4fb5f66b..bcd6e230 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonViewModel.swift @@ -23,6 +23,7 @@ import RxCocoa import RxSwift +/// viewModel class for CustomizableButtonView configuration open class CustomizableButtonViewModel { public typealias Appearance = CustomizableButtonView.Appearance From b99870183b0fc348fc3e1795cd66398efcc836a8 Mon Sep 17 00:00:00 2001 From: Artur Azarau Date: Tue, 23 Apr 2019 14:31:33 +0300 Subject: [PATCH 14/17] Dead code deleted --- .../Views/CustomizableButton/CustomizableButtonView.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift index 9c07bb78..8f22d7bf 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift @@ -297,7 +297,6 @@ public extension CustomizableButtonView { var buttonCornerRadius: CGFloat? - var buttonShadowPadding: CGFloat var spinnerPosition: SpinnerPosition var numberOfLines: Int @@ -311,7 +310,6 @@ public extension CustomizableButtonView { buttonIconOffset: UIOffset = .zero, buttonInsets: UIEdgeInsets = .zero, buttonCornerRadius: CGFloat? = nil, - buttonShadowPadding: CGFloat = 0, spinnerPosition: SpinnerPosition = .center, numberOfLines: Int = 0) { @@ -328,7 +326,6 @@ public extension CustomizableButtonView { self.buttonCornerRadius = buttonCornerRadius - self.buttonShadowPadding = buttonShadowPadding self.spinnerPosition = spinnerPosition self.numberOfLines = numberOfLines From cfb8259dbea52ea9864b8b6672a8c5e869cdc393 Mon Sep 17 00:00:00 2001 From: Artur Azarau Date: Tue, 23 Apr 2019 14:39:47 +0300 Subject: [PATCH 15/17] Version in podspec updated --- LeadKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LeadKit.podspec b/LeadKit.podspec index 8e1c55cd..a373516d 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.18" 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" From 09e97500fc9de7b65d1c2f099b1275154697368c Mon Sep 17 00:00:00 2001 From: Artur Azarau Date: Tue, 23 Apr 2019 16:34:14 +0300 Subject: [PATCH 16/17] Build scripts were updated --- build-scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-scripts b/build-scripts index 05ed714e..014dba7a 160000 --- a/build-scripts +++ b/build-scripts @@ -1 +1 @@ -Subproject commit 05ed714e9f9dd228662a98cc909d1e8175e02e71 +Subproject commit 014dba7afd678d41dabe1bab9962ba27b629c707 From ef2e351eaee6aacb0353c2efb64922e6425381be Mon Sep 17 00:00:00 2001 From: Artur Date: Thu, 25 Apr 2019 19:16:06 +0300 Subject: [PATCH 17/17] Some fix --- .../Views/CustomizableButton/CustomizableButtonView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift index 8f22d7bf..7c0b1e8e 100644 --- a/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift +++ b/Sources/Classes/Views/CustomizableButton/CustomizableButtonView.swift @@ -164,7 +164,6 @@ open class CustomizableButtonView: UIView, InitializableView { private func removeSpinner() { if spinnerView != nil { self.spinnerView?.removeFromSuperview() - self.spinnerView = nil } }