iProgram
iProgram

Reputation: 6547

How to access UI elements from UIView

I am just starting to move from the Interface Builder to 100% code.

I have successfully been able to create an Auto Layout single screen application as a proof of concept. The thing is, I feel as if there is a much more efficient way of doing this.

What is the proper way of getting objects from a UIView class and accessing then in a UIViewController ?

Here is the code to my whole project

AppDelegate.swift.

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = ViewController()
        window?.makeKeyAndVisible()
        return true
    }
}

ViewControllerView.swift

import UIKit

public enum ViewControllerTextFields: String {
    case username = "usernameField"
    case password = "passwordField"
}

public enum ViewControllerButtons: String {
    case login = "loginButton"
}

class ViewControllerView: UIView {

    private var views = [String: UIView]()

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .lightGray
        createScreenComponents()
        createConstraints()
    }

    func makeTextField(withPlaceholder text: String, textColor color: UIColor) -> UITextField {
        let textField = UITextField()
        textField.attributedPlaceholder =  NSAttributedString(string: text, attributes: [.foregroundColor : color.withAlphaComponent(0.5)])
        textField.textColor = color
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }

    func makeButton(withTitle title: String) -> UIButton {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle(title, for: .normal)
        return button
    }

    func createScreenComponents() {
        let usernameField = makeTextField(withPlaceholder: "Username", textColor: .white)
        let passwordField = makeTextField(withPlaceholder: "Password", textColor: .white)

        let loginButton = makeButton(withTitle: "Login")

        views["usernameField"] = usernameField
        views["passwordField"] = passwordField
        views["loginButton"] = loginButton
    }

    func createConstraints() {
        for (key, val) in views {
            addSubview(val)
            addConstraints(NSLayoutConstraint.constraints(
                withVisualFormat: "H:|[\(key)]|",
                options: [],
                metrics: nil,
                views: views)
            )
        }

        addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "V:|[usernameField]",
            options: [],
            metrics: nil,
            views: views)
        )

        addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "V:[usernameField][passwordField(==usernameField)]",
            options: [],
            metrics: nil,
            views: views)
        )

        addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "V:[passwordField][loginButton(==passwordField)]|",
            options: [],
            metrics: nil,
            views: views)
        )

    }

    public func getTextFieldWithId(_ identifier: ViewControllerTextFields) -> UITextField {
        return views["\(identifier.rawValue)"] as! UITextField
    }

    public func getButtonWithID(_ identifier: ViewControllerButtons) -> UIButton {
        return views["\(identifier.rawValue)"] as! UIButton
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    var username: UITextField!
    var password: UITextField!

    override func loadView() {
        let viewObject = ViewControllerView()
        view = viewObject

        username = viewObject.getTextFieldWithId(.username)
        password = viewObject.getTextFieldWithId(.password)

        viewObject.getButtonWithID(.login).addTarget(self, action: #selector(test), for: .touchUpInside)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @objc func test() {
        print("Hello")
    }
}

Is there a way to access the ViewControllerView objects such as a UITextFields and UIButtons without having to use the viewObject variable and using functions to return the views?

Upvotes: 1

Views: 1891

Answers (2)

DonMag
DonMag

Reputation: 77442

I think this is a much more logical - and easier - way to go about it:

// Typical AppDelegate.swift for running WITHOUT storyboard

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = ViewController()
        window?.makeKeyAndVisible()

        return true
    }

}

// our start-up View Controller

import UIKit

class ViewController: UIViewController {

    var theVCView = ViewControllerView()

    override func viewDidLoad() {
        super.viewDidLoad()

        // replace "default" view with our custom view
        self.view = theVCView

        // example of adding a "Call Back" closure to "hear from" and get access to the custom view elements
        theVCView.buttonCallBackAction = {
            _ in
            var sUsername = ""
            var sPassword = ""
            if let t = self.theVCView.usernameField.text {
                sUsername = t
            }
            if let t = self.theVCView.passwordField.text {
                sPassword = t
            }
            print("Username:", sUsername)
            print("Password:", sPassword)
            // do whatever else you want here...
        }

    }

}

// and... our custom UIView

import UIKit

class ViewControllerView: UIView {

    lazy var usernameField: UITextField = {
        let textField = UITextField()
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.attributedPlaceholder =  NSAttributedString(string: "Username", attributes: [NSForegroundColorAttributeName : (UIColor.white).withAlphaComponent(0.5)])
        textField.textColor = .white
        return textField
    }()

    lazy var passwordField: UITextField = {
        let textField = UITextField()
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.attributedPlaceholder =  NSAttributedString(string: "Password", attributes: [NSForegroundColorAttributeName : (UIColor.white).withAlphaComponent(0.5)])
        textField.textColor = .white
        return textField
    }()

    lazy var loginButton: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Login", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
        return button
    }()

    // optional "Call Back" closure
    var buttonCallBackAction : (()->())?

    func buttonTapped(_ sender: Any?) -> Void {
        buttonCallBackAction?()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.commonInit()
    }

    func commonInit() {
        self.backgroundColor = .lightGray
        createConstraints()
    }

    func createConstraints() {
        let views = [
            "usernameField": usernameField,
            "passwordField": passwordField,
            "loginButton": loginButton
        ] as [String : UIView]

        for (key, val) in views {
            addSubview(val)
            addConstraints(NSLayoutConstraint.constraints(
                withVisualFormat: "H:|[\(key)]|",
                options: [],
                metrics: nil,
                views: views)
            )
        }

        addConstraints(NSLayoutConstraint.constraints(
            withVisualFormat: "V:|-80-[usernameField][passwordField(==usernameField)][loginButton(==passwordField)]",
            options: [],
            metrics: nil,
            views: views)
        )

    }

}

Now, you have properties of your custom view that you can access in the "normal" way.

Upvotes: 0

genghiskhan
genghiskhan

Reputation: 1149

My recommendation would be to hold private variables of the textfields and buttons in ViewController View, but provide read-only variables that can be accessed in the controller,

In ViewControllerView.swift:

private var _username: UITextField!
private var _password: UITextField!
private var _login: UIButton!

var username: UITextField! { get { return _username } }
var password: UITextField! { get { return _password } }
var login: UIButton! { get { return _login } }

Then in ViewController.swift, you can substitute the following lines:

username = viewObject.getTextFieldWithId(.username)
password = viewObject.getTextFieldWithId(.password)

viewObject.getButtonWithID(.login).addTarget(self, action: #selector(test), for: .touchUpInside)

with:

username = viewObject.username
password = viewObject.password

viewObject.login.addTarget(self, action: #selector(test), for: .touchUpInside)

You could even store these variables as instance variables in the ViewController class if you wish.

Upvotes: 1

Related Questions