dismissing the keyboard on UIAlertController UITextField - swift

normally view.endEditing(true) always removes the keyboard in swift on iOS.
however this is not always true, like when the UITextField is part of a self.present()'ed modal view i.e. a form inside a UIAlertController
Example
let alert = UIAlertController(title: _title, message: "Choose", preferredStyle: .alert)
let numberLabel = UILabel(frame: CGRect(x:10, y:0, width:90, height:20))
numberLabel.textColor = .black
numberLabel.textAlignment = NSTextAlignment.left
numberLabel.font = UIFont(name: titleLabel.font.fontName, size: 16)
numberLabel.font = UIFont.boldSystemFont(ofSize: titleLabel.font.pointSize)
numberLabel.text = "Quantity"
let quantityField = UITextField(frame: CGRect(x:100, y:0, width:140, height:25))
quantityField.layer.borderWidth = 1
quantityField.keyboardType = .numberPad
quantityField.layer.borderColor = UIColor.lightGray.cgColor
quantityField.delegate = self
self.addDoneButtonOnKeyboard(textField: quantityField)
self.present(alert, animated: true, completion: nil)
now in this self.addDoneButtonOnKeyboard method I have this nifty code
func addDoneButtonOnKeyboard(textField: UITextField){
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect(x:0, y:0, width:320, height:50))
doneToolbar.barStyle = UIBarStyle.default
let flexSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.done, target: self, action: #selector(ViewControllerDisplay.doneButtonAction(_:)))
let items = NSMutableArray()
items.add(flexSpace)
items.add(done)
doneToolbar.items = items as? [UIBarButtonItem]
doneToolbar.sizeToFit()
textField.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction(_ btn: UIBarButtonItem){
view.endEditing(true)
}
the purpose of all this is so that a "Done" keyboard button appears. It does. In fact it looks great, I can also capture the doneButtonAction() event which is great, but I am only capturing the UIBarButtonItem so I can't use UITextField.resignFirstResponder() method, even though I know it will work. I have to use "view"
What I want to do is call alert.endEditting(true) but I've tried cycling through the subviews, screening for any occurrences of UIAlertController but self.present doesn't work that way
Can anyone help me clear this keyboard off the alert view?

I fixed it by getting the topMost view Controller, and calling endEditing(true) I think this question should remain up it's quite a useful titbit for someone one day
#objc func doneButtonAction(_ btn: UIBarButtonItem){
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.view.endEditing(true)
}
}

Related

Swift: Set UIBarButtonItem as source for popover WITHOUT tap?

I want to show a popover on iPad as soon as my view loads having as source a button on the top right corner.
The popover displays properly on button tap, but I'm having trouble finding a way to display it without the button tap, when the page first loads. Is this possible?
This is what I have:
func displayOptions(sourceButton: UIBarButtonItem)
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
if let popoverController = actionSheet.popoverPresentationController {
var rect = CGRect(x: 0, y: 0, width: 0, height: 0)
popoverController.barButtonItem = sourceButton
popoverController.sourceRect = rect
popoverController.permittedArrowDirections = .up
}
viewController.present(actionSheet, animated: true, completion: nil)
}
This code works properly on button tap, but the app crashes if I try to call this function from viewDidLoad or viewDidAppear:
You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem.
Try using UIPopoverPresentationControllerDelegate
Code example:
viewDidLoad
let buttonCustom = UIBarButtonItem(title: "Custom", style: .done, target: self, action: #selector(ViewController.customBarButtonAction))
self.navigationItem.rightBarButtonItem = buttonCustom
displayOptions()
displayOptions
func displayOptions() {
let actionSheet = UIAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)
if let popoverController = actionSheet.popoverPresentationController {
let rect = CGRect(x: 0, y: 0, width: 0, height: 0)
popoverController.sourceRect = rect
popoverController.sourceView = self.view
popoverController.permittedArrowDirections = .up
popoverController.delegate = self
}
self.present(actionSheet, animated: true, completion: nil)
}
using UIPopoverPresentationControllerDelegate
func prepareForPopoverPresentation(_ popoverPresentationController: UIPopoverPresentationController) {
popoverPresentationController.barButtonItem = self.navigationItem.rightBarButtonItem
}
You need to provide the sourceView.
UIAlertController must have a title, a message or an action to display.
// Create a bar button item in ViewDidLoad.
let button1 = UIBarButtonItem(image: UIImage(named: "imagename"), style: .plain, target: self, action: Selector("action"))
self.navigationItem.rightBarButtonItem = button1
// Call in ViewDidLoad
displayOptions(sourceButton: button1)
func displayOptions(sourceButton: UIBarButtonItem) {
let actionSheet = UIAlertController(title: "title", message: "message", preferredStyle: .actionSheet)
if let popoverController = actionSheet.popoverPresentationController {
let rect = CGRect(x: 0, y: 0, width: 0, height: 0)
popoverController.barButtonItem = sourceButton
popoverController.sourceRect = rect
popoverController.sourceView = self.view
popoverController.permittedArrowDirections = .up
}
self.present(actionSheet, animated: true, completion: nil)
}

Do I need to add constraints to my UIToolBar if I'm adding it as a subview to my UIPickerView?

I'm trying to add a simple "Done" button to my UIPickerView programmatically, without using storyboards.
When I try to add toolBar as a subview of the UIPickerView, the toolbar doesn't even show up and I get some constraint related errors.
Any idea on how I can add the button to the PickerView?
Here is a snippet of my code:
var timerImage = UIButton()
var timer = Timer()
var timerDisplayed = 0
let image1 = UIImage(named: "stopwatch")
let timePicker = UIPickerView()
let timeSelect : [String] = ["300","240","180","120","90","60","45","30","15"]
let toolBar = UIToolbar()
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(ThirdViewController.dismissKeyboard))
func pickerViewConstraints(){
timePicker.anchor(top: nil, leading: view.safeAreaLayoutGuide.leadingAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.safeAreaLayoutGuide.trailingAnchor)
}
#objc func timeClock(){
toolBar.setItems([doneButton], animated: true)
toolBar.sizeToFit()
toolBar.isTranslucent = false
toolBar.isUserInteractionEnabled = true
toolBar.barStyle = .default
view.addSubview(timePicker)
timePicker.addSubview(toolBar)
pickerViewConstraints()
timePicker.backgroundColor = .white
DispatchQueue.main.async {
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.Action), userInfo: nil, repeats: true)
self.timerImage.setImage(nil, for: .normal)
}
}
No, you don't need to add constraints. It can be like :
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(title: buttonTitle, style: .done, target: self, action: #selector(doneButtonAction))
let cancelButton = UIBarButtonItem(title: cancelTitle, style: .plain, target: self, action: #selector(cancelButtonAction))
let barAccessory = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 44))
barAccessory.barStyle = .default
barAccessory.isTranslucent = true
barAccessory.barTintColor = .blue
barAccessory.setItems([cancelButton, space, doneButton], animated: false)
picker.addSubview(barAccessory)
self.view.bringSubviewToFront(picker)
Hope it helps...
You need to set a delegate of type UIPickerViewDelegate to your picker view. In this delegate, implement the delegate method pickerView:viewForRow:forComponent:reusingView
to provide your custom view for the picker item.
For the custom view you provide, you can design it however you want, including adding the button.
It looks like it's just a few minor issues here. Here's how I've done it prior:
override func viewDidLoad() {
super.viewDidLoad()
createToolbar()
}
// Toolbar for "done"
func createToolbar() {
let toolBar = UIToolbar()
toolBar.sizeToFit()
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(PageOneViewController.dismissKeyboard))
toolBar.setItems([doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
// Makes toolbar apply to text fields
educationText.inputAccessoryView = toolBar
politicalText.inputAccessoryView = toolBar
drinkingText.inputAccessoryView = toolBar
heightText.inputAccessoryView = toolBar
schoolInput.inputAccessoryView = toolBar
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
It seems like you haven't technically "called" the toolbar / done button, unless I'm just not seeing that part of the code. Also I've nested the "done" code inside of the function.
If you're using text fields then you should be able to follow to "educationText.inputAccessoryView = toolBar" format to apply all of the code above to each text field (education, politics, drinking, etc). I hope this helps! Good luck.

implement "done" button in UIToolBar to close UIDatePicker and ToolBar

I want to close my uidatepicker and my toolbar by pressing a "done"-button in my toolbar.
I am using a label instead of a textfield so the common solutions don't work.
I have tried:
#IBAction func ButtonPressed(_ sender: UIButton) {
print("hallo")
let picker = UIDatePicker()
picker.backgroundColor = .white
picker.datePickerMode = .date
//picker.sizeToFit()
var datumComponents = DateComponents()
datumComponents.year = 2019
datumComponents.month = 6
datumComponents.day = 1
let meinStartKalender = Calendar.current
let StartDatum = meinStartKalender.date(from: datumComponents)
picker.minimumDate = StartDatum
picker.maximumDate = Date()
let pickerSize1 = myView.bounds.width
picker.addTarget(self, action: #selector(dueDateChanged(sender:)), for: UIControl.Event.valueChanged)
picker.frame = CGRect(x:0.0, y: self.view.frame.height - 300 , width: pickerSize1, height: 200)
self.view.addSubview(picker)
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.backgroundColor = .red
toolBar.frame = CGRect(x:0.0, y: self.view.frame.height - 340 , width: pickerSize1, height: 40)
self.view.addSubview(toolBar)
what I've found already:
add:
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.donePressed(_:)))
toolBar.setItems([doneButton], animated: false)
but the error message says:
Value of type 'ViewController' has no member 'donePressed'
how can I change the action or how can I implement a button that works?
Add this in your code
#objc
func donePressed(_ sender: UIButton) {
}

swift playgrounds display uiview with nonstop looping

I create and display uiview in live view windows, when i create the button and add to the uiview , the program fail with nonstop looping which continuously load addbutton . Did somebody meet this problem and please tell me why :-)
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
var label1 : UILabel?
override func loadView() {
let view = UIView()
view.backgroundColor = .white
print("code run here ")
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
label1 = label
view.addSubview(label)
let k1:UIButton = addnewbutton() as! UIButton
//view.addSubview(k1)
self.view = view
}
#objc func buttonPressed(sender: UIButton!) {
var alertController = UIAlertController(title: "title", message: "message", preferredStyle: UIAlertControllerStyle.alert)
self.present(alertController, animated: true, completion: nil)
}
func addnewbutton() -> UIView{
var btn : UIButton
btn = UIButton()
btn.frame = CGRect(x:200,y:300,width:100,height:25)
btn.setTitle("clickme",for: UIControlState.normal)
//btn.titleLabel?.text = "clickme"
btn.backgroundColor = UIColor.black
btn.titleLabel?.textColor = UIColor.white
btn.titleColor(for: UIControlState.normal)
btn.addTarget(self, action: #selector(buttonPressed), for: UIControlEvents.touchUpInside)
view.addSubview(btn)
return btn
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
You add the button here
view.addSubview(btn)
inside addnewbutton
which recursively searches for the parent view of the VC and it's not yet setted inside loadView so control calls it again and the problem happens to infinite loop , so comment that line and uncomment this
view.addSubview(k1) // which is inside loadView
BTW make the return of addnewbutton to UIButton directly instead of a cast

Back Button Image - what is it called in Swift?

I am trying to use Swift's internal back button image.
I have asked this question before and got the technically correct answer that it inherits it from the previous View, BUT if I insert this code you can control the back button on the current View.
// Takeover Back Button
self.navigationItem.hidesBackButton = false
let newBackButton = UIBarButtonItem(title: "<", style: .Plain, target: self, action: "segueBack")
navigationItem.leftBarButtonItem = newBackButton
That gives me a <, "ABC" would give me ABC etc but how do I trigger Swift to put up it's internal Back Button image. The code below doesn't work but I would have thought is along the right lines.
let backImg: UIImage = UIImage(named: "BACK_BUTTON_DEFAULT_ICON")!
navigationItem.leftBarButtonItem!.setBackgroundImage(backImg, forState: .Normal, barMetrics: .Default)
Has anyone worked out how to do this?
Try to add custom view as back button like as
var backButton = UIButton(frame: CGRectMake(0, 0, 70.0, 70.0))
var backImage = UIImage(named: "backBtn")
backButton.setImage(backImage, forState: UIControlState.Normal)
backButton.titleEdgeInsets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 0.0)
backButton.setTitle("Back", forState: UIControlState.Normal)
backButton.addTarget(self, action: "buttonPressed", forControlEvents: UIControlEvents.TouchUpInside)
var backBarButton = UIBarButtonItem(customView: backButton)
var spacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
spacer.width = -15
self.navigationItem.leftBarButtonItems = [spacer,backBarButton]
It will look same as iOS back button
I struggled with this question for a while. Finally I got the back image with the following code:
let backImage = navigationController?.navigationBar.subviews[2].subviews[0].subviews[0].subviews[0] as! UIImageView).image
Before run the code above, make sure the back button is showing. Then you can save backImage to anywhere you want.
Here is the backImage I got.
Here is my solution:
override func viewDidLoad() {
...
let buttonBack = UIBarButtonItem(image: UIImage(named: "backButton"), style: .plain, target: self, action: #selector(buttonSavePressed(_:)))
self.navigationItem.leftBarButtonItem = buttonBack
let backButton = UIButton(frame: CGRect(x: 0, y: 0, width: 24.0, height: 24.0))
let backImage = UIImage(named: "backButton")
backButton.setImage(backImage, for: .normal)
backButton.setTitle("Back", for:.normal)
if #available(iOS 13.0, *) {
backButton.setTitleColor(.link, for: .normal)
} else {
backButton.setTitleColor(.blue, for: .normal)
}
backButton.addTarget(self, action:#selector(buttonSavePressed(_:)), for: .touchUpInside)
let backBarButton = UIBarButtonItem(customView: backButton)
let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
spacer.width = -15
self.navigationItem.leftBarButtonItems = [spacer,backBarButton]
}
#objc func buttonBackPressed(_ sender: Any) {
...
}
If you want to get the default back button image, the arrow is a class of type _UINavigationBarBackIndicatorView.
Here follows the hack,
UIImage *imgViewBack ;
for (UIView *view in self.navigationController.navigationBar.subviews) {
// The arrow is a class of type _UINavigationBarBackIndicatorView. This is not any of the private methods, so I think
// this is fine for the AppStore...
if ([NSStringFromClass([view class]) isEqualToString:#"_UINavigationBarBackIndicatorView"]) {
// Set the image from the Default BackBtn Imageview
UIImageView *imgView = (UIImageView *) view;
if(imgView){
imgViewBack = imgView.image ;
}
}
}
Try this to replace the back button image:
let back_image = UIImage(named: "btn_back")
self.navigationBar.backIndicatorImage = back_image
self.navigationBar.backIndicatorTransitionMaskImage = back_image
If you don't like to have the "Back" title you can add this too:
self.navigationBar.topItem?.title = ""