Compare commits
14 Commits
develop
...
feature/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0090deac9 | ||
|
|
4fd167cfa3 | ||
|
|
df1e8ee2df | ||
|
|
ce54965edc | ||
|
|
0be0d71f7b | ||
|
|
c2998ee0bf | ||
|
|
ac1e6f692d | ||
|
|
c7e47707c2 | ||
|
|
3e8c6a12e4 | ||
|
|
d159075f83 | ||
|
|
a8e1cf9b48 | ||
|
|
76c3205921 | ||
|
|
abc7f409e7 | ||
|
|
1934da2543 |
@ -16,6 +16,8 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
|
||||
//--------------------------------------------------
|
||||
// MARK: - Combine Properties
|
||||
//--------------------------------------------------
|
||||
public var subject = PassthroughSubject<Void, Never>()
|
||||
public var updateStrategy: HandlerableUpdateStrategy = .immediate
|
||||
|
||||
/// Set of Subscribers for any Publishers for this Control
|
||||
public var subscribers = Set<AnyCancellable>()
|
||||
@ -33,10 +35,7 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
private var initialSetupPerformed = false
|
||||
|
||||
/// Key of whether or not updateView() is called in setNeedsUpdate()
|
||||
open var shouldUpdateView: Bool = true
|
||||
|
||||
|
||||
/// Dictionary for keeping information for this Control use only Primitives
|
||||
open var userInfo = [String: Primitive]()
|
||||
|
||||
@ -109,7 +108,8 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
|
||||
if !initialSetupPerformed {
|
||||
initialSetupPerformed = true
|
||||
setup()
|
||||
setNeedsUpdate()
|
||||
setupNeedsUpdateEvent()
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,6 +125,14 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
open override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
// Keep the strategy if it's 'alwaysImmediate'
|
||||
guard updateStrategy != .alwaysImmediate else { return }
|
||||
|
||||
// Update the strategy based on whether the view is in a window
|
||||
updateStrategy = window != nil ? .delayed : .immediate
|
||||
}
|
||||
|
||||
/// Update this view based off of property changes
|
||||
open func updateView() {
|
||||
|
||||
@ -229,7 +229,6 @@ open class SelectorItemBase<Selector: SelectorControlable>: Control, Errorable,
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
label.reset()
|
||||
childLabel.reset()
|
||||
errorLabel.reset()
|
||||
@ -250,7 +249,6 @@ open class SelectorItemBase<Selector: SelectorControlable>: Control, Errorable,
|
||||
value = nil
|
||||
isSelected = false
|
||||
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -17,16 +17,16 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Combine Properties
|
||||
//--------------------------------------------------
|
||||
public var subject = PassthroughSubject<Void, Never>()
|
||||
public var updateStrategy: HandlerableUpdateStrategy = .immediate
|
||||
|
||||
public var subscribers = Set<AnyCancellable>()
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
private var initialSetupPerformed = false
|
||||
|
||||
/// Key of whether or not updateView() is called in setNeedsUpdate()
|
||||
open var shouldUpdateView: Bool = true
|
||||
|
||||
|
||||
/// Dictionary for keeping information for this Control use only Primitives
|
||||
open var userInfo = [String: Primitive]()
|
||||
|
||||
@ -72,13 +72,22 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable {
|
||||
if !initialSetupPerformed {
|
||||
initialSetupPerformed = true
|
||||
setup()
|
||||
setNeedsUpdate()
|
||||
setupNeedsUpdateEvent()
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
open override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
// Keep the strategy if it's 'alwaysImmediate'
|
||||
guard updateStrategy != .alwaysImmediate else { return }
|
||||
|
||||
// Update the strategy based on whether the view is in a window
|
||||
updateStrategy = window != nil ? .delayed : .immediate
|
||||
}
|
||||
|
||||
/// Update this view based off of property changes
|
||||
open func updateView() {
|
||||
|
||||
@ -78,7 +78,6 @@ open class Badge: View {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
label.reset()
|
||||
label.lineBreakMode = .byTruncatingTail
|
||||
label.textPosition = .left
|
||||
@ -87,7 +86,6 @@ open class Badge: View {
|
||||
text = ""
|
||||
maxWidth = nil
|
||||
numberOfLines = 1
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -281,13 +281,11 @@ open class BadgeIndicator: View {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
label.reset()
|
||||
label.lineBreakMode = .byTruncatingTail
|
||||
label.textPosition = .center
|
||||
fillColor = .red
|
||||
number = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -133,11 +133,9 @@ open class Button: ButtonBase, Useable {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
use = .primary
|
||||
width = nil
|
||||
size = .large
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,8 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab
|
||||
//--------------------------------------------------
|
||||
// MARK: - Combine Properties
|
||||
//--------------------------------------------------
|
||||
public var subject = PassthroughSubject<Void, Never>()
|
||||
public var updateStrategy: HandlerableUpdateStrategy = .immediate
|
||||
public var subscribers = Set<AnyCancellable>()
|
||||
public var onClickSubscriber: AnyCancellable? {
|
||||
willSet {
|
||||
@ -44,9 +46,7 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
open var shouldUpdateView: Bool = true
|
||||
|
||||
//--------------------------------------------------
|
||||
open var availableSizes: [ButtonSize] { [] }
|
||||
|
||||
open var text: String? { didSet { setNeedsUpdate() } }
|
||||
@ -123,7 +123,8 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
accessibilityCustomActions = []
|
||||
setup()
|
||||
setNeedsUpdate()
|
||||
setupNeedsUpdateEvent()
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,18 +138,25 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab
|
||||
|
||||
/// Resets back to this objects default settings.
|
||||
open func reset() {
|
||||
shouldUpdateView = false
|
||||
surface = .light
|
||||
disabled = false
|
||||
text = nil
|
||||
accessibilityCustomActions = []
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
open override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
// Keep the strategy if it's 'alwaysImmediate'
|
||||
guard updateStrategy != .alwaysImmediate else { return }
|
||||
|
||||
// Update the strategy based on whether the view is in a window
|
||||
updateStrategy = window != nil ? .delayed : .immediate
|
||||
}
|
||||
|
||||
override open var intrinsicContentSize: CGSize {
|
||||
let intrinsicContentSize = super.intrinsicContentSize
|
||||
let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
|
||||
|
||||
@ -90,13 +90,11 @@ open class TextLink: ButtonBase {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
text = nil
|
||||
size = .large
|
||||
accessibilityCustomActions = []
|
||||
isAccessibilityElement = true
|
||||
accessibilityTraits = .link
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -256,7 +256,6 @@ open class ButtonIcon: Control {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
kind = .ghost
|
||||
surfaceType = .colorFill
|
||||
size = .large
|
||||
@ -264,7 +263,6 @@ open class ButtonIcon: Control {
|
||||
hideBorder = true
|
||||
iconOffset = .init(x: 0, y: 0)
|
||||
iconName = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -16,15 +16,15 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Combine Properties
|
||||
//--------------------------------------------------
|
||||
public var subject = PassthroughSubject<Void, Never>()
|
||||
public var subscribers = Set<AnyCancellable>()
|
||||
public var updateStrategy: HandlerableUpdateStrategy = .immediate
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
private var initialSetupPerformed = false
|
||||
|
||||
open var shouldUpdateView: Bool = true
|
||||
|
||||
open var useAttributedText: Bool = false
|
||||
|
||||
open var useScaledFont: Bool = false { didSet { setNeedsUpdate() }}
|
||||
@ -105,7 +105,8 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
|
||||
accessibilityCustomActions = []
|
||||
accessibilityTraits = .staticText
|
||||
setup()
|
||||
setNeedsUpdate()
|
||||
setupNeedsUpdateEvent()
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +114,6 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
|
||||
|
||||
/// Resets back to this objects default settings.
|
||||
open func reset() {
|
||||
shouldUpdateView = false
|
||||
surface = .light
|
||||
disabled = false
|
||||
attributes = nil
|
||||
@ -123,7 +123,6 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
|
||||
attributedText = nil
|
||||
numberOfLines = 0
|
||||
backgroundColor = .clear
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
@ -136,7 +135,16 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
//--------------------------------------------------
|
||||
open override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
// Keep the strategy if it's 'alwaysImmediate'
|
||||
guard updateStrategy != .alwaysImmediate else { return }
|
||||
|
||||
// Update the strategy based on whether the view is in a window
|
||||
updateStrategy = window != nil ? .delayed : .immediate
|
||||
}
|
||||
|
||||
open func updateView() {
|
||||
if !useAttributedText {
|
||||
if let text = text {
|
||||
|
||||
@ -231,9 +231,7 @@ open class Notification: View {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
|
||||
shouldUpdateView = false
|
||||
|
||||
|
||||
titleLabel.reset()
|
||||
titleLabel.text = ""
|
||||
titleLabel.textStyle = UIDevice.isIPad ? .boldBodyLarge : .boldBodySmall
|
||||
@ -259,7 +257,6 @@ open class Notification: View {
|
||||
hideCloseButton = false
|
||||
fullBleed = false
|
||||
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -203,7 +203,6 @@ open class RadioBoxItem: Control, Changeable {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
textLabel.reset()
|
||||
subTextLabel.reset()
|
||||
subTextRightLabel.reset()
|
||||
@ -227,7 +226,6 @@ open class RadioBoxItem: Control, Changeable {
|
||||
|
||||
isSelected = false
|
||||
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -92,7 +92,6 @@ open class RadioSwatch: Control {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
fillImage = nil
|
||||
text = ""
|
||||
primaryColor = nil
|
||||
@ -100,7 +99,6 @@ open class RadioSwatch: Control {
|
||||
strikethrough = false
|
||||
inputId = nil
|
||||
value = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
setNeedsDisplay()
|
||||
}
|
||||
|
||||
@ -123,6 +123,9 @@ extension Tabs {
|
||||
//--------------------------------------------------
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
updateStrategy = .alwaysImmediate
|
||||
label.updateStrategy = .alwaysImmediate
|
||||
|
||||
addSubview(label)
|
||||
accessibilityTraits = .button
|
||||
|
||||
@ -137,19 +140,7 @@ extension Tabs {
|
||||
|
||||
labelLeadingConstraint = label.leadingAnchor.constraint(equalTo: leadingAnchor)
|
||||
labelLeadingConstraint?.isActive = true
|
||||
|
||||
let layoutGuide = UILayoutGuide()
|
||||
addLayoutGuide(layoutGuide)
|
||||
|
||||
labelWidthConstraint = layoutGuide.widthAnchor.constraint(greaterThanOrEqualToConstant: minWidth)
|
||||
labelWidthConstraint?.isActive = true
|
||||
|
||||
//activate the constraints
|
||||
NSLayoutConstraint.activate([layoutGuide.topAnchor.constraint(equalTo: topAnchor),
|
||||
layoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
layoutGuide.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
layoutGuide.trailingAnchor.constraint(equalTo: trailingAnchor)])
|
||||
|
||||
|
||||
publisher(for: UITapGestureRecognizer())
|
||||
.sink { [weak self] _ in
|
||||
guard let self else { return }
|
||||
|
||||
@ -164,6 +164,7 @@ open class Tabs: View {
|
||||
//--------------------------------------------------
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
updateStrategy = .alwaysImmediate
|
||||
scrollView = UIScrollView()
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.showsHorizontalScrollIndicator = false
|
||||
|
||||
@ -192,7 +192,6 @@ open class TileContainer: Control {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
color = .white
|
||||
padding = .padding4X
|
||||
aspectRatio = .ratio1x1
|
||||
@ -201,7 +200,6 @@ open class TileContainer: Control {
|
||||
height = nil
|
||||
showBorder = false
|
||||
showDropShadows = false
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -242,7 +242,6 @@ open class Tilelet: TileContainer {
|
||||
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
shouldUpdateView = false
|
||||
aspectRatio = .none
|
||||
color = .black
|
||||
//models
|
||||
@ -251,7 +250,6 @@ open class Tilelet: TileContainer {
|
||||
subTitleModel = nil
|
||||
descriptiveIconModel = nil
|
||||
directionalIconModel = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -199,12 +199,10 @@ open class TitleLockup: View {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
textPosition = .left
|
||||
eyebrowModel = nil
|
||||
titleModel = nil
|
||||
subTitleModel = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -175,7 +175,6 @@ open class Toggle: Control, Changeable {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
label.reset()
|
||||
isOn = false
|
||||
isAnimated = true
|
||||
@ -187,8 +186,6 @@ open class Toggle: Control, Changeable {
|
||||
textPosition = .left
|
||||
inputId = nil
|
||||
value = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
/// This will toggle the state of the Toggle
|
||||
|
||||
@ -139,15 +139,12 @@ open class ToggleView: Control, Changeable {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
isOn = false
|
||||
isAnimated = true
|
||||
inputId = nil
|
||||
value = nil
|
||||
toggleView.backgroundColor = toggleColorConfiguration.getColor(self)
|
||||
knobView.backgroundColor = knobColorConfiguration.getColor(self)
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
/// This will toggle the state of the Toggle and execute the actionBlock if provided.
|
||||
|
||||
@ -142,14 +142,12 @@ open class Tooltip: Control, TooltipLaunchable {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
size = .medium
|
||||
title = ""
|
||||
content = ""
|
||||
fillColor = .primary
|
||||
closeButtonText = "Close"
|
||||
imageView.image = nil
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
|
||||
@ -80,7 +80,6 @@ open class TrailingTooltipLabel: View, TooltipLaunchable {
|
||||
/// Resets back to this objects default settings.
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
shouldUpdateView = false
|
||||
labelText = nil
|
||||
labelAttributes = nil
|
||||
labelTextStyle = .defaultStyle
|
||||
@ -88,7 +87,6 @@ open class TrailingTooltipLabel: View, TooltipLaunchable {
|
||||
tooltipCloseButtonText = "Close"
|
||||
tooltipTitle = ""
|
||||
tooltipContent = ""
|
||||
shouldUpdateView = true
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,18 +9,43 @@ import Foundation
|
||||
import Combine
|
||||
import UIKit
|
||||
|
||||
public enum HandlerableUpdateStrategy {
|
||||
case immediate
|
||||
case delayed
|
||||
case alwaysImmediate
|
||||
case paused
|
||||
}
|
||||
|
||||
public protocol Handlerable: AnyObject, Initable, Disabling, Surfaceable {
|
||||
var subject: PassthroughSubject<Void, Never> { get set }
|
||||
var subscribers: Set<AnyCancellable> { get set }
|
||||
var shouldUpdateView: Bool { get set }
|
||||
var updateStrategy: HandlerableUpdateStrategy { get set }
|
||||
func updateView()
|
||||
}
|
||||
|
||||
extension Handlerable {
|
||||
public func setupNeedsUpdateEvent() {
|
||||
subject
|
||||
.debounce(for: .milliseconds(50), scheduler: RunLoop.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.updateView()
|
||||
}.store(in: &subscribers)
|
||||
}
|
||||
|
||||
public func setNeedsUpdate() {
|
||||
if shouldUpdateView {
|
||||
shouldUpdateView = false
|
||||
switch updateStrategy {
|
||||
case .delayed:
|
||||
subject.send()
|
||||
case .immediate, .alwaysImmediate:
|
||||
updateView()
|
||||
shouldUpdateView = true
|
||||
case .paused:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func updateIfNeeded<T: Equatable>(_ oldValue: T, _ newValue: T) {
|
||||
if oldValue != newValue {
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -33,3 +58,15 @@ extension Handlerable where Self: UIControl {
|
||||
}).store(in: &subscribers)
|
||||
}
|
||||
}
|
||||
|
||||
extension Handlerable {
|
||||
@discardableResult func pausedUpdate(_ closure: (_ instance: inout Self) -> Void) -> Self {
|
||||
var copy = self
|
||||
let oldStrategy = copy.updateStrategy
|
||||
copy.updateStrategy = .paused
|
||||
closure(©)
|
||||
copy.updateStrategy = oldStrategy
|
||||
copy.updateView()
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user