Programmatic Components

Oğuzhan Akın
5 min readJan 4, 2023

--

Photo by AbsolutVision on Unsplash

Closure Kullanımı

UI elemanlarını kod ile oluşturmanın da bir kaç farklı yöntemi var fakat closure’lar bu iş için biçilmiş kaftan diyebilirim. Bir çok konuda işlevsellik kazandıran closure yapısı kodumuzu modüler hale getirmemize yardımcı olacak.

3 farklı componentin closure ile oluşturulması

Yukarıdaki kod bloğunda oluşturduğumuz titleLabel ve emailTextField nesnelerimizin yapısının emailContainerView’e göre daha basit ,anlaşılır yapıda olduğunu görebiliriz. Buradaki asıl amacımız birden fazla componenti içerecek mail giriş alanı oluşturmak ve bu yapıyı da UIView türünde oluşturduğumuz ContainerView’de tutmak, daha sonra viewDidLoad’un içerisine bu kompleks yapıyı addSubview edeceğiz. ContainerView’in içerisinde doğrudan imageView, emailTextField ve seperatorView’i daha öncesinde extensionda yazdığım anchor fonksiyonuyla constraintlerini vererek eklediğimizi görebiliriz.

UI Elemanlarını View’e çağırmak

Oluşturduğumuz emailContainerView’i ve titleLabel yardımcı fonksiyon aracılığıyla viewDidLoad’a göndermiş olduk. Anchor fonksiyonun kullanım mantığına değinmek gerekirse titleLabel safeArea’nın en üstüne sabitledik ve boşluk vermedik ardından emailContainerView’in üst hizasını top olarak titleLabel’ın alt hizasından aldık ve 40 boşluk verdik, bu mantıkla diğer kullanımları da inceleyerek mantığını anlayabiliriz. Bunca kodun çıktısını aşağıda görüntülüyoruz.

Aşağıya eklediğim kod bloğunda gördüğümüz üzere benzer iki yapı için aynı kodları tekrar ettik ve çok uzun satırlar süren bu görüntü bize bir şeyler daha yapmamız için sinyal veriyor. Yeniden extension yazamaya başlamak bizi hafifletecek, yaralarımızı saracak.

private lazy var emailContainerView: UIView = {
let view = UIView()

let imageView = UIImageView()
imageView.image = UIImage(named: "ic_mail_outline_white_2x")
imageView.alpha = 0.87
view.addSubview(imageView)
imageView.centerY(inView: view)
imageView.anchor(left: view.leftAnchor, paddingLeft: 8, width: 24, height: 24)

view.addSubview(emailTextField)
emailTextField.centerY(inView: view)
emailTextField.anchor(left: imageView.rightAnchor, bottom: view.bottomAnchor,
right: view.rightAnchor, paddingLeft: 8, paddingBottom: 8)

let seperatorView = UIView()
seperatorView.backgroundColor = .lightGray
view.addSubview(seperatorView)
seperatorView.anchor(left: view.leftAnchor, bottom: view.bottomAnchor,
right: view.rightAnchor, paddingLeft: 8, height: 0.75)

return view
}()

private lazy var passwordContainerView: UIView = {
let view = UIView()

let imageView = UIImageView()
imageView.image = UIImage(named: "ic_lock_outline_white_2x")
imageView.alpha = 0.87
view.addSubview(imageView)
imageView.centerY(inView: view)
imageView.anchor(left: view.leftAnchor, paddingLeft: 8, width: 24, height: 24)

view.addSubview(passwordTextField)
passwordTextField.centerY(inView: view)
passwordTextField.anchor(left: imageView.rightAnchor, bottom: view.bottomAnchor,
right: view.rightAnchor, paddingLeft: 8, paddingBottom: 8)

let seperatorView = UIView()
seperatorView.backgroundColor = .lightGray
view.addSubview(seperatorView)
seperatorView.anchor(left: view.leftAnchor, bottom: view.bottomAnchor,
right: view.rightAnchor, paddingLeft: 8, height: 0.75)

return view
}()

private let emailTextField: UITextField = {
let tf = UITextField()
tf.borderStyle = .none
tf.font = UIFont.systemFont(ofSize: 16)
tf.textColor = .white
tf.keyboardAppearance = .dark
tf.attributedPlaceholder = NSAttributedString(string: "Email", attributes: [NSAttributedString.Key.foregroundColor : UIColor.lightGray])
return tf
}()

private let passwordTextField: UITextField = {
let tf = UITextField()
tf.borderStyle = .none
tf.font = UIFont.systemFont(ofSize: 16)
tf.textColor = .white
tf.keyboardAppearance = .dark
tf.isSecureTextEntry = true
tf.attributedPlaceholder = NSAttributedString(string: "Password", attributes: [NSAttributedString.Key.foregroundColor : UIColor.lightGray])
return tf
}()

Kodu Revize Etmek

Oluşturduğumuz 4 componentinde ortak özelliklerini yazacağımız extensionda ele alıp farklı özelliklerini de parametre alacak şekilde bir fonksiyon içerisine dahil edeceğiz. Aşağıya eklediğim kod bloğunu ayrı bir swift dosyası açarak extensionlar arasına eklenebilir.

import UIKit

extension UIView {

func inputContainerView(image: UIImage?, textField: UITextField? = nil) -> UIView {
let view = UIView()

let imageView = UIImageView()
imageView.image = image
imageView.alpha = 0.87
view.addSubview(imageView)


if let textField = textField {
imageView.centerY(inView: view)
imageView.anchor(left: view.leftAnchor, paddingLeft: 8, width: 24, height: 24)

view.addSubview(textField)
textField.centerY(inView: view)
textField.anchor(left: imageView.rightAnchor, bottom: view.bottomAnchor,
right: view.rightAnchor, paddingLeft: 8, paddingBottom: 8)
}

let seperatorView = UIView()
seperatorView.backgroundColor = .lightGray
view.addSubview(seperatorView)
seperatorView.anchor(left: view.leftAnchor, bottom: view.bottomAnchor,
right: view.rightAnchor, paddingLeft: 8, height: 0.75)

return view
}
}

extension UITextField {

func textField(withPlaceholder placeholder: String, isSecureTextEntry: Bool) -> UITextField {
let tf = UITextField()
tf.borderStyle = .none
tf.font = UIFont.systemFont(ofSize: 16)
tf.textColor = .white
tf.keyboardAppearance = .dark
tf.isSecureTextEntry = isSecureTextEntry
tf.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [NSAttributedString.Key.foregroundColor : UIColor.lightGray])
return tf
}
}

Extension’da yazdığımız inputContainerView fonksiyonunu componentimize bir takım ekstra özellik katmak için method olarak çağırabiliriz. Diğer bir extension ise yalnızca UITextField sınıfına yazdığımız ve yalnızca bu sınıftan türeteceğimiz componentlerde çağırılacak bir method olacak. Kodu revize ettikten sonraki görünüm şu şekilde olacak.

Controller’ı temiz tutmak

Son olarak bir de button ekleyeceğiz, yine controllerda nesnemizi oluşturup fazlalık kodları projenin farklı yerlerinde tekrar etmemek amacıyla bir customComponent yaratıp bu butonu doğrudan oluşturduğumuz sınıftan türeteceğiz.

Ayrı bir dosyada UIButton sınıfdan yeni sınıf oluşturulur
import UIKit

class AuthButton: UIButton {

override init(frame: CGRect) {
super.init(frame: frame)

setTitleColor(UIColor(white: 1, alpha: 0.5), for: .normal)
backgroundColor = .mainBlueTint
layer.cornerRadius = 5
heightAnchor.constraint(equalToConstant: 50).isActive = true
}

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

}
AuthButton sınıfından bir loginButton oluşturduk
import UIKit

final class LoginController: UIViewController {

// MARK: - Properties

private let titleLabel: UILabel = {
let label = UILabel()
label.text = "PROGRAMMATIC UI"
label.font = UIFont(name: "Avenir-Light", size: 32)
label.textColor = UIColor(white: 1, alpha: 0.8)
return label
}()

private lazy var emailContainerView: UIView = {
let view = UIView().inputContainerView(image: UIImage(named: "ic_mail_outline_white_2x"),
textField: emailTextField)
view.heightAnchor.constraint(equalToConstant: 50).isActive = true
return view
}()

private lazy var passwordContainerView: UIView = {
let view = UIView().inputContainerView(image: UIImage(named: "ic_lock_outline_white_2x"),
textField: passwordTextField)
view.heightAnchor.constraint(equalToConstant: 50).isActive = true
return view
}()

private let emailTextField: UITextField = {
return UITextField().textField(withPlaceholder: "Email",
isSecureTextEntry: false)
}()

private let passwordTextField: UITextField = {
return UITextField().textField(withPlaceholder: "Password",
isSecureTextEntry: true)
}()

private lazy var loginButton: AuthButton = {
let button = AuthButton(type: .system)
button.setTitle("Log In", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
button.addTarget(self, action: #selector(handleLogin), for: .touchUpInside)
return button
}()

// MARK: - Lifecycle

override func viewDidLoad() {
super.viewDidLoad()

configureUI()
}

// MARK: - Helpers

func configureUI() {

view.addSubview(titleLabel)
titleLabel.anchor(top: view.safeAreaLayoutGuide.topAnchor)
titleLabel.centerX(inView: view)

let stack = UIStackView(arrangedSubviews: [emailContainerView,
passwordContainerView,
loginButton])
stack.axis = .vertical
stack.distribution = .fillEqually
stack.spacing = 16

view.addSubview(stack)
stack.anchor(top: titleLabel.bottomAnchor, left: view.leftAnchor,
right: view.rightAnchor, paddingTop: 40, paddingLeft: 16,
paddingRight: 16)
}

// MARK: - Selectors

@objc func handleLogin() {
print("Tapped Login Button..")
}

}

LoginController’ın son halini yukarıda inceleyebiliriz, ayrıca UIStackView kullanımını da eklemek istedim. Programmatic UI’a hızlı bir giriş niteliğinde birbirinin devamı niteliğindeki 3 yazının sonuncusunu da tamamlamış olduk. Son ekran görüntüsüyle tamamlıyorum. Faydalı olması dileğiyle, vakit ayırdığınız için teşekkür ederim.

https://github.com/Ogushanakin/ProgrammaticUIMedium

--

--