Skip to content

Commit 6370d66

Browse files
author
Danny
committed
pushing cached xcode10-support branch files
0 parents  commit 6370d66

19 files changed

+1319
-0
lines changed

FueledUtils.podspec

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Pod::Spec.new do |s|
2+
s.name = 'FueledUtils'
3+
s.version = '1.5'
4+
s.summary = 'A collection of utilities used at Fueled'
5+
6+
s.description = <<-DESC
7+
This is a collection of classes, extensions, methods and functions used within Fueled projects that aims at decomplexifying tasks that should be easy.
8+
DESC
9+
10+
s.homepage = 'https://github.com/Fueled/ios-utilities'
11+
s.license = { :type => 'MIT', :file => 'LICENSE' }
12+
s.author = { 'vadim-fueled' => '[email protected]', 'stephane-fueled' => '[email protected]', 'leonty-fueled' => '[email protected]', 'bastien-fueled' => '[email protected]', 'ivan-fueled' => '[email protected]', 'thib4ult' => '[email protected]', 'benoit-fueled' => '[email protected]' }
13+
s.source = { :git => 'https://github.com/Fueled/ios-utilities.git', :tag => s.version.to_s }
14+
15+
s.ios.deployment_target = '8.0'
16+
17+
s.exclude_files = 'FueledUtils/FueledUtils.h'
18+
19+
s.source_files = "FueledUtils/*.swift"
20+
21+
s.dependency "ReactiveCocoa", "8.0.2"
22+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import UIKit
2+
3+
open class ButtonWithTitleAdjustment: UIButton {
4+
@IBInspectable public var adjustmentLineSpacing: CGFloat = 0 {
5+
didSet {
6+
self.updateAdjustedTitles()
7+
}
8+
}
9+
10+
@IBInspectable public var adjustmentKerning: CGFloat = 0 {
11+
didSet {
12+
self.updateAdjustedTitles()
13+
}
14+
}
15+
16+
override public init(frame: CGRect) {
17+
super.init(frame: frame)
18+
self.updateAdjustedTitles()
19+
}
20+
21+
required public init?(coder aDecoder: NSCoder) {
22+
super.init(coder: aDecoder)
23+
self.updateAdjustedTitles()
24+
}
25+
26+
override open func setTitleColor(_ color: UIColor?, for state: UIControl.State) {
27+
super.setTitleColor(color, for: state)
28+
self.updateAdjustedTitles()
29+
}
30+
31+
override open func setTitle(_ title: String?, for state: UIControl.State) {
32+
super.setTitle(title, for: state)
33+
self.updateAdjustedTitles()
34+
}
35+
36+
private func updateAdjustedTitles() {
37+
let states: [UIControl.State] = [.normal, .highlighted, .selected, .disabled, [.selected, .highlighted], [.selected, .disabled]]
38+
for state in states {
39+
self.setAdjustedTitle(self.title(for: state), for: state)
40+
}
41+
}
42+
43+
private func setAdjustedTitle(_ title: String?, for state: UIControl.State) {
44+
let adjustedString = title.map { title -> NSAttributedString in
45+
let paragraphStyle = NSMutableParagraphStyle()
46+
paragraphStyle.lineSpacing = self.adjustmentLineSpacing
47+
var attributes: [NSAttributedString.Key: Any] = [
48+
NSAttributedString.Key.paragraphStyle: paragraphStyle,
49+
NSAttributedString.Key.kern: self.adjustmentKerning,
50+
]
51+
if let titleColor = self.titleColor(for: state) {
52+
attributes[NSAttributedString.Key.foregroundColor] = titleColor
53+
}
54+
return NSAttributedString(string: title, attributes: attributes)
55+
}
56+
self.setAttributedTitle(adjustedString, for: state)
57+
}
58+
}
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
public extension Collection {
2+
public func getSafely(_ index: Self.Index) -> Self.Iterator.Element? {
3+
if index < self.startIndex || index >= self.endIndex {
4+
return nil
5+
}
6+
return self[index]
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import UIKit
2+
import Foundation
3+
4+
/// Adds formatting (decoration) characters to text field's content according to a variable pattern. Can be used for
5+
/// payment card number formatting, phone number formatting, etc.
6+
public final class DecoratingTextFieldDelegate: NSObject {
7+
public let patternForDataString: (String) -> String
8+
public let patternPlaceholderForDataCharacter: Character
9+
public let isDataCharacter: (Character) -> Bool
10+
11+
/**
12+
Intializes a delegate with a fixed pattern
13+
- parameters:
14+
- pattern: a string containing data placeholder and formatting characters.
15+
- patternPlaceholderForDataCharacter: a character that is not a formatting character.
16+
- isDataCharacter: a predicate to filter non-data characters from user's input. No matter what user tries to put \
17+
into the textfield, only characters for which `isDataCharacter` returns `true` will appear in the text field.
18+
## Example:
19+
A 16-digit VISA payment card pattern might look like this `####-####-####-####` `'#'` is a
20+
`patternPlaceholderForDataCharacter` and '`-`' is a formatting (decorating) character.
21+
*/
22+
public convenience init(
23+
pattern: String,
24+
patternPlaceholderForDataCharacter: Character,
25+
isDataCharacter: @escaping (Character) -> Bool)
26+
{
27+
self.init(
28+
patternForDataString: { _ in pattern },
29+
patternPlaceholderForDataCharacter: patternPlaceholderForDataCharacter,
30+
isDataCharacter: isDataCharacter
31+
)
32+
}
33+
34+
/**
35+
Intializes a delegate with a fixed pattern
36+
- parameters:
37+
- patternForDataString: `DecoratingTextFieldDelegate` will call this function passing current data string as a \
38+
parameter every time the data string changes, the returned pattern will subsequently be used to format the data \
39+
string passed.
40+
- patternPlaceholderForDataCharacter: a character that is not a formatting character.
41+
- isDataCharacter: a predicate to filter non-data characters from user's input. No matter what user tries to put \
42+
into the textfield, only characters for which `isDataCharacter` returns `true` will appear in the text field.
43+
## Example:
44+
A 16-digit VISA payment card pattern might look like this `####-####-####-####` `'#'` is a
45+
`patternPlaceholderForDataCharacter` and '`-`' is a formatting (decorating) character. Furthermore, to support \
46+
various kinds of payment cards a more complex behaviour may need to be implemented where the first 6 digits of a \
47+
payment card number will define total length and formatting pattern for any valid card number starting with those 6 \
48+
digits. This behaviour can be implemented by using `patternForDataString`.
49+
*/
50+
public init(
51+
patternForDataString: @escaping (String) -> String,
52+
patternPlaceholderForDataCharacter: Character,
53+
isDataCharacter: @escaping (Character) -> Bool)
54+
{
55+
self.patternForDataString = patternForDataString
56+
self.patternPlaceholderForDataCharacter = patternPlaceholderForDataCharacter
57+
self.isDataCharacter = isDataCharacter
58+
super.init()
59+
}
60+
61+
/// - Parameters:
62+
/// - dataString: a string conaining only data characters (see `isDataCharacter`).
63+
/// - Returns: a representation of `dataString` formatted using a corresponding pattern obtained using \
64+
/// `patternForDataString`.
65+
public func decorateString(_ dataString: String) -> String {
66+
var res = ""
67+
var dataIndex = dataString.startIndex
68+
let pattern = self.patternForDataString(dataString)
69+
for patternChar in pattern {
70+
if patternChar == patternPlaceholderForDataCharacter {
71+
if dataIndex == dataString.endIndex {
72+
return res
73+
}
74+
res += String(dataString[dataIndex])
75+
dataIndex = dataString.index(after: dataIndex)
76+
} else {
77+
res += String(patternChar)
78+
}
79+
}
80+
return res
81+
}
82+
83+
/// Strips formatting (decoration) characters from the input string.
84+
public func undecorateString(_ decoratedString: String) -> String {
85+
var res = ""
86+
for decoChar in decoratedString {
87+
if isDataCharacter(decoChar) {
88+
res += String(decoChar)
89+
}
90+
}
91+
return res
92+
}
93+
94+
fileprivate func convertDecoRange(_ decoRange: NSRange, fromDecoratedString decoratedString: String) -> NSRange {
95+
let decoPrefix = (decoratedString as NSString).substring(to: decoRange.location)
96+
let decoSubstring = (decoratedString as NSString).substring(with: decoRange)
97+
let dataPrefix = self.undecorateString(decoPrefix)
98+
let dataSubstring = self.undecorateString(decoSubstring)
99+
return NSRange(location: dataPrefix.nsLength, length: dataSubstring.nsLength)
100+
}
101+
102+
fileprivate func convertDataLocation(_ dataLocation: Int, toDecoratedString decoratedString: String) -> Int {
103+
if dataLocation <= 0 {
104+
return dataLocation
105+
}
106+
var res = 0
107+
var prefixLength = dataLocation
108+
for decoChar in decoratedString {
109+
let characterLength = String(decoChar).nsLength
110+
if isDataCharacter(decoChar) {
111+
if prefixLength <= 0 {
112+
return res
113+
}
114+
prefixLength -= characterLength
115+
}
116+
res += characterLength
117+
}
118+
return decoratedString.nsLength
119+
}
120+
}
121+
122+
extension DecoratingTextFieldDelegate: UITextFieldDelegate {
123+
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
124+
let decoString = textField.text ?? ""
125+
let decoReplacement = string
126+
let dataString = undecorateString(decoString)
127+
let dataReplacement = undecorateString(decoReplacement)
128+
var dataRange = convertDecoRange(range, fromDecoratedString: decoString)
129+
if range.length > 0 && decoReplacement.isEmpty && dataRange.length == 0 && dataRange.location > 0 {
130+
// probably backspace was hit with no data characters selected or prior to cursor
131+
// in this case we grow data range by one prior data character (if possible)
132+
// in order to erase that data character
133+
dataRange = (dataString as NSString).rangeOfComposedCharacterSequence(at: dataRange.location - 1)
134+
}
135+
136+
let newDataString = (dataString as NSString).replacingCharacters(in: dataRange, with: dataReplacement)
137+
let newDecoString = decorateString(newDataString)
138+
textField.text = newDecoString
139+
textField.sendActions(for: .editingChanged)
140+
141+
let newDataLocation = dataRange.location + dataReplacement.nsLength
142+
let newDecoLocation = convertDataLocation(newDataLocation, toDecoratedString: newDecoString)
143+
if let selPos = textField.position(from: textField.beginningOfDocument, offset: newDecoLocation) {
144+
textField.selectedTextRange = textField.textRange(from: selPos, to: selPos)
145+
}
146+
return false
147+
}
148+
}

FueledUtils/DimmingButton.swift

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Foundation
2+
import UIKit
3+
4+
/// A button that dims a view when highlighted.
5+
public final class DimmingButton: UIButton {
6+
/// A view to dim while highlited
7+
@IBOutlet public weak var dimmingView: UIView?
8+
9+
override public func awakeFromNib() {
10+
super.awakeFromNib()
11+
self.addTarget(self, action: #selector(DimmingButton.dim), for: .touchDown)
12+
self.addTarget(self, action: #selector(DimmingButton.dim), for: .touchDragEnter)
13+
self.addTarget(self, action: #selector(DimmingButton.undim), for: .touchDragExit)
14+
self.addTarget(self, action: #selector(DimmingButton.undim), for: .touchUpInside)
15+
self.addTarget(self, action: #selector(DimmingButton.undim), for: .touchCancel)
16+
}
17+
18+
@objc fileprivate func dim() {
19+
(self.dimmingView ?? self).alpha = 0.4
20+
}
21+
22+
@objc fileprivate func undim() {
23+
(self.dimmingView ?? self).alpha = 1
24+
}
25+
}

FueledUtils/HairlineView.swift

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Foundation
2+
import UIKit
3+
4+
/// A view with intrinsic content size of 1px by 1px
5+
open class HairlineView: UIView {
6+
override open var intrinsicContentSize : CGSize {
7+
let pixel = 1.0 / UIScreen.main.scale
8+
return CGSize(width: pixel, height: pixel)
9+
}
10+
11+
// prevent backgroundColor becoming clearColor on parent UITableViewCell selection
12+
override open var backgroundColor: UIColor? {
13+
set {
14+
if newValue != UIColor.clear {
15+
super.backgroundColor = newValue
16+
}
17+
}
18+
get {
19+
return super.backgroundColor
20+
}
21+
}
22+
}

FueledUtils/KeyboardInsetHelper.swift

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import Foundation
2+
import UIKit
3+
4+
/// Binds keyboard appearance and metrics to scroll view content and scroll bar insets and/or a layout constraint \
5+
/// relative to reference view. This object can be created and linked in a sotryboard.
6+
open class KeyboardInsetHelper: NSObject {
7+
/// Minimum inset
8+
@IBInspectable public var baseInset: CGFloat = 0
9+
/// The inset and constraint constant will be calculated in the coordinates of this view.
10+
@IBOutlet public weak var referenceView: UIView?
11+
/// Scroll view to adjust content inset at. When the keyboard appears or disappears, the inset will be adjusted to \
12+
/// align the bottom of the scroll view's content with the top of the keyboard (minimum `baseInset` takes priority).
13+
@IBOutlet public weak var scrollView: UIScrollView?
14+
/// When the keyboard appears or disappears, the constraint's constant will be set to the distance between the bottom of the \
15+
/// reference view and the top of the keyboard but no less than `baseInset`.
16+
@IBOutlet public weak var constraint: NSLayoutConstraint?
17+
18+
public override init() {
19+
super.init()
20+
let nc = NotificationCenter.default
21+
nc.addObserver(
22+
self,
23+
selector: #selector(handleKeyboardNotification(_:)),
24+
name: UIResponder.keyboardWillShowNotification,
25+
object: nil
26+
)
27+
nc.addObserver(
28+
self,
29+
selector: #selector(handleKeyboardNotification(_:)),
30+
name: UIResponder.keyboardWillHideNotification,
31+
object: nil
32+
)
33+
}
34+
35+
deinit {
36+
let nc = NotificationCenter.default
37+
nc.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
38+
nc.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
39+
}
40+
41+
@objc fileprivate func handleKeyboardNotification(_ notification: Notification) {
42+
guard let referenceView = referenceView,
43+
let userInfo = (notification as NSNotification).userInfo,
44+
let curve = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt,
45+
let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double,
46+
let keyboardFrameValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
47+
else { return }
48+
let keyboardFrame = referenceView.convert(keyboardFrameValue.cgRectValue, from: referenceView.window)
49+
let curveOption = UIView.AnimationOptions(rawValue: curve << 16)
50+
let animationOptions = curveOption.union(.beginFromCurrentState)
51+
let inset = max(baseInset, referenceView.bounds.maxY - keyboardFrame.minY)
52+
UIView.performWithoutAnimation {
53+
self.referenceView?.window?.layoutIfNeeded()
54+
}
55+
UIView.animate(
56+
withDuration: duration,
57+
delay: 0,
58+
options: animationOptions,
59+
animations: { self.updateForInset(inset) },
60+
completion: nil
61+
)
62+
}
63+
64+
open func updateForInset(_ inset: CGFloat) {
65+
scrollView?.contentInset.bottom = inset
66+
scrollView?.scrollIndicatorInsets.bottom = inset
67+
constraint?.constant = inset
68+
referenceView?.layoutIfNeeded()
69+
}
70+
}

0 commit comments

Comments
 (0)