iOS 로그인 UI (Code UI 종합 예제)
목차
프로퍼티 생성 3가지 방식, UI 설정(기본, NSLayout), .addTarget 사용법(objc, 순수 Swift)이 모두 포함된 로그인 화면 예제.
//
// ViewController.swift
// LoginWithCodeUI
//
// Created by SangHyuk Moon on 2/13/25.
//
import UIKit
final class ViewController: UIViewController {
// MARK: - Properties
//클로저 사용
private lazy var emailTextFieldView: UIView = {
let view = UIView()
view.backgroundColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1)
view.layer.cornerRadius = 5
view.clipsToBounds = true
view.addSubview(emailTextField)
view.addSubview(emailInfoLabel)
return view
}()
private var emailInfoLabel: UILabel = {
let label = UILabel()
label.text = "이메일 주소 또는 전화번호"
label.textColor = .white
label.font = .systemFont(ofSize: 18)
return label
}()
private var emailTextField: UITextField = {
let textField = UITextField()
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.keyboardType = .emailAddress
textField.textColor = .white
textField.tintColor = .white
textField.backgroundColor = .clear
textField.frame.size.height = 48
textField.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editingChanged)
return textField
}()
//메서드 사용
private lazy var passwordTextFieldView = setupPasswordTextFieldView()
private lazy var passwordInfoLabel = setupPasswordInfoLabel()
private lazy var passwordTextField = setupPassswordTextField()
private lazy var passwordToggleButton = setupPasswordToggleButton()
//creationUI()에 설정하여 사용
private var loginButton = UIButton()
private var passwordResetButton = UIButton()
private lazy var stackView = UIStackView(arrangedSubviews: [
emailTextFieldView,
passwordTextFieldView,
loginButton
])
private let textViewHeight: CGFloat = 48
//Label center Y constraint
private lazy var emailInfoLabelCenterYConstraint: NSLayoutConstraint = {
return emailInfoLabel
.centerYAnchor
.constraint(equalTo: emailTextFieldView.centerYAnchor)
}()
private lazy var passwordInfoLabelCenterYConstraint: NSLayoutConstraint = {
return passwordInfoLabel
.centerYAnchor
.constraint(equalTo: passwordTextFieldView.centerYAnchor)
}()
// MARK: - Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
createUI()
setupUI()
setupDelegate()
}
// MARK: - UI Setup
private func setupUI() {
view.backgroundColor = .black
view.addSubview(stackView)
view.addSubview(passwordResetButton)
emailInfoLabel.translatesAutoresizingMaskIntoConstraints = false
emailTextField.translatesAutoresizingMaskIntoConstraints = false
passwordInfoLabel.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
passwordToggleButton.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
passwordResetButton.translatesAutoresizingMaskIntoConstraints = false
//Default
emailInfoLabel
.leadingAnchor
.constraint(equalTo: emailTextFieldView.leadingAnchor, constant: 8)
.isActive = true
emailInfoLabel
.trailingAnchor
.constraint(equalTo: emailTextFieldView.trailingAnchor, constant: -8)
.isActive = true
emailInfoLabelCenterYConstraint.isActive = true
//Use NSLayout
NSLayoutConstraint.activate([
emailTextField
.leadingAnchor
.constraint(equalTo: emailTextFieldView.leadingAnchor, constant: 8),
emailTextField
.trailingAnchor
.constraint(equalTo: emailTextFieldView.trailingAnchor, constant: -8),
emailTextField
.topAnchor
.constraint(equalTo: emailTextField.topAnchor, constant: 15),
emailTextField
.bottomAnchor
.constraint(equalTo: emailTextFieldView.bottomAnchor, constant: -2),
passwordInfoLabel
.leadingAnchor
.constraint(equalTo: passwordTextFieldView.leadingAnchor, constant: 8),
passwordInfoLabel
.trailingAnchor
.constraint(equalTo: passwordTextFieldView.trailingAnchor, constant: -8),
passwordInfoLabelCenterYConstraint,
passwordTextField
.leadingAnchor
.constraint(equalTo: passwordTextFieldView.leadingAnchor, constant: 8),
passwordTextField
.trailingAnchor
.constraint(equalTo: passwordTextFieldView.trailingAnchor, constant: -8),
passwordTextField
.topAnchor
.constraint(equalTo: passwordTextField.topAnchor, constant: 15),
passwordTextField
.bottomAnchor
.constraint(equalTo: passwordTextFieldView.bottomAnchor, constant: -2),
passwordToggleButton
.trailingAnchor
.constraint(equalTo: passwordTextFieldView.trailingAnchor, constant: -8),
passwordToggleButton
.topAnchor
.constraint(equalTo: passwordTextFieldView.topAnchor, constant: 15),
passwordToggleButton
.bottomAnchor
.constraint(equalTo: passwordTextFieldView.bottomAnchor, constant: -15),
stackView
.centerXAnchor
.constraint(equalTo: view.centerXAnchor),
stackView
.centerYAnchor
.constraint(equalTo: view.centerYAnchor),
stackView
.leadingAnchor
.constraint(equalTo: view.leadingAnchor, constant: 30),
stackView
.trailingAnchor
.constraint(equalTo: view.trailingAnchor, constant: -30),
stackView
.heightAnchor
.constraint(equalToConstant: textViewHeight * 3 + 36),
passwordResetButton
.leadingAnchor
.constraint(equalTo: view.leadingAnchor, constant: 30),
passwordResetButton
.trailingAnchor
.constraint(equalTo: view.trailingAnchor, constant: -30),
passwordResetButton
.topAnchor
.constraint(equalTo: stackView.bottomAnchor, constant: 10),
passwordResetButton
.heightAnchor
.constraint(equalToConstant: textViewHeight),
])
}
// MARK: - setupDelegate
private func setupDelegate() {
emailTextField.delegate = self
passwordTextField.delegate = self
}
// MARK: - UI Creation Methods
private func createUI() {
setupLoginButton()
setupPasswordResetButton()
setupStackView()
}
private func setupPasswordTextFieldView() -> UIView {
let view = UIView()
view.backgroundColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1)
view.layer.cornerRadius = 5
view.clipsToBounds = true
view.addSubview(passwordTextField)
view.addSubview(passwordInfoLabel)
view.addSubview(passwordToggleButton)
return view
}
private func setupPasswordInfoLabel() -> UILabel{
let label = UILabel()
label.text = "비말번호"
label.textColor = .white
label.font = .systemFont(ofSize: 18)
return label
}
private func setupPassswordTextField() -> UITextField {
let textField = UITextField()
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.spellCheckingType = .no
textField.keyboardType = .default
textField.isSecureTextEntry = true
textField.clearsOnBeginEditing = true
textField.textColor = .white
textField.tintColor = .white
textField.backgroundColor = .clear
textField.frame.size.height = textViewHeight
textField.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editingChanged)
return textField
}
private func setupPasswordToggleButton() -> UIButton {
let button = UIButton(type: .system)
button.setTitle("표시", for: .normal)
button.setTitleColor(#colorLiteral(red: 0.6000000238, green: 0.6000000238, blue: 0.6000000238, alpha: 1), for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 14, weight: .light)
button.addTarget(self, action: #selector(passwordToggleModeSetting), for: .touchUpInside)
return button
}
private func setupLoginButton() {
loginButton.setTitle( "로그인", for: .normal)
loginButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .bold)
loginButton.layer.cornerRadius = 5
loginButton.clipsToBounds = true
loginButton.layer.borderWidth = 1
loginButton.layer.borderColor = #colorLiteral(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
loginButton.isEnabled = false
loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
}
private func setupPasswordResetButton() {
passwordResetButton.setTitle( "비밀번호 재설정", for: .normal)
passwordResetButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .light)
passwordResetButton.backgroundColor = .clear
//ojbc -> pure Swift
passwordResetButton.addAction(UIAction { [weak self] _ in
guard let self else { return }
self.resetButtonTapped()
}, for: .touchUpInside)
}
private func setupStackView() {
stackView.axis = .vertical
stackView.spacing = 18
stackView.distribution = .fillEqually
stackView.alignment = .fill
}
// MARK: - Action Methods
@objc func passwordToggleModeSetting() {
passwordTextField.isSecureTextEntry.toggle()
}
@objc func loginButtonTapped() {
view.endEditing(true)
print("success login")
}
//ojbc -> pure Swift
private func resetButtonTapped() {
let alert = UIAlertController(
title: "비밀번호 바꾸기",
message: "비밀번호를 바꾸시겠습니까",
preferredStyle: .alert
)
let success = UIAlertAction(title: "확인", style: .default) { _ in
print("확인")
}
let cancel = UIAlertAction(title: "취소", style: .cancel) { _ in
print("취소")
}
alert.addAction(success)
alert.addAction(cancel)
present(alert, animated: true)
}
//Dismiss keyboard when tapping outside text fields
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
}
// MARK: - Extension UITextFieldDelegate
extension ViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == emailTextField {
emailTextFieldView.backgroundColor = #colorLiteral(red: 0.2972877622, green: 0.2973434925, blue: 0.297280401, alpha: 1)
emailInfoLabel.font = UIFont.systemFont(ofSize: 11)
emailInfoLabelCenterYConstraint.constant = -13
}
if textField == passwordTextField {
passwordTextFieldView.backgroundColor = #colorLiteral(red: 0.2972877622, green: 0.2973434925, blue: 0.297280401, alpha: 1)
passwordInfoLabel.font = UIFont.systemFont(ofSize: 11)
passwordInfoLabelCenterYConstraint.constant = -13
}
animateLabel()
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == emailTextField {
emailTextFieldView.backgroundColor = #colorLiteral(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
if emailTextField.text == "" {
emailInfoLabel.font = UIFont.systemFont(ofSize: 18)
emailInfoLabelCenterYConstraint.constant = 0
}
}
if textField == passwordTextField {
passwordTextFieldView.backgroundColor = #colorLiteral(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
if passwordTextField.text == "" {
passwordInfoLabel.font = UIFont.systemFont(ofSize: 18)
passwordInfoLabelCenterYConstraint.constant = 0
}
}
animateLabel()
}
@objc func textFieldEditingChanged(_ textField: UITextField) {
if textField.text?.count == 1 {
if textField.text?.first == " " {
textField.text = ""
return
}
}
guard
let email = emailTextField.text, !email.isEmpty,
let password = passwordTextField.text, !password.isEmpty else{
loginButton.isEnabled = false
loginButton.backgroundColor = .clear
return
}
loginButton.backgroundColor = .red
loginButton.isEnabled = true
}
func animateLabel() {
UIView.animate(withDuration: 0.3) {
self.stackView.layoutIfNeeded()
}
}
}