I have a UITextView on my view, and the keyboard can be dismissed by pushing a done-button on the keyboard's toolbar. This button is also performing a save on the text. The whole thing was working perfectly until the most recent version of my app. I can't figure out what happened. I was suspecting that the problem was setting backwards compatibility of my app to iOS version 11.0. But then i tried with higher development targets again, and the result is the same. I did not touch this view's code when building the recent updates, so I am not sure what causes the problem. Relevant parts of my code :
let textView: UITextView = {
let tw = UITextView()
tw.font = Font.kohinoorTeluguMedium_20
tw.returnKeyType = .default
tw.isScrollEnabled = true
tw.keyboardDismissMode = .interactive
tw.translatesAutoresizingMaskIntoConstraints = false
return tw
}()
fileprivate func setupKeyboardToolbar() {
let keyboardToolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
keyboardToolBar.translatesAutoresizingMaskIntoConstraints = false
keyboardToolBar.sizeToFit()
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleDoneButton))
keyboardToolBar.items = [doneButton]
self.textView.inputAccessoryView = keyboardToolBar
textView.delegate = self
// self.textView.autoresizingMask = .flexibleHeight
keyboardToolBar.layoutIfNeeded()
keyboardToolBar.isHidden = false
}
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboardToolbar()
tableView.register(DetailCell.self, forCellReuseIdentifier: cellId)
tableView.backgroundColor = AdaptiveColors.tableViewBackgroundColor
tableView.separatorColor = AdaptiveColors.tableViewBackgroundColor
setupDismissButton()
let editButton = UIBarButtonItem(title: LocalizedString.edit, style: .plain, target: self, action: #selector(handleEdit))
editButton.tintColor = .white
navigationItem.rightBarButtonItem = editButton
}
#objc func handleDoneButton() {
let context = CoreDataManager.shared.persistentContainer.viewContext
lesson?.notes = textView.text
do {
try context.save()
} catch let savErr {
print("Error saving context \(savErr)")
}
textView.resignFirstResponder()
}
func textViewDidChange(_ textView: UITextView) {
tableView.beginUpdates()
tableView.endUpdates()
}
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
return true
}
}
After reading relevant stackOverflow answers, I have tried calling layoutIfNeeded() and isHidden = false on keyboardToolbar, with no luck. Also gave a try to set self.textView.autoResizingMask = .flexibleHeight, with no luck. Also tried to set keyboardToolbar's delegate to self, and declaring self as UIToolbarDelegate.
Thanks anyone for helping!
Ok, I have fixed it, it was a stupid mistake I overlooked at some point.
I had some issues with auto layout, so I moved this textView object to my custom cell class, instead of the view controller. Then I was talking to that textView in cellForRow at indexPath method of my VC, but i forgot to setup the toolbar in the cell class. I still had the textView and toolbar setup in the view controller.
I have fixed this problem by moving the setup in the custom cell lass, then making a delegate pattern between the custom cell and the vc, to make note-saving possible.
Related
I am trying to have my UILabel's text automatically update in increments of 1 based on a looping Timer. The label is linked to a variable. I'm not sure if it matters but I am doing my UI programmatically using auto layout anchors.
I understand this is not working because the variable lives outside of ViewDidLoad(). I also tried setting up a subclass of a UILabel in a separate file but I could not figure out the proper way to do that. I was having trouble connecting the variable to the subclass and properly implementing didSet.
Here is the relevant code from my View Controller, any recommendations or alternative methods are appreciated.
import UIKit
class ViewController: UIViewController {
var numberOfBreaths = 0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let breathCounter = UILabel()
breathCounter.translatesAutoresizingMaskIntoConstraints = false
breathCounter.text = "\(numberOfBreaths)"
breathCounter.textAlignment = .center
breathCounter.center = self.view.center
// Irrelevant hidden label code redacted
let startStop = RoundButton()
startStop.translatesAutoresizingMaskIntoConstraints = false
startStop.backgroundColor = .white
startStop.setTitle("breathe", for: .normal)
startStop.setTitleColor(.darkGray , for: .normal)
startStop.layer.borderWidth = 2.5
startStop.layer.borderColor = CGColor(red: 225, green: 225, blue: 0, alpha: 1)
startStop.addTarget(self, action: #selector(self.breathCount), for: .touchUpInside)
view.addSubview(breathCounter)
view.addSubview(holdTimer)
view.addSubview(startStop)
// Anchor code redacted
}
#objc func breathCount(_ sender: RoundButton) {
print("Button Tapped")
createTimer()
}
func createTimer() {
_ = Timer.scheduledTimer(timeInterval: 3.5, target: self, selector: #selector(nextBreath), userInfo: nil, repeats: true)
}
#objc func nextBreath() {
numberOfBreaths += 1
breathCounter.text = "\(numberOfBreaths)" // Error: Cannot find 'breathCounter' in scope
print(numberOfBreaths) // Prints expected number to console
}
}
View for context
If you declare breathCounter as a property on your view controller (like you did for numberOfBreaths, you will have access to it from both the viewDidLoad and nextBreath functions. I'd also hold a reference to your Timer
class ViewController: UIViewController {
var numberOfBreaths = 0
let breathCounter = UILabel()
var timer : Timer?
And then inside viewDidLoad, remove the existing let breathCounter = UILabel() line.
And inside createTimer:
self.timer = Timer.scheduledTimer(timeInterval: 3.5, target: self, selector: #selector(nextBreath), userInfo: nil, repeats: true)
You error message: // Error: Cannot find 'breathCounter' in scope gives a good clue... it's all about scope.
You declare your UILabel within the ViewDidLoad() method and so that's where it lives; that's its scope. As soon as ViewDidLoad completes, *poof * UILabel disappears from memory.
What you need to do is move your let breathCounter = UILabel() outside of ViewDidLoad so it gets created along with your ViewController; then you will be able to reference it as long as your ViewController exists in memory.
first post so apologies if I mess something up. I have researched this for hours upon hours and read other posts here on stack exchange to no avail.
I have created a nib file that defines a custom view and have defined a custom class (UIView) to manage the outlets of the custom view. As you can see from the code below excerpted from my custom UIView class associated with the nib, I have a date picker as the input view for the custom class and a UIToolBar with two UIBarButtonItems. Both of these appear as desired through a tap gesture recognizer... however the problem is the UIBarButtonItems do not call the action when tapped. Placing a breakpoint in the action function reveals that the code is never run. I feel that something with the view lifecycle is preventing a reference from being made, but I am new to Swift so some help here would be appreciated. I don't think it is selector syntax as the tap gesture recognizer works as desired. I've tried messing with button click handling access levels. I've tried doing input view setup when the view awakes from the nib as well, along with trying to put the code in different parts of the lifecycle.
If it matters for lifecycle's sake, this nib is a part of a table view cell. I call for this nib to be loaded when the table view cell awakes from it's nib.
Thanks!
#IBOutlet weak var timerStackView: UIStackView!{
didSet{
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(HandleTap(_:)))
timerStackView.addGestureRecognizer(tapGesture)
}
}
var datePicker: UIDatePicker {
let picker = UIDatePicker()
picker.backgroundColor = UIColor.black
picker.isOpaque = false
picker.setValue(UIColor.white, forKey: "textColor")
return picker
}
var datePickerAccessoryView: UIToolbar {
let accessoryView = UIToolbar()
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.done, target: self, action: #selector(handleDatePickerButtonClick(_:)))
doneButton.tintColor = UIColor.white
let cancelButton = UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.plain, target: self, action: #selector(handleDatePickerButtonClick(_:)))
cancelButton.tintColor = UIColor.white
accessoryView.setItems([cancelButton, doneButton], animated: true)
return accessoryView
}
override var inputView: UIView? {return datePicker}
override var inputAccessoryView: UIView? {return datePickerAccessoryView}
override var canBecomeFirstResponder: Bool {return true}
override var canResignFirstResponder: Bool {return true}
// MARK: - Private functions
#objc fileprivate func HandleTap(_ sender: UITapGestureRecognizer) -> Void {
if !self.isFirstResponder {
switch sender.state {
case .ended:
datePicker.date = Date()
self.becomeFirstResponder()
default:
break
}
}
}
#objc #IBAction internal func handleDatePickerButtonClick(_ sender: UIBarButtonItem) -> Void {
switch sender.title! {
case "Done":
// To be implemented
case "Cancel":
// To be implemented
default:
break
}
}
You are initialising the UIToolbar without a frame and that would make it not register any touch events because they would be out of the toolbar's bounds.
Replace let accessoryView = UIToolbar() with something like let accessoryView = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 44))
Or you can call accessoryView.sizeToFit() before return accessoryView
I suppose the tap gesture recognizer is interfering with native UIBarButtonItem click events. But why do you use a gesture recognizer for that?
You should better add an action to each particular UIBarButtonItem.
I’m trying to “be a good citizen” by keeping View code separate from the ViewController (see this) and I’m wondering if this could also apply to code for a UIAlertController.
However the Apple documentation says:
The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
Despite this I thought it might still be possible to subclass a UIAlertController - yes, I realise that UIAlertController is not a View - so I tried the simple test example below.
The code includes subclassed views with layout constraints. A UIButton is used to activate the alert (and eventually the target action will become the basis of a function that could be called from another class).
The code runs but as soon as the button is pressed Xcode goes into debug at the following line
self.view.present(alert, animated: true, completion: nil)
with the message
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Have I made a mistake ? or am I trying to do something impossible ?
ViewController.swift
import UIKit
class ViewController: UIViewController {
var launch: TestView {
return view as! TestView
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func loadView() {
let contentView = TestView(frame: .zero)
view = contentView
}
}
TestView.swift
import UIKit
class TestView: UIView {
var view:ViewController!
override init(frame: CGRect) {
super.init(frame: frame)
view.self = view
addBehavior()
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
func addBehavior() {
let spacer: UIView = UIView (frame: .zero)
spacer.backgroundColor = UIColor.white
let view: UIView = UIView (frame: .zero)
view.backgroundColor = UIColor.lightGray
view.alpha = 0.5
let button: UIButton = UIButton(frame: .zero)
button.backgroundColor = UIColor.blue
button.setTitleColor(UIColor.white, for: UIControlState.normal)
button.setTitle("test", for: .normal)
button.addTarget(self, action: #selector(showExcessNotesAlert), for: .touchUpInside)
let stackView = UIStackView(arrangedSubviews: [spacer, view, button])
stackView.axis = .vertical
stackView.spacing = 5
stackView.translatesAutoresizingMaskIntoConstraints = false
addSubview(stackView)
let views = ["stackView": stackView]
var layoutConstraints = [NSLayoutConstraint]()
layoutConstraints += NSLayoutConstraint.constraints(withVisualFormat: "|-[stackView]-|", options: [], metrics: nil, views: views)
layoutConstraints.append(spacer.heightAnchor.constraint(equalToConstant: 30))
layoutConstraints.append(view.heightAnchor.constraint(equalToConstant: 450))
layoutConstraints.append(button.heightAnchor.constraint(equalToConstant: 50))
NSLayoutConstraint.activate(layoutConstraints)
backgroundColor = .white
}
func showExcessNotesAlert(_ sender: UIButton) {
let alert = UIAlertController(title: "Alert", message: "Grow a brain", preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(defaultAction)
self.view.present(alert, animated: true, completion: nil)
}
}
There are several problems in here, but the likely cause of your crash is this line:
view.self = view
That's not valid code for several reasons, but the reason it's crashing is that you're trying to access a variable (view), which you have defined as an implicitly unwrapped optional (by putting the "!" at the end), yet you never initialize it before trying to access, so of course it's nil. The bare minimum initialization would be to replace view.self = view with self.view = UIViewController(). I don't fully understand what you want to do with that view variable, but making my suggested change will likely cause that crash to stop.
Ultimately, I don't see any code in your snippet to suggest you shouldn't just use the built-in UIAlertController from within your viewController. Again, it's a little hard to follow exactly what you're hoping to achieve.
How to add button above the keyboard like this one in Stack Exchange app? And when you long press the text in UITextView How to add "Select" and "Select All"?
The first question, you can set textField's inputAccessoryView to your custom view, this can customize the keyboard's header.
The result:
You can do it like below;
first, you should instance the view you want to add above the keyboard.
class ViewController : UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.inputAccessoryView = Bundle.main.loadNibNamed("CustomAccessoryView", owner: self, options: nil)?.first as! UIView?
In your CustomAccessoryView, you can set the action of the button:
import UIKit
class CustomAccessoryView: UIView {
#IBAction func clickLoveButton(_ sender: UIButton) {
print("Love button clicked")
}
}
I would recommend to create a toolbar for your UITextField's accessoryView property.
The idea is to add this toolbar once, before the textfield would show for the first time. Therefore, we assign the delegate to self, and override the textFieldShouldBeginEditing delegate call with our implementation to add the accessoryView.
Here is a simple example, how can u achieve it:
import UIKit
class ViewController: UIViewController {
// your `UITextfield` instance
// Don't forget to attach it from the IB or create it programmaticly
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Assign the delegate to self
textField.delegate = self
}
}
// MARK: Create extension to conform to UITextfieldDelegate
extension ViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
setupTextFieldsAccessoryView()
return true
}
func setupTextFieldsAccessoryView() {
guard textField.inputAccessoryView == nil else {
print("textfields accessory view already set up")
return
}
// Create toolBar
let toolBar: UIToolbar = UIToolbar(frame:CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 44))
toolBar.barStyle = UIBarStyle.black
toolBar.isTranslucent = false
// Add buttons as `UIBarButtonItem` to toolbar
// First add some space to the left hand side, so your button is not on the edge of the screen
let flexsibleSpace: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil) // flexible space to add left end side
// Create your first visible button
let doneButton: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(didPressDoneButton))
// Note, that we declared the `didPressDoneButton` to be called, when Done button has been pressed
toolBar.items = [flexsibleSpace, doneButton]
// Assing toolbar as inputAccessoryView
textField.inputAccessoryView = toolBar
}
func didPressDoneButton(button: UIButton) {
// Button has been pressed
// Process the containment of the textfield or whatever
// Hide keyboard
textField.resignFirstResponder()
}
}
This should be your output:
You'll have to use the inputAccessoryView of your textfield.
you can put the code snippet below in your viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 60))
button.backgroundColor = UIColor.blue
button.setTitle("NEXT", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.addTarget(self, action: #selector(self. yourButton), for: .touchUpInside)
numtextField.inputAccessoryView = button
}
#objc func nextButton()
{
print("do something")
}
Just copy and paste simple code for you accessory button embedded with keypad
func addKeyboardToolbar() {
let ViewForDoneButtonOnKeyboard = UIToolbar()
ViewForDoneButtonOnKeyboard.sizeToFit()
let button = UIButton.init(type: .custom)
button.setImage(UIImage.init(named: "login-logo"), for: UIControlState.normal)
button.addTarget(self, action:#selector(doneBtnfromKeyboardClicked), for:.touchUpInside)
button.frame = CGRect.init(x: 0, y: 0, width:UIScreen.main.bounds.width, height: 30) //CGRectMake(0, 0, 30, 30)
let barButton = UIBarButtonItem.init(customView: button)
ViewForDoneButtonOnKeyboard.items = [barButton]
postTextView.inputAccessoryView = ViewForDoneButtonOnKeyboard
}
func doneBtnfromKeyboardClicked (){
self.contentView.endEditing(true)
}
to add a toolbar with a done button which dismisses the keyboard above a UITextField you can write a UITextField extension with the following function:
public func addAccessoryView() {
let doneButton = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action: "resignFirstResponder")
let flexSpace: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let toolbar = UIToolbar()
toolbar.barStyle = UIBarStyle.Default
toolbar.translucent = true
toolbar.tintColor = Color.blue
toolbar.sizeToFit()
toolbar.setItems([flexSpace, doneButton], animated: false)
toolbar.userInteractionEnabled = true
self.inputAccessoryView = toolbar
}
you can then call the function in your textfield like this:
textfield.addAccessoryView()
I've created a navigationBar in my view which is suppose to hold 2 buttons left and right. The problem is whatever i do it wont show the buttons/views. How am i suppose to do this? here is my code so far.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.titleTextAttributes = [ NSFontAttributeName: UIFont(name: "Aerovias Brasil NF", size: 30)!, NSForegroundColorAttributeName: UIColor.whiteColor()]
navTitleView = UIView(frame: CGRectMake(0, 0, 100, 44))
titleLabel.text = "SHOPOP"
navTitleView?.addSubview(titleLabel)
self.navigationItem.titleView?.addSubview(navTitleView!)
navigationHeight = UIApplication.sharedApplication().statusBarFrame.height+self.navigationController!.navigationBar.bounds.height
searchBar = UISearchBar(frame: CGRectZero)
searchBarWrapper = UINavigationBar(frame: CGRectMake(0, 100, self.navigationController!.navigationBar.bounds.size.width, self.navigationHeight!))
var buttonSearchBar:UIBarButtonItem = UIBarButtonItem(customView: searchBar!)
var cancelButton:UIBarButtonItem = UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.Plain, target: nil, action: nil)
searchBarWrapper?.topItem?.leftBarButtonItem = buttonSearchBar
searchBarWrapper?.topItem?.rightBarButtonItem = cancelButton
self.navigationController?.view.addSubview(searchBarWrapper!)
}
#IBAction func showSearchBar(sender: UIBarButtonItem) {
searchBar?.becomeFirstResponder()
UIView.animateWithDuration(0.25, animations: {
// Optional chaining may return nil
self.searchBarWrapper!.center = CGPointMake(self.navigationController!.view.center.x, self.navigationHeight!/2)
// return
}, completion: {
finished in
println("Basket doors opened!")
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func hideSearchBar(sender: UIBarButtonItem) {
UIView.animateWithDuration(0.25, animations: {
// Optional chaining may return nil
self.searchBarWrapper!.center = CGPointMake(self.navigationController!.view.center.x, -self.navigationHeight!/2)
self.searchBar?.resignFirstResponder()
// return
}, completion: {
finished in
println("Basket doors opened!")
})
Based on the changes to your question you should look into the UISearchController class. A cursory glance at the documentation leads me to believe that after you configure it properly you can push it onto the view controller stack and it will behave like you want.
Old answer:
While I'm not sure about wrapping a UISearchBar inside of a UIBarButtonItem I can say with certainty that this is the wrong way to go about adding a UINavigationBar to a UINavigationController:
self.navigationController?.view.addSubview(searchBarWrapper!)
The right way to go about providing items for the navigation bar is to override UIViewController's navigationItem property and return a customized UINavigationItem instance. This way when you push and pop view controllers off of the UINavigationController's stack the navigation bar will be updated automatically.
This is an example from a UIViewController subclass in one of my projects.
private var customNavigationItem = UINavigationItem(title:nil);
override var navigationItem:UINavigationItem
{
get
{
customNavigationItem.title = self.title;
customNavigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem:.Cancel,
target:self, action:"dismissForm:");
customNavigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem:.Save,
target:self, action:"saveForm:");
return customNavigationItem;
}
}
Doing this will ensure that all of the "free" functionality that UINavigationController provides works as intended. Messing with UINavigationController's view is generally a bad idea.