Skip to content

Commit f476e97

Browse files
authored
Merge pull request #4 from youjinp/develop
Update currencyTextField and readme
2 parents 04108c6 + fc6a8a3 commit f476e97

File tree

5 files changed

+165
-83
lines changed

5 files changed

+165
-83
lines changed

.DS_Store

0 Bytes
Binary file not shown.

README.md

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,112 @@
1-
# SwiftUIKit, a collection of missing SwiftUI components
1+
![SwiftUIKit](https://user-images.githubusercontent.com/15708500/86090069-83443900-bafd-11ea-8692-41713679bae7.png)
22

3-
There is an example app at `SwiftUIKitExampleApp` which can be built and run.
3+
# SwiftUIKit
4+
5+
A collection of components that will simplify and accelerate your iOS development.
6+
7+
## Components
8+
9+
1. [CurrencyTextField](#1-currencytextfield)
10+
1. [AdaptToKeyboard](#2-adapttokeyboard)
11+
1. [ContactPicker](#3-contactpicker)
412

5-
# CurrencyTextField
613
## Demo
14+
15+
There is an example app at `SwiftUIKitExampleApp` which can be built and run. Just clone this repo and run it.
16+
17+
# 1. CurrencyTextField
18+
19+
## Demo
20+
721
![Currency Text Field Demo](demo/currencyTextField.gif)
822

923
## Description
24+
1025
Real time formatting of users input into currency format.
1126

1227
## Usage
28+
1329
```swift
30+
import SwiftUIKit
31+
1432
struct ContentView: View {
15-
@State private var value = 0.0
16-
var body: some View {
17-
CurrencyTextField("Amount", value: self.$value)
18-
.font(.largeTitle)
19-
.multilineTextAlignment(TextAlignment.center)
20-
}
33+
@State private var value = 0.0
34+
35+
var body: some View {
36+
CurrencyTextField("Amount", value: self.$value)
37+
.font(.largeTitle)
38+
.multilineTextAlignment(TextAlignment.center)
39+
}
2140
}
2241
```
2342

24-
# AdaptToKeyboard
43+
# 2. AdaptToKeyboard
44+
2545
## Demo
46+
2647
![Adapt to keyboard demo](demo/keyboardAdapt.gif)
2748

2849
## Description
50+
2951
Animate view's position when keyboard is shown / hidden
3052

3153
## Usage
54+
55+
```swift
56+
import SwiftUIKit
57+
58+
struct ContentView: View {
59+
var body: some View {
60+
VStack {
61+
Spacer()
62+
Button(action: {}) {
63+
Text("Hi")
64+
.adaptToKeyboard()
65+
}
66+
}
67+
}
68+
}
69+
```
70+
71+
# 3. ContactPicker
72+
73+
## Demo
74+
75+
![Contact picker demo](https://user-images.githubusercontent.com/15708500/86092942-55152800-bb02-11ea-9065-623a1a80d808.gif)
76+
77+
## Description
78+
79+
SwiftUI doesn't work well with `CNContactPickerViewController` if you just put it inside a `UIViewControllerRepresentable`. See this [stackoverflow post](https://stackoverflow.com/questions/57246685/uiviewcontrollerrepresentable-and-cncontactpickerviewcontroller/57621666#57621666). With `ContactPicker` here its just a one liner.
80+
81+
To enable multiple selection use `onSelectContacts` instead.
82+
83+
## Usage
84+
3285
```swift
33-
someView()
34-
.adaptToKeyboard()
86+
import SwiftUIKit
87+
88+
struct ContentView: View {
89+
@State var showPicker = false
90+
91+
var body: some View {
92+
ZStack {
93+
// This is just a dummy view to present the contact picker,
94+
// it won't display anything, so place this anywhere.
95+
// Here I have created a ZStack and placed it beneath the main view.
96+
ContactPicker(
97+
showPicker: $showPicker,
98+
onSelectContact: {c in
99+
self.contact = c
100+
}
101+
)
102+
VStack {
103+
Button(action: {
104+
self.showPicker.toggle()
105+
}) {
106+
Text("Pick a contact")
107+
}
108+
}
109+
}
110+
}
111+
}
35112
```

Sources/SwiftUIKit/views/CurrencyTextField.swift

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import UIKit
1111
public struct CurrencyTextField: UIViewRepresentable {
1212

1313
@Binding var value: Double?
14-
15-
public typealias UIViewType = UITextField
14+
private var isResponder: Binding<Bool>?
15+
private var tag: Int
1616

1717
private var placeholder: String
1818

@@ -31,6 +31,7 @@ public struct CurrencyTextField: UIViewRepresentable {
3131
private var isUserInteractionEnabled: Bool
3232
private var clearsOnBeginEditing: Bool
3333

34+
private var onReturn: () -> Void
3435
private var onEditingChanged: (Bool) -> Void
3536

3637
@Environment(\.layoutDirection) private var layoutDirection: LayoutDirection
@@ -39,6 +40,9 @@ public struct CurrencyTextField: UIViewRepresentable {
3940
public init(
4041
_ placeholder: String = "",
4142
value: Binding<Double?>,
43+
isResponder: Binding<Bool>? = nil,
44+
tag: Int = 0,
45+
4246
font: UIFont? = nil,
4347
foregroundColor: UIColor? = nil,
4448
accentColor: UIColor? = nil,
@@ -51,10 +55,14 @@ public struct CurrencyTextField: UIViewRepresentable {
5155
isSecure: Bool = false,
5256
isUserInteractionEnabled: Bool = true,
5357
clearsOnBeginEditing: Bool = false,
58+
onReturn: @escaping () -> Void = {},
5459
onEditingChanged: @escaping (Bool) -> Void = { _ in }
5560
) {
5661
self._value = value
5762
self.placeholder = placeholder
63+
self.isResponder = isResponder
64+
self.tag = tag
65+
5866
self.font = font
5967
self.foregroundColor = foregroundColor
6068
self.accentColor = accentColor
@@ -67,24 +75,21 @@ public struct CurrencyTextField: UIViewRepresentable {
6775
self.isSecure = isSecure
6876
self.isUserInteractionEnabled = isUserInteractionEnabled
6977
self.clearsOnBeginEditing = clearsOnBeginEditing
78+
self.onReturn = onReturn
7079
self.onEditingChanged = onEditingChanged
7180
}
7281

73-
public func makeCoordinator() -> CurrencyTextField.Coordinator {
74-
Coordinator(value: $value){ flag in
75-
self.onEditingChanged(flag)
76-
}
77-
}
78-
7982
public func makeUIView(context: UIViewRepresentableContext<CurrencyTextField>) -> UITextField {
8083

8184
let textField = UITextField()
8285
textField.delegate = context.coordinator
83-
textField.addDoneButtonOnKeyboard()
8486

8587
textField.addTarget(context.coordinator, action: #selector(context.coordinator.textFieldEditingDidBegin(_:)), for: .editingDidBegin)
8688
textField.addTarget(context.coordinator, action: #selector(context.coordinator.textFieldEditingDidEnd(_:)), for: .editingDidEnd)
8789

90+
// tag
91+
textField.tag = self.tag
92+
8893
// font
8994
if let f = context.environment.font {
9095
switch f {
@@ -156,14 +161,33 @@ public struct CurrencyTextField: UIViewRepresentable {
156161
return textField
157162
}
158163

164+
public func makeCoordinator() -> CurrencyTextField.Coordinator {
165+
Coordinator(value: $value, isResponder: self.isResponder, onReturn: self.onReturn){ flag in
166+
self.onEditingChanged(flag)
167+
}
168+
}
169+
159170
public func updateUIView(_ textField: UITextField, context: UIViewRepresentableContext<CurrencyTextField>) {
171+
172+
// value
160173
if self.value != context.coordinator.internalValue {
161174
if self.value == nil {
162175
textField.text = nil
163176
} else {
164177
textField.text = Formatter.currency.string(from: NSNumber(value: self.value!))
165178
}
166179
}
180+
181+
// set first responder ONCE
182+
// other times, let textfield handle it
183+
if self.isResponder?.wrappedValue == true && !textField.isFirstResponder && !context.coordinator.didBecomeFirstResponder {
184+
textField.becomeFirstResponder()
185+
context.coordinator.didBecomeFirstResponder = true
186+
}
187+
188+
// to dismiss, use dismissKeyboard()
189+
// don't uiView.resignFirstResponder()
190+
// otherwise many uibugs when using NavigationView
167191
}
168192

169193
public static func dismantleUIView(_ uiView: UITextField, coordinator: CurrencyTextField.Coordinator) {
@@ -172,13 +196,18 @@ public struct CurrencyTextField: UIViewRepresentable {
172196

173197
public class Coordinator: NSObject, UITextFieldDelegate {
174198
@Binding var value: Double?
199+
private var isResponder: Binding<Bool>?
200+
private var onReturn: ()->()
175201
var internalValue: Double?
176202
var onEditingChanged: (Bool)->()
203+
var didBecomeFirstResponder = false
177204

178-
init(value: Binding<Double?>, onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
205+
init(value: Binding<Double?>, isResponder: Binding<Bool>?, onReturn: @escaping () -> Void = {}, onEditingChanged: @escaping (Bool) -> Void = { _ in }) {
179206
print("coordinator init")
180207
_value = value
181208
internalValue = value.wrappedValue
209+
self.isResponder = isResponder
210+
self.onReturn = onReturn
182211
self.onEditingChanged = onEditingChanged
183212
}
184213

@@ -251,17 +280,33 @@ public struct CurrencyTextField: UIViewRepresentable {
251280
return true
252281
}
253282

283+
public func textFieldDidBeginEditing(_ textField: UITextField) {
284+
DispatchQueue.main.async {
285+
self.isResponder?.wrappedValue = true
286+
}
287+
}
288+
289+
public func textFieldDidEndEditing(_ textField: UITextField) {
290+
DispatchQueue.main.async {
291+
self.isResponder?.wrappedValue = false
292+
}
293+
}
294+
254295
@objc func textFieldEditingDidBegin(_ textField: UITextField){
255296
onEditingChanged(true)
256297
}
257298
@objc func textFieldEditingDidEnd(_ textField: UITextField){
258299
onEditingChanged(false)
259300
}
260301

261-
public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
262-
if reason == .committed {
302+
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
303+
if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
304+
nextField.becomeFirstResponder()
305+
} else {
263306
textField.resignFirstResponder()
264307
}
308+
self.onReturn()
309+
return true
265310
}
266311
}
267312
}
@@ -350,36 +395,3 @@ fileprivate extension NumberFormatter {
350395
self.numberStyle = numberStyle
351396
}
352397
}
353-
354-
fileprivate extension UITextField{
355-
356-
@IBInspectable var doneAccessory: Bool{
357-
get{
358-
return self.doneAccessory
359-
}
360-
set (hasDone) {
361-
if hasDone{
362-
addDoneButtonOnKeyboard()
363-
}
364-
}
365-
}
366-
367-
func addDoneButtonOnKeyboard(){
368-
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
369-
doneToolbar.barStyle = .default
370-
371-
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
372-
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction))
373-
374-
let items = [flexSpace, done]
375-
doneToolbar.items = items
376-
doneToolbar.sizeToFit()
377-
378-
self.inputAccessoryView = doneToolbar
379-
}
380-
381-
@objc func doneButtonAction(){
382-
self.resignFirstResponder()
383-
}
384-
}
385-

SwiftUIKitExampleApp/.DS_Store

0 Bytes
Binary file not shown.

SwiftUIKitExampleApp/SwiftUIKitExampleApp/CurrencyTextFieldDemoView.swift

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,26 @@ import SwiftUIKit
1212
struct CurrencyTextFieldDemoView: View {
1313

1414
@State private var value: Double? = 0.0
15-
@State private var offset: CGFloat = 0
15+
@State private var focus = false
1616

1717
var body: some View {
18-
GeometryReader { geometry in
19-
VStack {
20-
CurrencyTextField("Amount", value: self.$value, onEditingChanged: { flag in
21-
self.offset = flag ? -150 : 0
22-
})
23-
.font(.largeTitle)
24-
.multilineTextAlignment(TextAlignment.center)
25-
.padding()
26-
.background(
27-
RoundedRectangle(cornerRadius: 10)
28-
.fill(Color.white))
29-
.shadow(radius: 15)
30-
.padding()
31-
Button(action: {
32-
print("\(self.value)")
33-
UIApplication.shared.resignFirstResponder()
34-
}) {
35-
Text("Print")
36-
}
37-
}.frame(width: geometry.size.width, height: geometry.size.height)
38-
.edgesIgnoringSafeArea(.all)
39-
.background(Color(hue: 0, saturation: 0, brightness: 0.9))
40-
41-
}.offset(y: self.offset)
18+
VStack {
19+
CurrencyTextField("Amount", value: self.$value, isResponder: $focus)
20+
.font(.largeTitle)
21+
.multilineTextAlignment(TextAlignment.center)
22+
Button(action: {
23+
print("\(String(describing: self.value))")
24+
}) {
25+
Text("Print")
26+
}
27+
Spacer()
28+
}.padding([.top], 40)
29+
.contentShape(Rectangle())
30+
.dismissKeyboardOnSwipe()
31+
.dismissKeyboardOnTap()
32+
.onAppear {
33+
self.focus = true
34+
}
4235
}
43-
36+
4437
}

0 commit comments

Comments
 (0)