iOS 9 UIInputViewController dismissKeyboard - ios

I have a custom UIInputViewController, let's call it MyInputViewController. I set the a text field's input view to the view of a singleton instance of my input controller. Inserting, deleting and moving the cursor works fine however when I call self.dismissKeyboard() in the input view controller (method is called, print() writes to the console), it doesn't do anything.
What could be wrong? It got it to work a few days ago however I couldn't remember what might be the error.
class MyInputController: UIInputViewController {
// SINGLETON:
static let keyboard: MyInputController = MyInputController()
class func setSharedKeyboardForTextField(textField: UITextField) {
textField.inputAccessoryView = nil
textField.inputView = keyboard.view
}
// MARK: initializers
private init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func loadView() {
super.loadView()
self.view.translatesAutoresizingMaskIntoConstraints = false=
}
override func viewDidLoad() {
//set up buttons
}
// MARK: helper functions
func inputText() -> String {
return (self.textDocumentProxy.documentContextBeforeInput ?? "") + (self.textDocumentProxy.documentContextAfterInput ?? "")
}
// MARK: animations
private func grow(sender: MathematicalKeyboardKey) {
sender.superview?.bringSubviewToFront(sender)
UIView.animateWithDuration(0.1) { () -> Void in
sender.transform = scaleTransform
}
}
private func shrink(sender: MathematicalKeyboardKey) {
sender.superview?.sendSubviewToBack(sender)
UIView.animateWithDuration(0.1) { () -> Void in
sender.transform = CGAffineTransformIdentity
}
}
// MARK: actions
func keyTouched(sender: MathematicalKeyboardKey) {
// works
grow(sender)
}
func keyExit(sender: MathematicalKeyboardKey) {
// works
shrink(sender)
}
func resign(sender: MathematicalKeyboardKey) {
// called but doesn't work
dismissKeyboard()
shrink(sender)
}
func keyPressed(sender: MathematicalKeyboardKey) {
// works
self.textDocumentProxy.insertText(sender.insertion)
shrink(sender)
}
func remove(sender: MathematicalKeyboardKey) {
// works
self.textDocumentProxy.deleteBackward()
shrink(sender)
}
func clear(sender: MathematicalKeyboardKey) {
// works
while self.textDocumentProxy.hasText() {
self.textDocumentProxy.deleteBackward()
}
shrink(sender)
}
func moveLeft(sender: MathematicalKeyboardKey) {
// works
self.textDocumentProxy.adjustTextPositionByCharacterOffset(-1)
shrink(sender)
}
func moveRight(sender: MathematicalKeyboardKey) {
// works
self.textDocumentProxy.adjustTextPositionByCharacterOffset(1)
shrink(sender)
}
}

You can use ;
resignFirstResponder()
method instead of
dismissKeyboard()
method.
#IBAction func hideKeyboard(sender: UIButton) {
resignFirstResponder()
}

Related

Delegate nil after setting it

I am using the delegate method but for some odd reason my delegate variable seems to be nil when I want to call the delegate method. I can't for the life of me figure out what I'm doing wrong
protocol ProfileProtocol {
func buttonTapped()
}
class ProfileView: UIView {
var delegate: ProfileProtocol?
#IBOutlet weak var button: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func awakeFromNib() {
configure()
}
func setup() {
...
}
#IBAction func buttonTapped(_ sender: UIButton) {
// delegate nil
delegate?.buttonTapped()
}
}
ProfileViewController (yes it conforms to ProfileProtocol):
override func viewDidLoad() {
swipeableView.nextView = {
createCardView()
}
}
func createCardView() -> UIView {
let cardView = ProfileView(frame: swipeableView.bounds)
cardView.delegate = self
let contentView = Bundle.main.loadNibNamed("ProfileCardView", owner: self, options: nil)?.first! as! UIView
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.backgroundColor = cardView.backgroundColor
cardView.addSubview(contentView)
activeCardView = cardView
return cardView
}
func buttonTapped() {
self.performSegue(withIdentifier: "profileToEmojiCollection", sender: self)
}
Whenever I tap the button in my ProfileView, my ProfileViewController should perform a segue, however the delegate method isn't even being called because delegate is nil when I tap the button
I like to keep my custom views modular, and do things programmatically, it avoids the use of a Xib.
You should keep your view's responsibilities and subviews to the view itself. Ultimately the View receiving the the action(s) should be responsible for calling the delegate's methods. Also nextView is a closure that returns a UIView: (() -> UIView?)? not a UIView, a call to a function in a closure is not an explicit return you should return the view: let view = createCardView() return view.
ProfileView.swift
import UIKit
protocol ProfileProtocol {
func buttonTapped()
}
class ProfileView: UIView {
var delegate: ProfileProtocol?
lazy var button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
button.setTitle("Profile Button", for: .normal)
button.backgroundColor = UIColor.black
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
}
#objc func buttonTapped(_ sender: UIButton) {
// Check for a nil delegate, we dont want to crash if one is not set
if delegate != nil {
delegate!.buttonTapped()
} else {
print("Please set ProfileView's Delegate")
}
}
func setup() {
//setup subviews
self.addSubview(button)
button.widthAnchor.constraint(equalToConstant: 150).isActive = true
button.heightAnchor.constraint(equalToConstant: 50).isActive = true
button.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}
}
You can create ProfileView's like any other UIView, but remember to set the Delegate of each of them after creation:
swipeableView.nextView = {
let view = createProfileView() //set properties during creation?
view.delegate = self
//set properties after creation?
//view.backgroundColor = UIColor.red
return view
}
ViewController.swift
import UIKit
class ViewController: UIViewController, ProfileProtocol {
lazy var profileView: ProfileView = {
let view = ProfileView()
view.backgroundColor = UIColor.lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
profileView.delegate = self
setup()
}
func buttonTapped() {
print("Do Something")
}
func setup() {
self.view.addSubview(profileView)
profileView.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
profileView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.7).isActive = true
profileView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
profileView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

UISearchBar resignFirstResponder not working

UIViewController needs to hide keyboard inside viewWillDisappear or viewDidDisappear methods. UIViewController stays in memory after disappearing and can be presented again. On first appearance UISearchBar is not firstResponder and keyboard is hidden. But if I pop UIViewController with keyboard shown and then push it again - keyboard is not hidden, however I call:
override func viewDidLoad() {
super.viewDidLoad()
instrumentsTableView.register(UINib(nibName: kDealsFilterInstrumentTableViewCellNib, bundle: nil), forCellReuseIdentifier: kDealsFilterInstrumentTableViewCellReusableId)
instrumentsTableView.dataSource = self
instrumentsTableView.delegate = self
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if presenter.numberOfInstruments != 0 {
instrumentsTableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
}
KeyboardManager.shared.unsubscribe()
instrumentsSearchBar.text = ""
presenter.findInstruments(with: "") //just sets settings to default/ reloads data
instrumentsSearchBar.endEditing(true)
instrumentsSearchBar.resignFirstResponder()
view.endEditing(true)
view.resignFirstResponder()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
KeyboardManager.shared.subscribe(self)
}
KeyboardManager - sends notification if keyboard's state has changed, if relevant:
final class KeyboardManager {
private init() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: Notification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: Notification.Name.UIKeyboardWillHide, object: nil)
}
static let shared = KeyboardManager()
#objc private func keyboardWillShow(_ notification: Notification) {
if let keyboardSize = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let height = keyboardSize.cgRectValue.height
keyboardHeight = height
keyboardState = .shown
}
}
#objc private func keyboardWillHide(_ notification: Notification) {
keyboardHeight = 0
keyboardState = .hidden
}
private weak var subscriber: KeyboardManagerDelegate?
func subscribe(_ delegate: KeyboardManagerDelegate) {
subscriber = delegate
}
func unsubscribe() {
subscriber = nil
}
private var keyboardHeight: CGFloat = 0
private var keyboardState: KeyboardState = .hidden {
didSet {
if keyboardState != oldValue {
subscriber?.keyboardDidChange(state: keyboardState, height: keyboardHeight)
}
}
}
}
enum KeyboardState {
case shown
case hidden
}
protocol KeyboardManagerDelegate: class {
func keyboardDidChange(state: KeyboardState, height: CGFloat)
}
I've tried to use this code inside viewWillAppear and viewWillDisappear - but UISearchBar is still firstResponder. If I pop with keyboard being hidden - it stays hidden. What might be the problem?
Screencast:
Sample project with the same issue on bitbucket
For keyboard issue this will work fine,
self.view.endEditing(true)
Write this in viewWillDisappear or viewDidDisappear
I've tried your code: Sample project with the same issue on bitbucket and its working as expected and fine.
Here is that code.
class ViewController: UIViewController {
#IBAction func btnShowSearch(button: UIButton) {
if let search = self.storyboard?.instantiateViewController(withIdentifier: "SeachBarViewController") {
self.navigationController?.pushViewController(search, animated: true)
}
}
}
// SeachBarViewController
class SeachBarViewController: UIViewController {
#IBOutlet var searchBar: UISearchBar!
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
attemptToHidKeyboard()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
attemptToHidKeyboard()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
attemptToHidKeyboard()
}
override func didMove(toParentViewController parent: UIViewController?) {
if parent == nil {
attemptToHidKeyboard()
}
}
override func willMove(toParentViewController parent: UIViewController?) {
if parent == nil {
attemptToHidKeyboard()
}
}
private func attemptToHidKeyboard() {
self.searchBar.resignFirstResponder()
self.searchBar.endEditing(true)
self.view.resignFirstResponder()
self.view.endEditing(true)
}
}
Here is result:

SubView(nib) wont remove after calling removeFromSuperView()

I have an overlay view to segregate content, I'm checking for authentication in viewWillAppear() and I have a Notification subscribed to my Auth method. If I authenticate before any of my other views appear the overlay does not show up, however it does on the first view and will not go away even after calling removeFromSuperView().
import UIKit
import FirebaseAuth
class ProtectedViewController: UIViewController, ForceSignInBannerDelegate,
SignUpViewControllerDelegate, LoginViewControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
NotificationCenter.default.addObserver(self, selector: #selector(checkAuthentication), name: .myNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.checkAuthentication()
}
func checkAuthentication() {
let bannerViewController = ForceSignInBanner.instanceFromNib() as! ForceSignInBanner
bannerViewController.delegate = self
if (!AuthenticationService.sharedInstance.isAuthenticated()) {
self.setView(view: bannerViewController, hidden: false)
print("Need to login")
} else if(AuthenticationService.sharedInstance.isAuthenticated()) {
self.setView(view: bannerViewController, hidden: true)
}
}
func setView(view: UIView, hidden: Bool) {
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: { _ in
view.isHidden = hidden
if hidden {
view.removeFromSuperview()
} else {
self.view.addSubview(view)
}
}, completion: nil)
}
It's because you're trying to remove a new ForceSignInBanner each time. Ideally you should create it once and keep a reference to the ForceSignInBanner created (as an optional property of ProtectedViewController).
Then remove the ForceSignInBanner that you've stored in the property.
class ProtectedViewController: UIViewController, ForceSignInBannerDelegate {
// This lazily loads the view when the property is first used and sets the delegate.
// Ideally you wouldn't force-case the `as` but I've left it for simplicity here.
private lazy var forceSignInBannerView: ForceSignInBanner = {
let forceSignInBannerView = ForceSignInBanner.instanceFromNib() as! ForceSignInBanner
forceSignInBannerView.delegate = self
return forceSignInBannerView
}()
// ... your other code ... //
fun toggleForceSignInBannerViewVisibility(isVisible: Bool) {
if isVisible {
view.addSubview(forceSignInBannerView)
} else {
forceSignInBannerView.removeFromSuperview()
}
}
}

How to set A subclass UILabel text ? THREAD ERROR

I'm trying to set a UILabel text as follows (All the following code is swift3)
// button Action which select a random text
#IBAction func getResultButton(_ sender: TransitionButton) {
if( typeTextField.text == "ABC" && pickedCompanyField.text != "XYZ"){
// query
sender.startAnimation()
// sleep(5)
sender.stopAnimation(animationStyle: .normal)
} else if( typeTextField.text == "DEF" && pickedCompanyField.text != "XYZ"){
// query
sender.startAnimation()
sender.stopAnimation(animationStyle: .shake)
// DispatchQueue.main.async {
self.resultLabel.text = "56.36"
// }
}
}
and I use the following subclass
import UIKit
class CpLabel: UILabel {
override public var canBecomeFirstResponder: Bool {
get {
return true
}
}
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
func sharedInit() {
isUserInteractionEnabled = true
addGestureRecognizer(UILongPressGestureRecognizer(
target: self,
action: #selector(showMenu(sender:))
))
}
override func copy(_ sender: Any?) {
UIPasteboard.general.string = text
UIMenuController.shared.setMenuVisible(false, animated: true)
}
func showMenu(sender: Any?) {
becomeFirstResponder()
let menu = UIMenuController.shared
if !menu.isMenuVisible {
menu.setTargetRect(bounds, in: self)
menu.setMenuVisible(true, animated: true)
}
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return (action == #selector(copy(_:)))
}
}
I get this Thread Error ... I don't know what is causing it ? I have tried to use DispatchQue... function to see if the error regarding main thread stuff but it seems not ? SO Any? Help will be appreciated
Error screenshot:
thanks guys I found the issue ... It was declared as UILabel and its sub classed to CpLabel , So I changed the deceleration it to CpLabel and it worked..

refreshControl extension does not show spinner after imagePicker

I wrote an extension that allows me to show a refreshControl on a regular tableView in a UIViewController. The code below works perfectly on the viewDidLoad() and when pull to refresh.
Extension:
extension UITableView
{
func findRefreshControl () -> UIRefreshControl?
{
for view in subviews
{
if view is UIRefreshControl
{
print("The refresh control: \(view)")
return view as? UIRefreshControl
}
}
return nil
}
func addRefreshControlWith(sender: UIViewController, action: Selector, view: UIView)
{
guard findRefreshControl() == nil else { return }
let refreshControl = UIRefreshControl()
refreshControl.addTarget(sender, action: action, for: UIControlEvents.valueChanged)
view.addSubview(refreshControl)
}
func showRefreshControlWith(attributedTitle: String? = nil)
{
findRefreshControl()?.attributedTitle = NSAttributedString(string: attributedTitle!)
findRefreshControl()?.beginRefreshing()
}
func hideRefreshControl ()
{
findRefreshControl()?.endRefreshing()
}
}
Callees in ViewController: (Triggerend by observe events)
private func showLoader()
{
tableView.showRefreshControlWith(attributedTitle: "Loading Profile".localized())
}
private func hideLoader()
{
tableView.hideRefreshControl()
}
Observers:
_ = presenter.profilePictureUploadingPending.skip(first: 1).observeNext { _ in self.showLoader() }
_ = presenter.profilePictureUploaded.skip(first: 1).observeNext { _ in self.hideLoader() }
However, when I enter the UIImagePicker to upload a photo in my app and return back to the viewController it should trigger the the spinning process again until the photo is uploaded successfully.
I debugged my code and it is firing up the methods but the spinner disappeared from the view hierarchy subviews and is not showing up...
I am not sure how i can solve this issue but I really appreciate any help from you guys!
Thanks,
Kevin.
Solved with following solution:
private func showLoader()
{
DispatchQueue.global(qos: .default).async {
DispatchQueue.main.async {
self.tableView.contentOffset.x = 1
self.tableView.contentOffset.y = -81
self.tableView.showRefreshControlWith(attributedTitle: "LOADING_PROFILE".localized())
}
}
}

Resources