Seconds on UIDatePicker (.countDownTimer) - swift

How do I add a seconds column to the UIDatePicker?
I am currently displaying this:
With this UIViewRepresentable in SwiftUI
struct DurationPicker: UIViewRepresentable {
#Binding var duration: TimeInterval
func makeUIView(context: Context) -> UIDatePicker {
let datePicker = UIDatePicker()
datePicker.datePickerMode = .countDownTimer
datePicker.addTarget(context.coordinator, action: #selector(Coordinator.updateDuration), for: .valueChanged)
return datePicker
}
func updateUIView(_ datePicker: UIDatePicker, context: Context) {
datePicker.countDownDuration = duration
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
let parent: DurationPicker
init(_ parent: DurationPicker) {
self.parent = parent
}
#objc func updateDuration(datePicker: UIDatePicker) {
parent.duration = datePicker.countDownDuration
}
}
}
Here is my view code for reference
struct ContentView: View {
#Binding var interval:TimeInterval
init( interval: Binding<TimeInterval> = .constant(0) ) {
_interval = interval
}
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
.padding()
DurationPicker(duration: $interval)
NavigationLink(destination: DetailView()) {
Text("Show Detail View")
}
}
}
}
}
I can't figure out how I might go about adding a seconds column, even though the official clock app on iOS displays one.

Related

I am unable to use a spacer to push a text view to the top of my swift ui VStack view, when adding UI wrappers

I have got a piece of code below but the issue is the text field is positioned in the middle of the view and when I add spacers below to push the textfield to the top of the view, it doesn't get pushed the textfield to the top of the view but rather it only moves when i specify the minLength of the spacer but we know this isnt the right approach because different phones have different dimensions. so can someone provide a proper fix to this or spot what is hindering spacer() from working
import SwiftUI
struct FocusedTextField: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
var parent: FocusedTextField
init(_ parent: FocusedTextField) {
self.parent = parent
}
func textFieldDidEndEditing(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
#Binding var text: String
#Binding var isFocused: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<FocusedTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.textColor = .black
textField.placeholder = "Enter some text"
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<FocusedTextField>) {
uiView.text = text
if isFocused && !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
}
}
struct testList: View {
#State private var text: String = ""
#State private var isFocused: Bool = false
var body: some View {
VStack{
FocusedTextField(text: $text, isFocused: $isFocused)
}
.onAppear {
self.text = ""
self.isFocused = true
}
.padding()
}
}
struct textList_Previews: PreviewProvider {
static var previews: some View {
testList()
}
}
For vertical wrap you need to make add some code in makeUIView:
textField.setContentHuggingPriority(.required, for: .vertical)
and add spacer() in testList()
Try below code:
struct FocusedTextField: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
var parent: FocusedTextField
init(_ parent: FocusedTextField) {
self.parent = parent
}
func textFieldDidEndEditing(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
#Binding var text: String
#Binding var isFocused: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<FocusedTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.textColor = .black
textField.placeholder = "Enter some text"
textField.setContentHuggingPriority(.required, for: .vertical) //<---------------here
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<FocusedTextField>) {
uiView.text = text
if isFocused && !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
}
}
struct testList: View {
#State private var text: String = ""
#State private var isFocused: Bool = false
var body: some View {
VStack{
FocusedTextField(text: $text, isFocused: $isFocused)
Spacer() //<--------- here
}
.onAppear {
self.text = ""
self.isFocused = true
}
.padding()
}
}
struct textList_Previews: PreviewProvider {
static var previews: some View {
testList()
}
}
The problem is in the FocusedTextField. If you use the TextField from SwiftUI itself, you can easily push it up in the VStack by using Spacer().
struct ContentView: View {
#State private var text: String = ""
var body: some View {
VStack{
TextField("Enter some Text", text: $text)
Spacer()
}
.onAppear {
self.text = ""
}
.padding()
}
}
I highly recommend you look into the #FocusState property wrapper to implement the focus state.
You can read more about it here:
https://developer.apple.com/documentation/swiftui/focusstate

how to use textField Extension to disable Copy and paste in swift ios

I am trying to disable copy and paste functionality in a textField with the help of swift without using any separate cocoatouch class.
Can you suggest how to do it using Either keyboard extension or textfield extension.
Any response will be appreciated.
Create a custom TextField as use as follows
import SwiftUI
struct CustomeTextField: View {
#State var textStr = ""
var body: some View {
VStack(spacing: 10) {
Text("This is textfield:")
.font(.body)
.foregroundColor(.gray)
TextFieldWrapperView(text: self.$textStr)
.background(Color.gray)
.frame(width: 200, height: 50)
}
.frame(height: 40)
}
}
struct TextFieldWrapperView: UIViewRepresentable {
#Binding var text: String
func makeCoordinator() -> TFCoordinator {
TFCoordinator(self)
}
}
extension TextFieldWrapperView {
func makeUIView(context: UIViewRepresentableContext<TextFieldWrapperView>) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
}
}
class TFCoordinator: NSObject, UITextFieldDelegate {
var parent: TextFieldWrapperView
init(_ textField: TextFieldWrapperView) {
self.parent = textField
}
func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
if action == #selector(UIResponderStandardEditActions.paste(_:)) {
return false
}
return canPerformAction(action: action, withSender: sender)
}
}

Why does a binding in UIViewRepresentables Coordinator have a constant read value

I have been writing a UIViewRepresentable and noticing some curios effects in regards to a binding I'm passing into the view.
When I read the bindings value in the coordinator through the saved UIViewRepresentable the value is always the value that it was initialized with. Trying to update the same binding however triggers an update in the surrounding UI.
This is code produces this behavior:
struct NativeTextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.borderStyle = .roundedRect
view.addTarget(
context.coordinator,
action: #selector(Coordinator.updateText(sender:)),
for: .editingChanged
)
return view
}
func updateUIView(_ uiView: UITextField, context: Context) {
context.coordinator.updateUI(uiView)
}
func makeCoordinator() -> Coordinator {
Coordinator(_text)
}
class Coordinator: NSObject {
#Binding var text: String
init(_ text: Binding<String>){
_text = text
}
#objc func updateText(sender: UITextField){
text=sender.text!
}
func updateUI(_ uiView: UITextField) {
uiView.text = text
}
}
}
If I hover give my updateUI method a NativeTextView parameter, and use the .text field of it through the parameter, I read the correct value and the UI works correctly:
struct NativeTextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.borderStyle = .roundedRect
view.addTarget(
context.coordinator,
action: #selector(Coordinator.updateText(sender:)),
for: .editingChanged
)
return view
}
func updateUIView(_ uiView: UITextField, context: Context) {
context.coordinator.updateUI(uiView, view: self)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
var myView: NativeTextView
init(_ view: NativeTextView){
self.myView=view
}
#objc func updateText(sender: UITextField){
myView.text=sender.text!
}
func updateUI(_ uiView: UITextField, view: NativeTextView) {
uiView.text = view.text
}
}
}
It seems that the binding retains the ability to write to the outside #State variable but does not manage to access the current states value correctly. I'm guessing that this has something to do with the recreation of the NativeTextView view when SwiftUI notices an update of the #State, but I have not been able to find any documentation that would explain this behavior.
Does anyone know why this happens?
PS: for completeness this is my ContentViews body:
ZStack {
Color.red
VStack {
Text(test)
.padding()
.onTapGesture() {
test = "Bla"
}
NativeTextView(text: $test)
}
}

SwiftUI: Hide keyboard but show cursor

I want to use custom buttons to input text into a TextField, but still show and move the cursor. Is there a way to hide the default keyboard while still showing the cursor?
I was hoping for something like this:
TextField("", text: $text)
.keyboardType(.none)
Here is what it currently looks like.
You can use UIViewRepresentable class and pass the input view as an empty view.
struct HideKeyboardTextField: UIViewRepresentable {
var placeholder: String
#Binding var text: String
func makeUIView(context: UIViewRepresentableContext<HideKeyboardTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.placeholder = placeholder
textField.inputView = UIView()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<HideKeyboardTextField>) {
uiView.text = text
}
func makeCoordinator() -> HideKeyboardTextField.Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: HideKeyboardTextField
init(parent: HideKeyboardTextField) {
self.parent = parent
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
parent.text = textField.text ?? ""
}
}
}
}
Usage:
struct ContentView: View {
#State var text: String = ""
var body: some View {
HideKeyboardTextField(placeholder: "Input", text: $text)
}
}

SwiftUI inputAccesoryView Implementation

I am trying to implement an inputAccessoryView on a TextField in SwiftUI. The goal is to have a "Done" Button appear above the Keyboard which when pressed gets rid of the keyboard (i.e. resignFirstResponder()).
I came across the following Medium article which purports to implement this behavior exactly as I would require, however, I am struggling to get it working.
Medium link containing method to be implemented.
I have tried to implement this in a blank XCode project, my code compiles, however, the TextField never shows up, and I cannot touch in the area it should be to bring up the keyboard. How do I correctly implement this code to get the desired behavior?
Code
import Foundation
import UIKit
import SwiftUI
class TextFieldViewController
: UIViewController {
// our custom text field will report changes to the outside
let text: Binding<String>?
// if the toolbar (see below) is used (Done), the keyboard shall be dismissed
// and optionally we execute a provided closure
let onDismiss: (() -> Void)?
init (
text: Binding<String>
, onDismiss: (() -> Void)?) {
self.text = text
self.onDismiss = onDismiss
super.init(
nibName: nil //"<XIB>"
, bundle: nil //Bundle.main?
)
}
required init?(coder: NSCoder) {
self.text = nil
self.onDismiss = nil
super.init(coder: coder)
}
// helper function to encapsulate calling the "view" of UIViewController
fileprivate func getTextField() -> UITextField? {
return view as? UITextField
}
override func viewDidLoad() {
let textField = self.getTextField()
guard textField != nil else {
return
}
// configure a toolbar with a Done button
let toolbar = UIToolbar()
toolbar.setItems([
// just moves the Done item to the right
UIBarButtonItem(
barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace
, target: nil
, action: nil
)
, UIBarButtonItem(
title: "Done"
, style: UIBarButtonItem.Style.done
, target: self
, action: #selector(self.onSet)
)
]
, animated: true
)
toolbar.barStyle = UIBarStyle.default
toolbar.sizeToFit()
textField?.inputAccessoryView = toolbar
}
#objc private func onSet() {
let textField = self.getTextField()
textField?.resignFirstResponder()
self.text?.wrappedValue = textField?.text ?? ""
self.onDismiss?()
}
}
// The SwiftUI view, wrapping the UITextField
struct TextFieldView: View {
var text: Binding<String>
var onDismissKeyboard: (() -> Void)?
var body: some View {
TextFieldRepresentable(
text: self.text
, dismissKeyboardCallback: self.onDismissKeyboard
)
}
}
// The UIViewControllerRepresentable, feeding and controlling the UIViewController
struct TextFieldRepresentable
: UIViewControllerRepresentable {
// the callback
let dismissKeyboardCallback: (() -> Void)?
// created in the previous file/gist
let viewController: TextFieldViewController
init (
text: Binding<String>
, dismissKeyboardCallback: (() -> Void)?) {
self.dismissKeyboardCallback = dismissKeyboardCallback
self.viewController = TextFieldViewController(
text: text
, onDismiss: dismissKeyboardCallback
)
}
// UIViewControllerRepresentable
func makeUIViewController(context: Context) -> UIViewController {
return viewController
}
// UIViewControllerRepresentable
func updateUIViewController(_ viewController: UIViewController, context: Context) {
}
}
struct ContentView : View {
#State var email:String = ""
var body: some View {
HStack{
Circle()
TextFieldView(text: $email)
Circle()
}
}
}
Here is a demo with custom toolbar & binding for entered text, but simplified by excluding on dismiss callback (as it is not important for approach demo), just to have less code. Hope it will be helpful.
import SwiftUI
import UIKit
import Combine
struct CustomInputTextField : UIViewRepresentable {
#Binding var text: String
let textField = UITextField(frame: CGRect(x:0, y:0, width: 100, height: 32)) // just any
func makeUIView(context: UIViewRepresentableContext<CustomInputTextField>) -> UITextField {
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomInputTextField>) {
self.textField.text = text
}
func makeCoordinator() -> CustomInputTextField.Coordinator {
let coordinator = Coordinator(self)
// configure a toolbar with a Done button
let toolbar = UIToolbar()
toolbar.setItems([
// just moves the Done item to the right
UIBarButtonItem(
barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace
, target: nil
, action: nil
)
, UIBarButtonItem(
title: "Done"
, style: UIBarButtonItem.Style.done
, target: coordinator
, action: #selector(coordinator.onSet)
)
]
, animated: true
)
toolbar.barStyle = UIBarStyle.default
toolbar.sizeToFit()
textField.inputAccessoryView = toolbar
return coordinator
}
typealias UIViewType = UITextField
class Coordinator: NSObject {
let owner: CustomInputTextField
private var subscriber: AnyCancellable
init(_ owner: CustomInputTextField) {
self.owner = owner
subscriber = NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: owner.textField)
.sink(receiveValue: { _ in
owner.$text.wrappedValue = owner.textField.text ?? ""
})
}
#objc fileprivate func onSet() {
owner.textField.resignFirstResponder()
}
}
}
struct DemoCustomKeyboardInput : View {
#State var email:String = ""
var body: some View {
VStack{
CustomInputTextField(text: $email).border(Color.black)
.padding(.horizontal)
.frame(maxHeight: 32)
Divider()
Text("Entered text: \(email)")
}
}
}
struct DemoCustomKeyboardInput_Previews: PreviewProvider {
static var previews: some View {
DemoCustomKeyboardInput()
}
}
I use this code multi line textfield.
SwiftUI
Swift5
Version 11.3 (11C29)
struct MultiLineTextField: UIViewRepresentable {
#Binding var text: String
let onEditingChanged: (Bool) -> Void
init(text: Binding<String>, onEditingChanged: #escaping (Bool) -> Void = {_ in}) {
self._text = text
self.onEditingChanged = onEditingChanged
}
func makeCoordinator() -> MultiLineTextField.Coordinator {
return MultiLineTextField.Coordinator(parent1: self)
}
func makeUIView(context: UIViewRepresentableContext<MultiLineTextField>) -> UITextView {
let textView = UITextView()
textView.isEditable = true
textView.isUserInteractionEnabled = true
textView.isScrollEnabled = true
textView.font = .systemFont(ofSize: 20)
textView.delegate = context.coordinator
textView.text = self.text
/******* toolbar add **********/
let toolbar = UIToolbar()
toolbar.setItems(
[
UIBarButtonItem(
title: "Done",
style: UIBarButtonItem.Style.done,
target: self,
action: nil
)
]
, animated: true
)
toolbar.barStyle = UIBarStyle.default
toolbar.sizeToFit()
textView.inputAccessoryView = toolbar
/******* toolbar add **********/
return textView
}
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<MultiLineTextField>) {
if uiView.text != self.text {
uiView.text = self.text
}
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: MultiLineTextField
let onEditingChanged: (Bool) -> Void
init(parent1: MultiLineTextField, onEditingChanged: #escaping (Bool) -> Void = {_ in}) {
self.parent = parent1
self.onEditingChanged = onEditingChanged
}
func textViewDidChange(_ textView: UITextView) {
self.parent.text = textView.text
}
func textViewDidBeginEditing(_ textView: UITextView) {
onEditingChanged(true)
}
func textViewDidEndEditing(_ textView: UITextView) {
onEditingChanged(false)
}
}
}
I've solved this problem using 99% pure SwiftUI on iOS 14.
That's my implementation:
import SwiftUI
struct ContentView: View {
#State private var showtextFieldToolbar = false
#State private var text = ""
var body: some View {
ZStack {
VStack {
TextField("Write here", text: $text) { isChanged in
if isChanged {
showtextFieldToolbar = true
}
} onCommit: {
showtextFieldToolbar = false
}
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
VStack {
Spacer()
if showtextFieldToolbar {
HStack {
Spacer()
Button("Close") {
showtextFieldToolbar = false
UIApplication.shared
.sendAction(#selector(UIResponder.resignFirstResponder),
to: nil, from: nil, for: nil)
}
.foregroundColor(Color.black)
.padding(.trailing, 12)
}
.frame(idealWidth: .infinity, maxWidth: .infinity,
idealHeight: 44, maxHeight: 44,
alignment: .center)
.background(Color.gray)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct InputAccessory: UIViewRepresentable {
var placeHolder: String
func makeUIView(context: Context) -> UITextField {
let toolbar = UIToolbar()
toolbar.setItems([
// just moves the Done item to the right
UIBarButtonItem(
barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace
, target: nil
, action: nil
)
, UIBarButtonItem(
title: "Done"
, style: UIBarButtonItem.Style.done
, target: self
, action: nil
)
]
, animated: true
)
toolbar.barStyle = UIBarStyle.default
toolbar.sizeToFit()
let customView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 44))
customView.backgroundColor = UIColor.red
let sampleTextField = UITextField(frame: CGRect(x: 20, y: 100, width: 300, height: 40))
sampleTextField.inputAccessoryView = toolbar
sampleTextField.placeholder = placeHolder
return sampleTextField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.endEditing(true)
}
}
struct ContentView : View {
#State var email:String = "e"
var body: some View {
HStack{
Circle()
InputAccessory(placeHolder: "hello")
Circle()
}
}
}
PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
Now you can hide and show the textfield with the "showInput" state. The next problem is, that you have to open your keyboard at a certain event and show the textfield. That's again not possible with SwiftUI and you have to go back to UiKit and making it first responder.
Overall, at the current state it's not possible to work with the keyboard or with the certain textfield method.