UITapGestureRecognizer is not being recognized - ios

In my storyboard I have a basic view, with a login field. In the storyboard the checkbox for Enable user interaction is checked, and I am also settings this through the code.
Below is the code I'm using for this:
class LoginViewController : UIViewController {
#IBOutlet weak var emailField: UITextField!
#IBOutlet weak var passwordField: UITextField!
override func viewDidLoad() {
super.viewDidLoad();
let closeKeyboardGesture = UITapGestureRecognizer(target: self, action: "dismissKeyboard");
self.view.userInteractionEnabled = true
self.view.addGestureRecognizer(closeKeyboardGesture);
}
private func dismissKeyboard() {
print("Called")
if passwordField.selected {
passwordField.resignFirstResponder()
}
if emailField.selected {
emailField.resignFirstResponder()
}
}
}
As you can see it's fairly straightforward, however the dismissKeyboard function is never called. What bothers me is I'm using these gestures elsewhere in my application and they're working fine.

That´s because you have your dismissKeyboard function marked as private. If you are calling a method from a selector and it is private they cannot be called because the method is called from outside.
So remove private from dismissKeyboard and it will work.
Update
If you change the above it will work, what´s not working for you right now is the resignFirstResponder You don´t need to the if-checks since you always want to hide the keyboard when you active the closeKeyboardGesture, so it´s enough to only call self.view.endEditing(true). I have created a sample project for you that has a working example. You can download it from here.

func dismissKeyboard() {
view.endEditing(true)
}
This will do the trick.

Related

RxSwift calling bind fires immediately vs subscribe(onNext: )

Everything I've read says that bind(to:) calls subscribe(onNext:) within it. So I assume I should be able to swap out some stuff, but when I use `bind(to:) the thing it's binding to fires immediately. Here's my example:
ButtonCollectionViewCell
class ButtonCollectionViewCell: UICollectionViewCell {
lazy var buttonTapped: Observable<Void> = { _buttonTapped.asObservable() }()
private var _buttonTapped = PublishSubject<Void>()
private var disposeBag = DisposeBag()
#IBOutlet private weak var textLabel: UILabel!
#IBOutlet private weak var actionButton: UIButton!
// MARK: - Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
actionButton.rx.tap.bind(to: _buttonTapped).disposed(by: disposeBag)
}
override func prepareForReuse() {
disposeBag = DisposeBag()
}
}
Now when I do the following below everything works as expected and it prints to the console when I tap the button
ViewController with a collection view
func createButtonCell() {
let buttonCell = ButtonCollectionViewCell() // there's more code to create it, this is just for simplicity
buttonCell.buttonTapped.subscribe { _ in
print("tapped")
}.disposed(by: disposeBag)
return buttonCell
}
However, if I change the above to:
func createButtonCell() {
let buttonCell = ButtonCollectionViewCell()
buttonCell.buttonTapped.bind(to: buttonTapped)
return buttonCell
}
private func buttonTapped(_ sender: Observable<Void>) {
print("tapped")
}
The "tapped" is printed out right before I get to the cell when scrolling which I assume is when it's being created.
I don't understand this. I thought I could almost swap out the implementations? I would like to use the second example above there as I think it's neater but can't figure out how.
Your two examples are not identical...
In the first example you have: .subscribe { _ in print("tapped") } which is not a subscribe(onNext:) call. The last closure on the subscribe is being used, not the first. I.E., you are calling subscribe(onDisposed:).
Also, your ButtonCollectionViewCell is setup wrong. You bind in the awakeFromNib() which is only called once, and dispose in the prepareForReuse() which is called multiple times. One of the two needs to be move to a more appropriate place...
UPDATE
You could either rebind your subject after reseating the disposeBag, or you could just not put the chain in the dispose bag in the first place by doing:
_ = actionButton.rx.tap
.takeUntil(rx.deallocating)
.bind(to: _buttonTapped)

Using shared classes for different views

I have an onboarding user flow:
Name -> Age -> Gender
Each of the screens shares the same structure:
Question (top)
Input (middle)
Continue (bottom)
I have a class OnboardingHelper.swift that creates a class to set the question box and continue button:
class UserOnboardingHelper{
var text: String
var questionbox: UIView
var viewController: UIViewController
var continueButton: UIButton
init(text: String, questionbox: UIView, viewController: UIViewController, continueButton: UIButton){
self.text = text
self.questionbox = questionbox
self.viewController = viewController
self.continueButton = continueButton
}
func setQuestionBox(){
//sets question box
}
func setContinueButton(){
//sets continue button
enableContinueButton()
addContinueButtonPath()
}
func enableContinueButton(){
//enables continue button
}
func disableContinueButton(){
//disables continue button
}
func addContinueButtonPath(){
//sets path of continue button based on which view
}
}
In each of the onboarding ViewControllers I am setting the class in ViewDidLoad():
class NamePageViewController: UIViewController, UITextFieldDelagate {
#IBOutlet weak var questionbox: UIView!
#IBOutlet weak var continueButton: UIButton!
#IBOutlet weak var inputLabel: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
let namePageSettings = UserOnboardingHelper(text: "What is your name", questionbox: questionbox, viewController: self, continueButton: continueButton)
namePageSettings.setQuestionBox()
namePageSettings.setContinueButton()
inputLabel.delegate = self
if nameIsFilled {
namePageSettings.enableContinueButton()
} else{
namePageSettings.disableContinueButton()
}
}
}
The issue is that in the ViewController I textFieldDidEndEditing() function which needs to call the namePageSettings class from viewDidLoad()
func textFieldDidEndEditing(_ textField: UITextField){
if (textField.text?.empty)!{
//I want to call disableContinueButton() from UserOnboardingHelper
} else {
//I want to enable enableContinueButton() from UserOnboardingHelper
}
}
Trying to understand if:
The overall approach is correct and if not, what's the best way
If the above approach is in the right direction, how should disableContinueButton() and enableContinueButton() be called?
Thanks in advance! Sorry if the approach is really dumb - I'm still trying to wrap my head around classes.
You can have the view controller have a weak reference to the onboarding helper, so you can still call helper methods without creating a retain cycle.
In NamePageViewController, add a property:
weak var userOnboardingHelper: UserOnboardingHelper?
Then, in UserOnboardingHelper's initializer, add:
self.viewController.userOnboardingHelper = self
You can now call the onboarding helper's methods in the view controller:
userOnboardingHelper.disableContinueButton()
userOnboardingHelper.enableContinueButton()

UIButton inside a custom input view doesn't invoke any methods

I created a custom input view with a couple of UIButtons inside it. All these buttons invoke one single IBAction.
The IBAction is not called although I'am 100% sure I set them properly in IB. To prove that this isn't causing the problem I added a target to one of the buttons programmatically, but the action still won't get invoked.
import UIKit
protocol CashDeskInputDelegate: class {
func didReceiveInput(inputValue: String)
func didReceiveDoneMessage()
}
class CashDeskInputViewController: UIInputViewController {
// Delegate
weak var delegate: CashDeskInputDelegate?
// Actions
#IBAction func buttonTapped(sender: UIButton) {
print("buttonTapped")
guard let valueOfSender = sender.titleLabel?.text else {
dismissKeyboard()
return
}
valueOfSender == "Done" ? delegate?.didReceiveDoneMessage() : delegate?.didReceiveInput(valueOfSender)
}
// Test outlet
#IBOutlet weak var firstButton: UIButton!
override func viewDidLoad() {
// Add an action to the first button for testing purposes.
firstButton.addTarget(self, action: "buttonTapped:", forControlEvents: .TouchUpInside)
}
}
I didn't implement the delegate properly yet, but that not the the point. The print("buttonTapped") won't even show up. The method just doesn't get called, I tested this using breakpoints.
Does anyone know what is causing this problem

Swift: how to use UISwitch to control UITextField to be enabled or disabled with a delegate

In XCode 6.3.2, I have a UITextField:
#IBOutlet weak var uiswitchControlledTextField: UITextField!
I am now using a UISwitch (named mySwitch) to control its enabled or disabled state in the following way:
#IBOutlet weak var mySwitch: UISwitch!
mySwitch.addTarget(self, action: Selector("stateChanged:"), forControlEvents: UIControlEvents.ValueChanged)
//callback below:
func stateChanged(switchState: UISwitch) {
uiswitchControlledTextField.enabled = switchState.on
}
The above works well, however, I am looking to try if it would be possible to create a UITextFieldDelegate to control the above UITextField in the same way. So far, I have the following by implementing textFieldShouldBeginEditing, in which I wish to return false to disable the UITextField, but I don't know how to let the UISwitch dynamically return true or false from textFieldShouldBeginEditing
import Foundation
import UIKit
class SwitchControlledTextFieldDelegate: NSObject, UITextFieldDelegate {
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
return false; //do not show keyboard or cursor
}
}
In ViewController, I try to set
self.uiswitchControlledTextField.delegate = SwitchControlledTextFieldDelegate()
but it does not work as I wished. Any help would be appreciated.
self.uiswitchControlledTextField.delegate = SwitchControlledTextFieldDelegate()
The problem is that that line merely creates an instance of your SwitchControlledTextFieldDelegate class, which then immediately goes right back out of existence.
You need to use, as your text field delegate, some instance which already exists and which will persist - like, perhaps, your view controller!
(Xcode 7)
Use this:
override func viewDidLoad() {
super.viewDidLoad()
// Setting the delegate
self.textField3.delegate = self
self.editingSwitch.setOn(false, animated: false)
}
// Text Field Delegate Methods
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
return self.editingSwitch.on
}
#IBAction func toggleTheTextEditor(sender: AnyObject) {
if !(sender as! UISwitch).on {
self.textField3.resignFirstResponder()
}
}

how to instantiate/ show a TableViewController

I am struggling with this awful project for two weeks now and nothing seems to work. I have to do an app that loads some words from a server and presents them as pins on a map(MKView). I have to cluster the pins when the user zooms-out and for that I have used a this-party library written in Objective-C, but I also had to make a custom callout view with a button. When the user presses the said button the app should go to a TableViewController and here is my problem: I can't make it to do it. I have used before the "performSegueWIthIdentifier" and it worked very well, but now I get the error "there is no segue with '---' identifier". I know there are many other threads with this, but none of the solutions there work for me. Moreover, I have tried to instantiate the ViewController programmatically, but this does not work either because I get the "unexpectedly found nil..." and I don't know what to do any more.
I know I am doing something wrong, most probably how I call these functions, but I don't know what. This is what I have tried until now:
in the .xib file I have this:
import UIKit
class MarkerInfoView: MKAnnotationView {
#IBOutlet weak var theButton: UIButton!
#IBAction func readIt(sender: AnyObject) {
ViewController.goToArticles()
}
}
and in the ViewController:
class func goToArticles(){
ViewController().reallyGoToArticles()
}
I did this because I could not find another way to be able to call performSegueWithIdentifier or presentViewController
func reallyGoToArticles(){
println("let's go!")
let theArticlesSB = UIStoryboard(name: "Main", bundle: nil)
let vc = theArticlesSB.instantiateViewControllerWithIdentifier("theArticles") as! articlesViewController
self.presentViewController(vc, animated: true, completion: nil)
self.performSegueWithIdentifier("showArticles", sender: self)
}
I have uncommented both options just to show you.
I have uploaded the project here
Thank you very much!
Update
I forgot to mention that if I put the line
self.performSegueWithIdentifier("showArticles", sender: self)
in viewDidLoad it works
I had a look at your project and found your issue.
In MarkerInfoView.swift, you call ViewController.goToArticles() which is a class function:
class func goToArticles(){
ViewController().reallyGoToArticles()
}
This class function creates a NEW instance of ViewController which has nothing to do with the storyboard (and is not aware of segues).
You have to call self.reallyGoToArticles() from an instance method like
func goToArticles(){
self.reallyGoToArticles()
}
You have to manage to call the existing ViewController from your MarkerInfoView
EDIT: Here is how to achieve it
MarkerInfoView.swift
class MarkerInfoView: MKAnnotationView {
var vc: ViewController!
#IBOutlet weak var placePhoto: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var detailsLabel: UILabel!
#IBOutlet weak var theButton: UIButton!
#IBAction func readIt(sender: AnyObject) {
vc.goToArticles()
}
}
ViewController.swift
func mapView(mapView: MKMapView!, didSelectAnnotationView annotation: MKAnnotationView!)
{
if let pin = annotation.annotation as? CustomPointAnnotation{
if var infoView = UIView.viewFromNibName("MarkerInfoView") as? MarkerInfoView {
infoView.nameLabel.text = pin.theTitle
infoView.detailsLabel.text = pin.theDetails
infoView.vc = self
infoView.center = CGPointMake(self.view.bounds.width/2, self.view.bounds.height/2)
self.view.addSubview(infoView)
} else {
}
}
}
func goToArticles(){
self.reallyGoToArticles()
}

Resources