Skip to content

Commit fdfc531

Browse files
Initial Commit
tag 1.0.0
0 parents  commit fdfc531

File tree

84 files changed

+8155
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+8155
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
/.build
3+
**/xcuserdata
4+
Carthage/
5+
PRIVATE_KEYS.swift
6+
Fabled/Fonts
7+
*.png

Attribution.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Asset Attribution
2+
3+
- [logout by Alfred Brave from the Noun Project](https://thenounproject.com/term/logout/2775411/)
4+
- [Refresh by Syawaluddin from the Noun Project](https://thenounproject.com/term/refresh/2805537)

BindableView/BindableView.swift

+305
Large diffs are not rendered by default.

BindableView/Button.swift

+267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
//
2+
// Button.swift
3+
// BindableViews
4+
//
5+
// Created by Nathan Hosselton on 7/3/19.
6+
// Copyright © 2019 Nathan Hosselton. All rights reserved.
7+
//
8+
9+
import UIKit.UIButton
10+
11+
/// A wrapper view for `UIButton` which provides a binding for responding to touch events.
12+
///
13+
/// Because subclasses of `UIButton` cannot make use of its (now standard) convenience
14+
/// constructor `init(type:)`, we provide this wrapper view which installs the configured
15+
/// `UIButton` as its subview.
16+
final class Button: UIView, BindableControl {
17+
typealias ControlType = UIButton
18+
19+
/// The binding provided by this view for responding to touch events on the underlying `UIButton`.
20+
///
21+
/// - Note: `observe(:with:)` is the intended method for observing this control's events and must be
22+
/// called at least once before touch event forwarding to this binding will begin.
23+
/// - Seealso: `observe(:with:)`
24+
let controlEventBinding = Binding<(sender: UIButton, event: UIEvent)>()
25+
26+
/// The `UIButton` instance created and managed by this view.
27+
/// - Note: This accessor is provided for convenience. Direct manipulation of the `UIButton` is
28+
/// usually unnecessary and may interfere with its relationship to this parent view.
29+
/// - Important: Do not add the `UIButton` directly to your view hierarchy.
30+
let uiButton: UIButton
31+
32+
/// The text to display in the `UIButton`'s label for the `.normal` control state.
33+
var title: String {
34+
didSet {
35+
uiButton.setTitle(title, for: .normal)
36+
}
37+
}
38+
39+
/// The designated initializer for this class.
40+
///
41+
/// Immediately constructs the underlying `UIButton` object and adds it to the view hierarchy for this
42+
/// view. Further configuration events on this object will continue to be forwarded to the `UIButton`;
43+
/// configuring it directly via the `uiButton` or `associatedControl` properties is not necessary. Events
44+
/// which update this view's frame will automatically update the bounds of the `UIButton` to match.
45+
///
46+
/// - Parameters:
47+
/// - type: The `ButtonType` to be used. The default is `.system`.
48+
/// - title: The text to use for the button's title in the normal control state.
49+
init(type: UIButton.ButtonType = .system, _ title: String) {
50+
self.title = title
51+
self.uiButton = UIButton(type: type)
52+
53+
super.init(frame: .zero)
54+
55+
uiButton.setTitle(title, for: .normal)
56+
uiButton.translatesAutoresizingMaskIntoConstraints = false
57+
addSubview(uiButton)
58+
59+
NSLayoutConstraint.activate([
60+
uiButton.leadingAnchor.constraint(equalTo: leadingAnchor),
61+
uiButton.topAnchor.constraint(equalTo: topAnchor),
62+
uiButton.trailingAnchor.constraint(equalTo: trailingAnchor),
63+
uiButton.bottomAnchor.constraint(equalTo: bottomAnchor)
64+
])
65+
}
66+
67+
init(_ image: UIImage) {
68+
self.title = ""
69+
self.uiButton = UIButton(type: .system)
70+
71+
super.init(frame: .zero)
72+
73+
uiButton.setImage(image, for: .normal)
74+
uiButton.translatesAutoresizingMaskIntoConstraints = false
75+
addSubview(uiButton)
76+
77+
NSLayoutConstraint.activate([
78+
uiButton.leadingAnchor.constraint(equalTo: leadingAnchor),
79+
uiButton.topAnchor.constraint(equalTo: topAnchor),
80+
uiButton.trailingAnchor.constraint(equalTo: trailingAnchor),
81+
uiButton.bottomAnchor.constraint(equalTo: bottomAnchor)
82+
])
83+
}
84+
85+
/// Configure the button's title label for other states. May be called multiple times.
86+
/// - Parameters:
87+
/// - text: The title to use for the label.
88+
/// - state: A variadic list of control states when the title should be displayed. Defaults to
89+
/// `.normal` when omitted.
90+
func title(_ text: String, while state: UIControl.State...) -> Self {
91+
let states = state.isEmpty ? [.normal] : state
92+
for state in states {
93+
switch state {
94+
case .normal: self.title = text
95+
default: uiButton.setTitle(text, for: state)
96+
}
97+
}
98+
return self
99+
}
100+
101+
/// Configure the button's title label color for the provided state(s). May be called multiple times.
102+
/// - Parameters:
103+
/// - color: The color to use for the title label's text.
104+
/// - state: A variadic list of control states when the color should be used. Defaults to `.normal`
105+
/// when omitted.
106+
func titleColor(_ color: UIColor, while state: UIControl.State...) -> Self {
107+
let states = state.isEmpty ? [.normal] : state
108+
for state in states {
109+
uiButton.setTitleColor(color, for: state)
110+
}
111+
return self
112+
}
113+
114+
/// Set the font of the text displayed in the button's title label.
115+
/// - parameter font: The font to be used.
116+
/// - SeeAlso: `font(:)`
117+
func font(_ font: UIFont) -> Self {
118+
uiButton.titleLabel?.font = font
119+
return self
120+
}
121+
122+
/// Set the font of the text displayed in the button's title label using the provided font name and
123+
/// preserving the current font size.
124+
/// - parameter name: The full name of the font to be used, e.g. `"HelveticaNeue-LightItalic"`.
125+
/// - SeeAlso: `font(:)`
126+
func font(_ name: String) -> Self {
127+
uiButton.titleLabel?.font = UIFont(name: name, size: uiButton.titleLabel?.font.pointSize ?? UIFont.systemFontSize)
128+
return self
129+
}
130+
131+
/// Sets the font of the text displayed in the button's title label using a descriptor.
132+
/// - parameter descriptor: The descriptor to use for setting the font.
133+
func font(from descriptor: UIFontDescriptor) -> Self {
134+
uiButton.titleLabel?.font = UIFont(descriptor: descriptor, size: uiButton.titleLabel?.font.pointSize ?? UIFont.systemFontSize)
135+
return self
136+
}
137+
138+
/// Sets the size of the current font of the button's title label.
139+
/// - parameter size: The size to use for the current font, in points.
140+
func fontSize(_ size: CGFloat) -> Self {
141+
uiButton.titleLabel?.font = uiButton.titleLabel?.font.withSize(size)
142+
return self
143+
}
144+
145+
/// Sets the tint color for the UIButton object.
146+
/// - parameter color: The color to set for the tint.
147+
func tintColor(_ color: UIColor) -> Self {
148+
uiButton.tintColor = color
149+
return self
150+
}
151+
152+
/// Sets the inset or outset margins for the rectangle surrounding all of the button’s content.
153+
/// - parameter insets: The insets to use.
154+
func contentEdgeInsets(_ insets: UIEdgeInsets) -> Self {
155+
uiButton.contentEdgeInsets = insets
156+
return self
157+
}
158+
159+
func styleProvider(_ provider: (_ stylable: UIButton) -> Void) -> Self {
160+
provider(uiButton)
161+
return self
162+
}
163+
164+
/// Sets the content hugging priority of the view and its enclosed UIButton.
165+
/// - Parameters:
166+
/// - priority: The layout priority value to use.
167+
/// - axis: Variadic list of the layout axes on which to set the priority. If not provided, defaults to all.
168+
func contentHuggingPriority(_ priority: UILayoutPriority, _ axis: NSLayoutConstraint.Axis...) -> Self {
169+
let axes = axis.isEmpty ? [.horizontal, .vertical] : axis
170+
axes.forEach {
171+
setContentHuggingPriority(priority, for: $0)
172+
uiButton.setContentHuggingPriority(priority, for: $0)
173+
}
174+
return self
175+
}
176+
177+
/// Sets the content compression resistance priority of this view and its enclosed UIButton.
178+
/// - Parameters:
179+
/// - priority: The layout priority value to use.
180+
/// - axis: Variadic list of the layout axes on which to set the priority. If not provided, defaults to all.
181+
func contentCompressionResistance(_ priority: UILayoutPriority, _ axis: NSLayoutConstraint.Axis...) -> Self {
182+
let axes = axis.isEmpty ? [.horizontal, .vertical] : axis
183+
axes.forEach {
184+
setContentCompressionResistancePriority(priority, for: $0)
185+
uiButton.setContentCompressionResistancePriority(priority, for: $0)
186+
}
187+
return self
188+
}
189+
190+
override var forFirstBaselineLayout: UIView {
191+
return uiButton.forFirstBaselineLayout
192+
}
193+
194+
override var forLastBaselineLayout: UIView {
195+
return uiButton.forLastBaselineLayout
196+
}
197+
198+
/// Adjusts the frame of this view and its enclosed `UIButton` automatically. Separately settng
199+
/// the `frame` of the `UIButton` is unsupported.
200+
override var frame: CGRect {
201+
didSet {
202+
uiButton.frame = .init(origin: .zero, size: bounds.size)
203+
}
204+
}
205+
206+
/// Target selector for the `UIButton` instance's action, which emits the event through the `binding`.
207+
@objc private func onControlEvent(sender: UIButton, event: UIEvent) {
208+
controlEventBinding.emit((sender, event))
209+
}
210+
211+
deinit {
212+
associatedControl.removeTarget(self, action: #selector(onControlEvent), for: .allEvents)
213+
214+
#if DEBUG
215+
// print("\(type(of: self)) deinit")
216+
#endif
217+
}
218+
219+
220+
//MARK: Unavailable
221+
222+
@available(*, unavailable)
223+
required init(coder: NSCoder = .empty) {
224+
fatalError("\(#file + #function) is not available.")
225+
}
226+
227+
@available(*, unavailable)
228+
override init(frame: CGRect) {
229+
fatalError("\(#file + #function) is not available.")
230+
}
231+
}
232+
233+
extension Button {
234+
var associatedControl: UIButton {
235+
return uiButton
236+
}
237+
238+
/// As `Button` does not have any sensible use for accepting an external binding, this property simply
239+
/// references the `controlEventBinding`.
240+
var binding: Binding<(sender: UIButton, event: UIEvent)>? {
241+
return controlEventBinding
242+
}
243+
244+
func observe(_ event: UIControl.Event = .touchUpInside, with observer: @escaping () -> Void) -> Self {
245+
associatedControl.addTarget(self, action: #selector(onControlEvent), for: event)
246+
controlEventBinding.observe { _ in observer() }
247+
return self
248+
}
249+
250+
func observe(_ event: UIControl.Event = .touchUpInside, with observer: @escaping (_ sender: UIButton) -> Void) -> Self {
251+
associatedControl.addTarget(self, action: #selector(onControlEvent), for: event)
252+
controlEventBinding.observe { (sender, _) in observer(sender) }
253+
return self
254+
}
255+
256+
func observe(_ event: UIControl.Event = .touchUpInside, with observer: @escaping (_ e: UIEvent) -> Void) -> Self {
257+
associatedControl.addTarget(self, action: #selector(onControlEvent), for: event)
258+
controlEventBinding.observe { (_, event) in observer(event) }
259+
return self
260+
}
261+
262+
func observe(_ event: UIControl.Event = .touchUpInside, with observer: @escaping (_ sender: UIButton, _ e: UIEvent) -> Void) -> Self {
263+
associatedControl.addTarget(self, action: #selector(onControlEvent), for: event)
264+
controlEventBinding.observe(with: observer)
265+
return self
266+
}
267+
}

0 commit comments

Comments
 (0)