1

I have a simple Instagram clone project that I am following along in a Udemy course with a LogInController class and a Registration class but when I run the app in an iOS 16.2 simulator using Xcode 14.2 with macOS Monterey 12.6.5 I get the following error message;

Thread 1: "+[InstagramFirestoreTutorial.LoginController presentRegistrationController]: unrecognized selector sent to class 0x10b086788"

when I click on "Don't have an account? Sign Up" at the bottom of the Log In screen, the LoginController calls an objective-C function to service the button tap and fails to instantiate the RegistrationController on line 118;

    @objc func presentRegistrationController() {
        let registrationController = RegistrationController()
        navigationController?.pushViewController(registrationController, animated: true)
    }

Here is the LoginController class code;

//
//  LoginController.swift
//  InstagramFirestoreTutorial
//
//  Created by *** on 27/02/2023.
//

import UIKit

class LoginController: UIViewController {
    
    // MARK: - Class Properties
    
    private let iconImageView: UIImageView = {
        let imageView = UIImageView(image: UIImage(named: "Instagram_logo_white")!)
        imageView.contentMode = .scaleAspectFill
        imageView.translatesAutoresizingMaskIntoConstraints = false
        return imageView
    }()
    
    private let emailTextField: CustomTextField = {
        let textField = CustomTextField(placeholder: "Email")
        textField.keyboardType = .emailAddress
        return textField
    }()
    
    private let passwordTextField: CustomTextField = {
        let textField = CustomTextField(placeholder: "Password")
        textField.isSecureTextEntry = true
        return textField
    }()
        
    private lazy var stackView: UIStackView = {
        let stackView = UIStackView(arrangedSubviews: [emailTextField, passwordTextField, logInButton, passwordRetrievalButton])
        stackView.axis = .vertical
        stackView.spacing = 20
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()
    
    private let logInButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Log In", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20.0)
        button.backgroundColor = UIColor(named: "logInButton")
        button.layer.cornerRadius = 5.0
        button.setHeight(50.0)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
        
    private let inviteRegistrationButton: UIButton = {
       
        let button = UIButton(type: .system)
        button.setAttributedTitle(withQuestion: "Don't have an account?", andAction: "Sign Up")
        button.addTarget(LoginController.self, action: #selector(presentRegistrationController), for: .touchUpInside)
        return button
    }()
    
    private let passwordRetrievalButton: UIButton = {
       
        let button = UIButton(type: .system)
        button.setAttributedTitle(withQuestion: "Forgotten your password?", andAction: "Get help logging in")
        return button
    }()

    // MARK: - Class Lifecycle Methods
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        configureUI()
        
    }
    
    // MARK: - Class Supplementary Methods
    
    private func configureUI() {
        
        view.backgroundColor = .white
        
        navigationController?.navigationBar.isHidden = true
        navigationController?.navigationBar.barStyle = .black
        
        configureGradientLayer()
        
        configureSubviews()
    }
    
    private func configureSubviews() {
        
        view.addSubview(iconImageView)
        NSLayoutConstraint.activate([
            iconImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            iconImageView.heightAnchor.constraint(equalToConstant: 80.0),
            iconImageView.widthAnchor.constraint(equalToConstant: 120.0),
            iconImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 32.0)
        ])
        
        view.addSubview(stackView)
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: iconImageView.bottomAnchor, constant: 32.0),
            stackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 32.0),
            stackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -32.0)
        ])
        
        view.addSubview(inviteRegistrationButton)
        NSLayoutConstraint.activate([
            inviteRegistrationButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            inviteRegistrationButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
    }
    
    // MARK: - Button Action Selector Functions
    
    @objc func presentRegistrationController() {
        let registrationController = RegistrationController()
        navigationController?.pushViewController(registrationController, animated: true)
    }
}

And here is the RegistrationController class code;

//
//  RegistrationController.swift
//  InstagramFirestoreTutorial
//
//  Created by **** on 27/02/2023.
//

import UIKit

class RegistrationController: UIViewController {
    
    // MARK: - Class Properties
    
    private let profilePhotoButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(named: "plus_photo"), for: .normal)
        button.tintColor = .white
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private let emailTextField: CustomTextField = {
        let textField = CustomTextField(placeholder: "Email")
        textField.keyboardType = .emailAddress
        return textField
    }()
    
    private let passwordTextField: CustomTextField = {
        let textField = CustomTextField(placeholder: "Password")
        textField.isSecureTextEntry = true
        return textField
    }()

    private let fullnameTextField = CustomTextField(placeholder: "Fullname")
    
    private let usernameTextField = CustomTextField(placeholder: "Username")
    
    private let signUpButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Sign Up", for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20.0)
        button.backgroundColor = UIColor(named: "signUpButton")
        button.layer.cornerRadius = 5.0
        button.setHeight(50.0)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private lazy var stackView: UIStackView = {
        let stackView = UIStackView(arrangedSubviews: [emailTextField, passwordTextField, fullnameTextField, usernameTextField, signUpButton])
        stackView.axis = .vertical
        stackView.spacing = 20
        stackView.translatesAutoresizingMaskIntoConstraints = false
        return stackView
    }()
    
    private let inviteToLogInButton: UIButton = {
       
        let button = UIButton(type: .system)
        button.setAttributedTitle(withQuestion: "Already have an account?", andAction: "Log In")
        button.addTarget(RegistrationController.self, action: #selector(presentLogInController), for: .touchUpInside)
        return button
    }()
    

    
    // MARK: - Class Lifecycle Methods
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
            configureUI()
    }
    
    // MARK: - Class Supplementary Methods
    
    private func configureUI() {
        
        configureGradientLayer()
        
        addUISubviews()
        
        navigationController?.navigationBar.isHidden = true
        
    }
    
    private func addUISubviews() {
        
        view.addSubview(profilePhotoButton)
        NSLayoutConstraint.activate([
            profilePhotoButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            profilePhotoButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 32.0),
            profilePhotoButton.widthAnchor.constraint(equalToConstant: 140.0),
            profilePhotoButton.heightAnchor.constraint(equalToConstant: 140.0)
        ])
        
        view.addSubview(stackView)
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: profilePhotoButton.bottomAnchor, constant: 32.0),
            stackView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 32.0),
            stackView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -32.0)
        ])
        
        view.addSubview(inviteToLogInButton)
        NSLayoutConstraint.activate([
            inviteToLogInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            inviteToLogInButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
    }
    
    @objc func presentLogInController() {
        navigationController?.popViewController(animated: true)
    }
}

Here is a screenshot of the Log In screen.

Log In Screen

I have noticed that when I replace LogInController.self with self in the button's addTarget method the app works although I do get a warning in Xcode;

Xcode warning

  • 1
    @Rob Thanks I have used 'self' and made the button a lazy var and that works now with no warnings. – IsaacNewtonGravity Apr 20 '23 at 16:10
  • 1
    This seems like a specific question where a custom answer is appropriate rather than closing it as a duplicate. Rob provided the answer in a comment. If we re-open it, he can post his solution as an answer and others can learn from it. I voted to re-open this question. – Duncan C Apr 20 '23 at 16:32
  • @Rob can you post your solution as an answer? – Duncan C Apr 20 '23 at 16:33

1 Answers1

0

You have a line that says

button.addTarget(LoginController.self, action: #selector(presentRegistrationController), for: .touchUpInside)

But LoginController.self is not the target. The instance of this type should be the target. It should be:

private lazy var inviteRegistrationButton: UIButton = {
    let button = UIButton(type: .system)
    button.setAttributedTitle(withQuestion: "Don't have an account?", andAction: "Sign Up")
    button.addTarget(self, action: #selector(presentRegistrationController), for: .touchUpInside)
    return button
}()

Bottom line, the error message you shared with us was not actually saying “you should use LoginController.self in this context” but rather “hey, if you are using self here; in this context, that is equivalent to LoginController.self; is that really what you meant?” Needless to say, that is not what you meant. So we should not accept that suggested “fix”, but rather change the code so that self really refers to the instance (e.g., by making this a lazy var or by moving this addTarget call to the addUISubviews method).

The inviteToLogInButton has a similar issue.

Rob
  • 415,655
  • 72
  • 787
  • 1,044