Skip to content

Commit 6c8fe6d

Browse files
committed
Add OptionPickerControl
1 parent c267726 commit 6c8fe6d

File tree

4 files changed

+213
-1
lines changed

4 files changed

+213
-1
lines changed

ICInputAccessory.podspec

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@ Pod::Spec.new do |s|
2323
s.source = { git: "https://github.com/polydice/ICInputAccessory.git", tag: "v#{s.version}" }
2424
s.requires_arc = true
2525

26-
s.default_subspecs = "KeyboardDismissTextField", "TokenField"
26+
s.default_subspecs = "KeyboardDismissTextField", "OptionPickerControl", "TokenField"
2727

2828
s.subspec "KeyboardDismissTextField" do |sp|
2929
sp.source_files = "Source/KeyboardDismissTextField/*.swift"
3030
sp.resources = "Source/KeyboardDismissTextField/*.xcassets"
3131
end
3232

33+
s.subspec "OptionPickerControl" do |sp|
34+
sp.source_files = "Source/OptionPickerControl/*.swift"
35+
end
36+
3337
s.subspec "TokenField" do |sp|
3438
sp.source_files = "Source/TokenField/*.swift"
3539
end

ICInputAccessory.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
B528196D1C9035BE007D01D5 /* InsetLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52819691C9035BE007D01D5 /* InsetLabel.swift */; };
1313
B528196E1C9035BE007D01D5 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = B528196A1C9035BE007D01D5 /* Token.swift */; };
1414
B528196F1C9035BE007D01D5 /* TokenField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B528196B1C9035BE007D01D5 /* TokenField.swift */; };
15+
B52ADB1920132F0C00D96B87 /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52ADB1720132F0C00D96B87 /* Option.swift */; };
16+
B52ADB1A20132F0C00D96B87 /* OptionPickerControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52ADB1820132F0C00D96B87 /* OptionPickerControl.swift */; };
1517
B533768B1F4436D000230739 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B533768A1F4436D000230739 /* AppDelegate.swift */; };
1618
B53376921F4436D000230739 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B53376911F4436D000230739 /* Assets.xcassets */; };
1719
B53376951F4436D000230739 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B53376931F4436D000230739 /* LaunchScreen.storyboard */; };
@@ -70,6 +72,8 @@
7072
B52819691C9035BE007D01D5 /* InsetLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InsetLabel.swift; path = Source/TokenField/InsetLabel.swift; sourceTree = SOURCE_ROOT; };
7173
B528196A1C9035BE007D01D5 /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Token.swift; path = Source/TokenField/Token.swift; sourceTree = SOURCE_ROOT; };
7274
B528196B1C9035BE007D01D5 /* TokenField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TokenField.swift; path = Source/TokenField/TokenField.swift; sourceTree = SOURCE_ROOT; };
75+
B52ADB1720132F0C00D96B87 /* Option.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Option.swift; path = Source/OptionPickerControl/Option.swift; sourceTree = SOURCE_ROOT; };
76+
B52ADB1820132F0C00D96B87 /* OptionPickerControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OptionPickerControl.swift; path = Source/OptionPickerControl/OptionPickerControl.swift; sourceTree = SOURCE_ROOT; };
7377
B53376881F4436D000230739 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
7478
B533768A1F4436D000230739 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7579
B53376911F4436D000230739 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -148,6 +152,15 @@
148152
name = TokenField;
149153
sourceTree = "<group>";
150154
};
155+
B52ADB1220132EEF00D96B87 /* OptionPickerControl */ = {
156+
isa = PBXGroup;
157+
children = (
158+
B52ADB1720132F0C00D96B87 /* Option.swift */,
159+
B52ADB1820132F0C00D96B87 /* OptionPickerControl.swift */,
160+
);
161+
name = OptionPickerControl;
162+
sourceTree = "<group>";
163+
};
151164
B53376891F4436D000230739 /* Example */ = {
152165
isa = PBXGroup;
153166
children = (
@@ -202,6 +215,7 @@
202215
isa = PBXGroup;
203216
children = (
204217
B52819671C90358C007D01D5 /* KeyboardDismissAccessory */,
218+
B52ADB1220132EEF00D96B87 /* OptionPickerControl */,
205219
B52819701C9035C3007D01D5 /* TokenField */,
206220
B56BC42D1C89A7EA00C20AD6 /* ICInputAccessory.h */,
207221
B548C5AC1C8D69A5009D5AEE /* Images.xcassets */,
@@ -431,6 +445,8 @@
431445
B528196D1C9035BE007D01D5 /* InsetLabel.swift in Sources */,
432446
B56BC4361C89A8D800C20AD6 /* KeyboardDismissAccessoryView.swift in Sources */,
433447
B548C5EE1C8EB9E2009D5AEE /* KeyboardDismissTextField.swift in Sources */,
448+
B52ADB1920132F0C00D96B87 /* Option.swift in Sources */,
449+
B52ADB1A20132F0C00D96B87 /* OptionPickerControl.swift in Sources */,
434450
B528196E1C9035BE007D01D5 /* Token.swift in Sources */,
435451
B528196F1C9035BE007D01D5 /* TokenField.swift in Sources */,
436452
);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// Option.swift
3+
// Flashcards
4+
//
5+
// Created by Ben on 22/11/2017.
6+
// Copyright © 2017 bcylin.
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy
9+
// of this software and associated documentation files (the "Software"), to deal
10+
// in the Software without restriction, including without limitation the rights
11+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
// copies of the Software, and to permit persons to whom the Software is
13+
// furnished to do so, subject to the following conditions:
14+
//
15+
// The above copyright notice and this permission notice shall be included in all
16+
// copies or substantial portions of the Software.
17+
//
18+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
// SOFTWARE.
25+
//
26+
27+
import Foundation
28+
29+
public protocol OptionDescriptive: Equatable {
30+
var title: String { get }
31+
static var optionalText: String { get }
32+
}
33+
34+
////////////////////////////////////////////////////////////////////////////////
35+
36+
37+
public struct Option<T: OptionDescriptive>: Equatable {
38+
39+
// MARK: - Initialization
40+
41+
public static func optional() -> Option<T> {
42+
return Option<T>()
43+
}
44+
45+
public init(_ value: T) {
46+
self.value = value
47+
}
48+
49+
// MARK: - Properties
50+
51+
public var title: String {
52+
return value?.title ?? T.optionalText
53+
}
54+
55+
public let value: T?
56+
57+
// MARK: - Private
58+
59+
private init() {
60+
self.value = nil
61+
}
62+
63+
// MARK: - Equatable
64+
65+
public static func == (lhs: Option<T>, rhs: Option<T>) -> Bool {
66+
return lhs.value == rhs.value
67+
}
68+
69+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//
2+
// OptionPickerControl.swift
3+
// Flashcards
4+
//
5+
// Created by Ben on 27/11/2017.
6+
// Copyright © 2017 bcylin.
7+
//
8+
// Permission is hereby granted, free of charge, to any person obtaining a copy
9+
// of this software and associated documentation files (the "Software"), to deal
10+
// in the Software without restriction, including without limitation the rights
11+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
// copies of the Software, and to permit persons to whom the Software is
13+
// furnished to do so, subject to the following conditions:
14+
//
15+
// The above copyright notice and this permission notice shall be included in all
16+
// copies or substantial portions of the Software.
17+
//
18+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
// SOFTWARE.
25+
//
26+
27+
import UIKit
28+
29+
open class OptionPickerControl<T: OptionDescriptive>: UIControl,
30+
UIPickerViewDataSource,
31+
UIPickerViewDelegate {
32+
33+
// MARK: - Initialization
34+
35+
public init() {
36+
super.init(frame: .zero)
37+
addSubview(hiddenTextField)
38+
}
39+
40+
required public init?(coder aDecoder: NSCoder) {
41+
fatalError("init(coder:) is not supported")
42+
}
43+
44+
// MARK: - Properties
45+
46+
public var options: [Option<T>] = [Option<T>.optional()]
47+
public var selectedOption: Option<T> = Option<T>.optional() {
48+
didSet {
49+
if hiddenTextField.isFirstResponder {
50+
sendActions(for: .valueChanged)
51+
} else if let index = options.index(of: selectedOption) {
52+
picker.selectRow(index, inComponent: 0, animated: false)
53+
}
54+
}
55+
}
56+
57+
// MARK: - Lazy Instantiation
58+
59+
private lazy var doneBarButton: UIBarButtonItem =
60+
UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissPicker(_:)))
61+
62+
private lazy var pickerToolbar: UIToolbar = {
63+
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 44))
64+
toolbar.items = [
65+
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
66+
self.doneBarButton
67+
]
68+
return toolbar
69+
}()
70+
71+
private lazy var picker: UIPickerView = {
72+
let picker = UIPickerView()
73+
picker.dataSource = self
74+
picker.delegate = self
75+
return picker
76+
}()
77+
78+
private lazy var hiddenTextField: UITextField = {
79+
let textField = UITextField()
80+
textField.inputAccessoryView = self.pickerToolbar
81+
textField.inputView = self.picker
82+
return textField
83+
}()
84+
85+
// MARK: - UIResponder
86+
87+
@discardableResult
88+
override open func becomeFirstResponder() -> Bool {
89+
return super.becomeFirstResponder() || hiddenTextField.becomeFirstResponder()
90+
}
91+
92+
@discardableResult
93+
override open func resignFirstResponder() -> Bool {
94+
return hiddenTextField.resignFirstResponder()
95+
}
96+
97+
// MARK: - UIPickerViewDataSource
98+
99+
open func numberOfComponents(in pickerView: UIPickerView) -> Int {
100+
return 1
101+
}
102+
103+
open func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
104+
return options.count
105+
}
106+
107+
// MARK: - UIPickerViewDelegate
108+
109+
open func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
110+
return options[row].title
111+
}
112+
113+
open func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
114+
selectedOption = options[row]
115+
}
116+
117+
// MARK: - IBActions
118+
119+
@objc private func dismissPicker(_ sender: Any) {
120+
resignFirstResponder()
121+
}
122+
123+
}

0 commit comments

Comments
 (0)