I am trying to update a button's constraints when the keyboard is shown and hidden by adding/subtracting the keyboard's height from the constraint's constant.
I had this working previously, but after some re-factoring, it's stopped working. Previously, keyboardWillShow: and keyboardWillHide: were implemented exactly as shown below. I've since tried to use setNeedsUpdateConstraints and setNeedsLayout to try to force a refresh, to no avail.
When doing some simple print() debugging, buttonHorizontalConstraint.constant does get updated, but the changes just aren't reflected visually.
RegistrationNameView.swift
class RegistrationNameView: UIView {
let questionLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFontOfSize(21.0)
label.text = "Hey, what's your name?"
label.textAlignment = .Center
label.textColor = UIColor.lightGrayColor()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let nameField: UITextField = {
let field = UITextField()
field.autocorrectionType = .No
field.font = UIFont.boldSystemFontOfSize(28.0)
field.placeholder = "Full name"
field.returnKeyType = .Next
field.textAlignment = .Center
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
let nextButton: UIButton = {
let button = UIButton()
button.setTitle("Continue", forState: .Normal)
button.setTitleColor(UIColor.whiteColor(), forState: .Normal)
button.titleLabel?.font = UIFont.boldSystemFontOfSize(17.0)
button.backgroundColor = UIColor(red: 0.263, green: 0.910, blue: 0.847, alpha: 1)
button.layer.cornerRadius = Global.UISizes.CornerRadius
button.translatesAutoresizingMaskIntoConstraints = false
button.contentEdgeInsets = UIEdgeInsetsMake(16.0, 0, 16.0, 0)
return button
}()
var buttonHorizontalConstraint = NSLayoutConstraint()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.whiteColor()
// Add subviews
self.addSubview(questionLabel)
self.addSubview(nameField)
self.addSubview(nextButton)
nameField.becomeFirstResponder()
// Constraint helpers
let spacer = Global.UISizes.SpacingUnit
let margins = self.layoutMarginsGuide
let layoutConstraints: [NSLayoutConstraint] = {
var constraints = [NSLayoutConstraint]()
// Title Label Constraints
constraints.append(
questionLabel.bottomAnchor.constraintEqualToAnchor(nameField.topAnchor, constant: -(spacer * 2)))
constraints.append(questionLabel.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor))
constraints.append(questionLabel.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor))
// Description Label Constraints
constraints.append(nameField.topAnchor.constraintEqualToAnchor(margins.centerYAnchor, constant: spacer * -12))
constraints.append(nameField.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor))
constraints.append(nameField.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor))
// Sign Up Button Constraints
self.buttonHorizontalConstraint = nextButton.bottomAnchor.constraintEqualToAnchor(margins.bottomAnchor, constant: -(spacer * 2))
constraints.append(self.buttonHorizontalConstraint)
constraints.append(nextButton.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor))
constraints.append(nextButton.trailingAnchor.constraintEqualToAnchor(margins.trailingAnchor))
return constraints
}()
NSLayoutConstraint.activateConstraints(layoutConstraints)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo! as NSDictionary).objectForKey(UIKeyboardFrameBeginUserInfoKey)?.CGRectValue.size {
self.buttonHorizontalConstraint.constant -= keyboardSize.height
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo! as NSDictionary).objectForKey(UIKeyboardFrameBeginUserInfoKey)?.CGRectValue.size {
self.buttonHorizontalConstraint.constant += keyboardSize.height
}
}
}
RegistrationNameViewController.swift
class RegistrationNameViewController: UIViewController {
var nameView: RegistrationNameView! { return self.view as! RegistrationNameView }
override func viewDidLoad() {
super.viewDidLoad()
let nameView = RegistrationNameView(frame: CGRectZero)
nameView.nextButton.addTarget(self, action: "nextStep:", forControlEvents: .TouchUpInside)
self.view = nameView
}
func nextStep(sender: AnyObject) {
print("going to the next step \(sender)")
let credentialsViewController = RegistrationCredentialsViewController()
self.navigationController?.pushViewController(credentialsViewController, animated: true)
}
}
RegistrationNavigationController.swift
class RegistrationNavigationController: UINavigationController, UINavigationControllerDelegate {
var nameViewController: RegistrationNameViewController = RegistrationNameViewController()
var credViewController: RegistrationCredentialsViewController = RegistrationCredentialsViewController()
override init(rootViewController: UIViewController) {
super.init(rootViewController: rootViewController)
self.delegate = self
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
let type = String(viewController.dynamicType)
switch type {
case "RegistrationNameViewController":
// Add keyboard notifications to RegistrationNameViewController
NSNotificationCenter.defaultCenter().addObserver(nameViewController.view,
selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(nameViewController.view,
selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
case "RegistrationCredentialsViewController":
// Remove keyboard notifications to RegistrationNameViewController before
// registering for new notifications
NSNotificationCenter.defaultCenter().removeObserver(nameViewController.view)
// Add keyboard notifications to RegistrationCredentialsViewController
NSNotificationCenter.defaultCenter().addObserver(credViewController.view,
selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(credViewController.view,
selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
default:
print("Default")
}
}
}
Thank you for the help!
I solved this on my own after some digging.
I believe the problem is that the NSNotificationCenter observer registrations were happening on a background thread, which caused the methods that actually impacted UI to not actually change the UI.
Instead of registering observers in navigationController:willShowViewController:animated:, I register them in viewWillAppear and unregister them in viewWillDisappear. This happens in RegistrationNameViewController instead of RegistrationNavigationController.
RegistrationNameViewController.swift
override func viewWillAppear(animated:
NSNotificationCenter.defaultCenter().addObserver(self.view,
selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self.view,
selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
(self.view as! RegistrationNameView).nameField.becomeFirstResponder()
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self.view)
}
This makes navigationController:willShowViewController:animated: unnecessary, and it can be removed from RegistrationNavigationController.swift
Related
There is a view with two UITextFields, and the view moves according to the height of the keyboard. However, moving the View is not ideal if at least one textfield has its isSecureTextEntry property set to true. It is noticeable when moving between textfields. View moves down once and returns instantly. This is because the height of the keyboard changes momentarily. I think it's a matter of showing / hiding QuickTypeBar. So how to eliminate that effect and control the view movement?
Below is all the code.
It may not look right in the simulator. Because the QuickType bar is not displayed.
This is happening on the iPhone X iOS13.3.1.
Thanks.
import UIKit
class ViewController: UIViewController {
public var boardView: UIView!
public var usernameTextField: UITextField!
public var passwordTextField: UITextField!
public var boardViewBottomAnchorConstraint: NSLayoutConstraint!
public var boardViewBottomAnchorConstraintConstant: CGFloat = -220
public var offset: CGFloat = 16.0
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
var constraints = [NSLayoutConstraint]()
let boardView = UIView()
boardView.backgroundColor = .brown
boardView.layer.cornerRadius = 16.0
boardView.translatesAutoresizingMaskIntoConstraints = false
boardViewBottomAnchorConstraint = boardView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: boardViewBottomAnchorConstraintConstant)
constraints.append(boardView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16))
constraints.append(boardViewBottomAnchorConstraint)
constraints.append(boardView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16))
view.addSubview(boardView)
self.boardView = boardView
let usernameTextField = UITextField()
usernameTextField.backgroundColor = .white
usernameTextField.delegate = self
usernameTextField.returnKeyType = .next
usernameTextField.textContentType = .username
usernameTextField.placeholder = "USERNAME TEXTFIELD"
usernameTextField.translatesAutoresizingMaskIntoConstraints = false
constraints.append(usernameTextField.heightAnchor.constraint(equalToConstant: 56))
constraints.append(usernameTextField.topAnchor.constraint(equalTo: boardView.topAnchor, constant: 100))
constraints.append(usernameTextField.leftAnchor.constraint(equalTo: boardView.leftAnchor, constant: 24))
constraints.append(usernameTextField.rightAnchor.constraint(equalTo: boardView.rightAnchor, constant: -24))
boardView.addSubview(usernameTextField)
self.usernameTextField = usernameTextField
let passwordTextField = UITextField()
passwordTextField.backgroundColor = .white
passwordTextField.delegate = self
passwordTextField.returnKeyType = .go
passwordTextField.textContentType = .password
passwordTextField.placeholder = "PASSWORD TEXTFIELD"
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
constraints.append(passwordTextField.heightAnchor.constraint(equalToConstant: 56))
constraints.append(passwordTextField.topAnchor.constraint(equalTo: usernameTextField.bottomAnchor, constant: 50))
constraints.append(passwordTextField.leftAnchor.constraint(equalTo: boardView.leftAnchor, constant: 24))
constraints.append(passwordTextField.bottomAnchor.constraint(equalTo: boardView.bottomAnchor, constant: -100))
constraints.append(passwordTextField.rightAnchor.constraint(equalTo: boardView.rightAnchor, constant: -24))
boardView.addSubview(passwordTextField)
self.passwordTextField = passwordTextField
NSLayoutConstraint.activate(constraints)
let tap = UITapGestureRecognizer(target: view, action: #selector(UIView.endEditing(_:)))
view.addGestureRecognizer(tap)
// false is the ideal move. If true, the view moves violently.
passwordTextField.isSecureTextEntry = true
}
}
extension ViewController {
#objc
public func keyboardWillShow(_ notification: Notification) { print("WILL SHOW")
updateConstraints(notification)
}
#objc
public func keyboardWillHide(_ notification: Notification) { print("WILL HIDE")
boardViewBottomAnchorConstraint.constant = boardViewBottomAnchorConstraintConstant
view.layoutIfNeeded()
}
#objc
public func keyboardWillChangeFrame(_ notification: Notification) { print("WILL CHANGE FRAME")
updateConstraints(notification)
}
private func updateConstraints(_ notification: Notification) {
guard
let toFrame = notification.userInfo?[UIApplication.keyboardFrameEndUserInfoKey] as? CGRect,
let fromFrame = notification.userInfo?[UIApplication.keyboardFrameBeginUserInfoKey] as? CGRect else { return }
print(fromFrame); print(toFrame);
let constant = -toFrame.size.height - offset
boardViewBottomAnchorConstraint.constant = constant
view.layoutIfNeeded()
}
}
extension ViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == usernameTextField {
passwordTextField.becomeFirstResponder()
} else {
passwordTextField.resignFirstResponder()
}
return true
}
}
try using github.com/hackiftekhar/IQKeyboardManager from cocoapods. inside documentation you have to add few lines in appdelegate class and you are good to go.
IQKeyboardmanager is avaiable both through cocoapods and IOS swift dependancy manager.
if UIApplication.shared.isKeyboardPresented {
print("Keyboard presented")
} else {
print("Keyboard is not presented")
}
Use this code in combination with your code. Determine when the keyboad is active and do your actions accordingly
edit ----
YOu need to ctrl + drag from each of your text fields into your code and create a #IBAction
#IBAction func textField1() {
//when a user taps on text field 1. this code block is triggered.
if UIApplication.shared.isKeyboardPresented {
print("Keyboard presented")
//if the keyboard is already presented do whatever other action is required
//inside here the keyboard is already active
} else {
print("Keyboard is not presented")
//if the keyboard is not currently active do any required action here
//Inside here the keyboards will become active
}
}
For all your other textfields and all other views you need to do the same thing and customize your code based on the actions
EDI ----
Below is how you can achieve the same functionality without using the interface builder
let textField = UITextField()
self.textField.addTarget(self, action: #selector(targetFunc), for: .touchUpInside)
#objc func targetFunc() {
// same actions as above in here
}
Problem
I have an application that has a user registration view. It has many UITextField, and many of these have a picker with a toolbar embedded to close the picker i.e:
myTextField.inputView = myPicker
myTextField.inputAccessoryView = myToolbar
Essentially I want to reuse these text fields in different parts of my application, so I thought of subclassing UITextField, something like PickerUITextField.
Attempt
I've tried something like this:
class PickerUITextField: UITextField {
let picker = UIPickerView()
let toolbar = UIToolbar()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
withToolbar()
self.inputView = self.picker
self.inputAccessoryView = self.toolbar
}
private func withToolbar() {
toolbar.barStyle = UIBarStyle.default
toolbar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done,
target: self, action: #selector(removeToolBar))
toolbar.setItems([space, doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
toolbar.sizeToFit()
}
#objc func removeToolBar() {
self.resignFirstResponder()
}
}
Question
However, how can I detect in the view controller that the user has pressed the "Done" button of my PickerUITextField? In other words:
class UserRegistrationViewController: UIViewController {
#IBOutlet weak var country: PickerUITextField!
// I want this to be triggered whenever the country picker closes
func didSelectCountry() {
print("User selected \(country.text!)")
}
}
Thank you for the help.
You can create a closure in PickerUITextField to perform done button action.
class PickerUITextField: UITextField {
let picker = UIPickerView()
let toolbar = UIToolbar()
var doneBtnAction:(() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
withToolbar()
self.inputView = self.picker
self.inputAccessoryView = self.toolbar
}
private func withToolbar() {
toolbar.barStyle = UIBarStyle.default
toolbar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done,
target: self, action: #selector(removeToolBar))
toolbar.setItems([space, doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
toolbar.sizeToFit()
}
#objc func removeToolBar() {
doneBtnAction?()
self.resignFirstResponder()
}
}
And in your view controller, you can assign a closure. It will be called when you tap the done button.
class UserRegistrationViewController: UIViewController {
#IBOutlet weak var country: PickerUITextField!
override func viewDidLoad() {
super.viewDidLoad()
country.doneBtnAction = { [weak self] in
print("User selected \(self?.country.text!)")
}
}
}
You can use a protocol/delegate:
protocol PickerUITextFieldDelegate: class {
func didSelectCountry()
}
class PickerUITextField: UITextField {
// UITextField already have a 'delegate' we need a different name
weak var pickerDelegate: PickerUITextFieldDelegate?
let picker = UIPickerView()
let toolbar = UIToolbar()
#objc func removeToolBar() {
self.resignFirstResponder()
self.pickerDelegate?.didSelectCountry()
}
}
// We need to implement the PickerUITextFieldDelegate delegate here:
class UserRegistrationViewController: UIViewController, PickerUITextFieldDelegate {
#IBOutlet weak var country: PickerUITextField!
// Don't forget to set the delegate!
override func viewDidLoad() {
super.viewDidLoad()
self.country.pickerDelegate = self
}
// This will now be triggered by the delegate
func didSelectCountry() {
print("User selected \(country.text!)")
}
}
I have created a subclass of UILabel, based on an example from here: UILabel doesn't show inputView. I am trying to create an instance of the label inside a class that subclasses UITableViewCell. The issue I am having is that to create an instance of DatePickerLabel it requires an NSCoder.
let dp = DatePickerLabel(coder: NSCoder)
I have this in my class that subclasses UITableViewCell but it doesn't seem to ever be triggered, leading to a null pointer when I run it (I tried using a variable and then assigning it inside this code):
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print("triggered")
}
Any help is greatly appreciated, DatePickerLabel shown below!
class DatePickerLabel: UILabel {
private let _inputView: UIView? = {
let picker = UIDatePicker()
return picker
}()
private let _inputAccessoryToolbar: UIToolbar = {
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.sizeToFit()
return toolBar
}()
override var inputView: UIView? {
return _inputView
}
override var inputAccessoryView: UIView? {
return _inputAccessoryToolbar
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
_inputAccessoryToolbar.setItems([ spaceButton, doneButton], animated: false)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(launchPicker))
self.addGestureRecognizer(tapRecognizer)
}
override var canBecomeFirstResponder: Bool {
return true
}
#objc private func launchPicker() {
becomeFirstResponder()
}
#objc private func doneClick() {
resignFirstResponder()
}
}
Cheers!
In the question you linked to, the initialisation function was initWithCoder because that it used to load views from a storyboard or nib. You are not doing that.
So, change your init to a “plain” one, like:
init() {
super.init()
...
}
So I'm trying to change the likes label that's in the collection view cell class from the collection view controller. I tried to do it with protocol and delegates and notification center, but my app crashes with lldb when I try to configure the likes label from my View Controller.
Here's the relevant code:
class HomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout, HomePostCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(refreshLikeButtonLabel(for:)), name: NSNotification.Name(rawValue: "refresh"), object: nil)
}
#objc func refreshLikeButtonLabel(for cell: HomePostCell) {
cell.likesLabel.text = "4"
print("is it working?")
}
}
Cell Class:
protocol HomePostCellDelegate {
func refreshLikeButtonLabel(for cell: HomePostCell)
}
class HomePostCell: UICollectionViewCell {
lazy var likesLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "AvenirNext-Regular", size: 20)
label.textColor = UIColor.black
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "refresh"), object: nil)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here's what the console says:
2017-11-09 07:06:09.969144-0500 Vlogr[1200:24674] [Fabric] unable to complete application configure: Error Domain=FABNetworkError Code=-5 "(null)" UserInfo={status_code=422, type=2, request_id=84d6b91378f57956e1ae930e41915b46, content_type=text/html; charset=utf-8}
(lldb)
Let me know if you have any further questions, Thank you!
Define the function in view controller like this:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(reload(_:)), name: NSNotification.Name(rawValue: "refresh"), object: nil)
}
#objc func reload(_ notification: Notification) {
if let likesLabel = notification.userInfo?["likesLabelInfo"] as? UILabel {
likesLabel.text = "4"
}
}
Define the label in the collection view cell here:
lazy var likesLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "AvenirNext-Regular", size: 20)
label.textColor = UIColor.black
let userInfo = ["likesLabelInfo": label]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "refresh"), object: nil, userInfo: userInfo)
return label
}()
I'm using the SlackTextViewController.
Before It works fine but now I got a problem.
When I tapped inputView of SlackTextViewController then Keyboard will be displayed.
But InputView is not following up keyboard. So I cant see InputView after keyboard showing up.
Here is snippet code.
class CommentVC: SLKTextViewController {
override var tableView: UITableView {
get {
return super.tableView!
}
}
override class func tableViewStyle(for decoder: NSCoder) -> UITableViewStyle {
return .plain
}
override func viewDidLoad() {
super.viewDidLoad()
//Do any additional setup after loading the view, typically from a nib.
let cellNib = UINib(nibName: "CommentTableCell", bundle: Bundle.main)
tableView.register(cellNib, forCellReuseIdentifier: "CommentTableCell")
//Set Title at the top
self.navigationController?.isNavigationBarHidden = false
self.navigationItem.title = "Comments"
self.navigationItem.hidesBackButton = true
let backButton = UIBarButtonItem(image: UIImage(named: "backBtn"), style: UIBarButtonItemStyle.plain, target: self, action: #selector(CommentVC.back(_:)))
self.navigationItem.leftBarButtonItem = backButton
self.navigationItem.leftBarButtonItem?.setBackgroundVerticalPositionAdjustment(-8, for: UIBarMetrics.default)
// SLKTVC's configuration
self.bounces = true
self.shakeToClearEnabled = true
self.isKeyboardPanningEnabled = true
self.shouldScrollToBottomAfterKeyboardShows = false
self.isInverted = false
}
override func didChangeKeyboardStatus(_ status: SLKKeyboardStatus) {
switch status {
case .willShow:
print("Will Show")
case .didShow:
print("Did Show")
case .willHide:
print("Will Hide")
case .didHide:
print("Did Hide")
}
}
}
My problem is didKeyboardChangeStatus is not called.
It does not working in iOS 10 and iOS 11.