오토레이아웃, 스토리보드 없이 UI 짜기!
import UIKit
class ViewController: UIViewController {
// 클로저의 실행문 타입으로 속성 만들기 -> 코드 정리에 효과적!
let emailTextFieldView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.darkGray
// 모서리 둥글게
view.layer.cornerRadius = 20
view.layer.masksToBounds = true
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
makeUI()
}
func makeUI() {
// 뷰에다 추가!
view.addSubview(emailTextFieldView)
// 반드시 설정해야지만 오토레이아웃이 잘 나타난다 // 자동으로 잡아주는 것을 끈다!
emailTextFieldView.translatesAutoresizingMaskIntoConstraints = false
// 왼쪽에서 얼마를 띄울 것인지
emailTextFieldView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
// 오른쪽에서 얼마를 띄울 것인지
emailTextFieldView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
// 위쪽도
emailTextFieldView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200).isActive = true
// 높이 (높이는 기준이 없다)
emailTextFieldView.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
}
Swift
복사
//
// ViewController.swift
// LoginProject
//
// Created by KIM Hyung Jun on 2023/07/01.
//
import UIKit
class ViewController: UIViewController {
// 이메일 입력하는 텍스트 뷰
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.font = UIFont.systemFont(ofSize: 18)
label.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
return label
}()
// 로그인 - 이메일 입력 필드
private lazy var emailTextField: UITextField = {
var tf = UITextField()
tf.frame.size.height = 48
tf.backgroundColor = .clear
tf.textColor = .white
tf.tintColor = .white
tf.autocapitalizationType = .none
tf.autocorrectionType = .no
tf.spellCheckingType = .no
tf.keyboardType = .emailAddress
// tf.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editing)
return tf
}()
// 비밀번호 입력하는 텍스트 뷰
private lazy var passwordTextFieldView: UIView = {
let view = UIView()
view.frame.size.height = 48
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(passwordSecureButton)
return view
}()
// 패스퉈드 텍스트필드의 안내문구
private var passwordInfoLabel: UILabel = {
let label = UILabel()
label.text = "비밀번호"
label.font = UIFont.systemFont(ofSize: 18)
label.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
return label
}()
// 로그인 - 비밀번호 입력 필드
private let passwordTextField: UITextField = {
let tf = UITextField()
tf.backgroundColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1)
tf.frame.size.height = 48
tf.backgroundColor = .clear
tf.textColor = .white
tf.tintColor = .white
tf.autocapitalizationType = .none
tf.autocorrectionType = .no
tf.spellCheckingType = .no
tf.isSecureTextEntry = true // 비밀번호 가리게
tf.clearsOnBeginEditing = false
// tf.addTarget(self, action: #selector(textFIeldEditingChanged(_:)), for: .editingChanged)
return tf
}()
// 패스워드 "표시" 버튼 비밀번호 가리기 기능
private let passwordSecureButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("표시", for: .normal)
button.setTitleColor(#colorLiteral(red: 0.8374180198, green: 0.8374378085, blue: 0.8374271393, alpha: 1), for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .light)
// button.addTarget(self, action: #selector(passwordSecureModeSetting), for: .touchUpInside)
return button
}()
// 로그인 버튼
private let loginButton: UIButton = {
let button = UIButton(type: .custom)
button.backgroundColor = .clear
button.layer.cornerRadius = 5
button.clipsToBounds = true // 영역 내로 잘라낸다
button.layer.borderWidth = 1
button.layer.borderColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1)
button.setTitle("로그인", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
button.isEnabled = false
// button.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
return button
}()
// 스택 뷰
lazy var stackView: UIStackView = {
let st = UIStackView(arrangedSubviews: [emailTextFieldView, passwordTextFieldView, loginButton])
st.spacing = 18
st.axis = .vertical
st.distribution = .fillEqually
st.alignment = .fill
return st
}()
// 비밀번호 재설정 버튼
private let passwordResetButton: UIButton = {
let button = UIButton()
button.backgroundColor = .clear
button.setTitle("비밀번호 재설정", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
// button.addTarget(self, action: #selector(resetButtonTapped), for: .touchUpInside)
return button
}()
// 3개의 각 테스트필드 및 로그인 버튼의 높이 설정
private let textViewHeight: CGFloat = 48
override func viewDidLoad() {
super.viewDidLoad()
makeUI()
}
func makeUI() {
view.backgroundColor = UIColor.black
view.addSubview(stackView)
view.addSubview(passwordResetButton)
// 오토레이아웃을 설정하기 전에 false로 설정해야 한다!
emailInfoLabel.translatesAutoresizingMaskIntoConstraints = false
emailTextField.translatesAutoresizingMaskIntoConstraints = false
passwordInfoLabel.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
passwordSecureButton.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
passwordResetButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
emailInfoLabel.leadingAnchor.constraint(equalTo: emailTextFieldView.leadingAnchor, constant: 8),
emailInfoLabel.trailingAnchor.constraint(equalTo: emailTextFieldView.trailingAnchor, constant: 8),
emailInfoLabel.centerYAnchor.constraint(equalTo: emailTextFieldView.centerYAnchor),
emailTextField.leadingAnchor.constraint(equalTo: emailTextFieldView.leadingAnchor, constant: 8),
emailTextField.trailingAnchor.constraint(equalTo: emailTextFieldView.trailingAnchor, constant: 8),
emailTextField.topAnchor.constraint(equalTo: emailTextFieldView.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),
passwordInfoLabel.centerYAnchor.constraint(equalTo: passwordTextFieldView.centerYAnchor),
passwordTextField.topAnchor.constraint(equalTo: passwordTextFieldView.topAnchor, constant: 15),
passwordTextField.bottomAnchor.constraint(equalTo: passwordTextFieldView.bottomAnchor, constant: 2),
passwordTextField.leadingAnchor.constraint(equalTo: passwordTextFieldView.leadingAnchor, constant: 8),
passwordTextField.trailingAnchor.constraint(equalTo: passwordTextField.trailingAnchor, constant: 8),
passwordSecureButton.topAnchor.constraint(equalTo: passwordTextFieldView.topAnchor, constant: 15),
passwordSecureButton.bottomAnchor.constraint(equalTo: passwordTextFieldView.bottomAnchor, constant: -15),
passwordSecureButton.trailingAnchor.constraint(equalTo: passwordTextFieldView.trailingAnchor, constant: -8),
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.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 10),
passwordResetButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30),
passwordResetButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
passwordResetButton.heightAnchor.constraint(equalToConstant: textViewHeight),
])
}
}
Swift
복사
alert 창
// reset 버튼이 눌렸을 때 resetButtonTapped 함수를 실행
button.addTarget(self, action: #selector(resetButtonTapped), for: .touchUpInside)
// selector로 만들었기 떄문에 앞에 @objc를 붙인다.
@objc func resetButtonTapped() {
// print("리셋버튼이 눌렸습니다")
let alert = UIAlertController(title: "비밀번호 바꾸기", message: "비밀번호를 바꾸시겠습니까?", preferredStyle: .alert)
let success = UIAlertAction(title: "확인", style: .default) { action in
print("확인 버튼이 눌렸습니다.")
}
let cancel = UIAlertAction(title: "취소", style: .cancel) { cancel in
print("취소 버튼이 눌렸습니다.")
}
alert.addAction(success)
alert.addAction(cancel)
// 다음 화면으로 넘어가는 메서드
present(alert, animated: true, completion: nil)
}
Swift
복사
let alert = UIAlertController(title: "비밀번호 바꾸기", message: "비밀번호를 바꾸시겠습니까?", preferredStyle: .actionSheet)
Swift
복사
actionSheet으로 바꾸면
전체 코드
import UIKit
// class는 동적 table dispatch 때문에 struct보다 느리게 동작한다.
// final을 붙이면 더이상 상속을 못하게 막는다 -> 메서드가 direct dispatch를 일어나게 한다.
// 실무에서는 final을 붙여야 함!!!
final class ViewController: UIViewController {
// MARK: - 이메일 입력하는 텍스트 뷰
// private은 외부에서 접근하지 못하게, 내부에서만 접근 가능
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.font = UIFont.systemFont(ofSize: 18)
label.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
return label
}()
// 로그인 - 이메일 입력 필드
private lazy var emailTextField: UITextField = {
var tf = UITextField()
tf.frame.size.height = 48
tf.backgroundColor = .clear
tf.textColor = .white
tf.tintColor = .white
tf.autocapitalizationType = .none
tf.autocorrectionType = .no
tf.spellCheckingType = .no
tf.keyboardType = .emailAddress
tf.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editingChanged)
return tf
}()
// MARK: - 비밀번호 입력하는 텍스트 뷰
// let이라고 쓰면 메모리에 동시에 올라가기 때문에,
// addSubview 부터 순서대로 메모리에 올려야 하는데
// 반드시 lazy var를 사용하여야 한다!
private lazy var passwordTextFieldView: UIView = {
let view = UIView()
view.frame.size.height = 48
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(passwordSecureButton)
return view
}()
// 패스퉈드 텍스트필드의 안내문구
private var passwordInfoLabel: UILabel = {
let label = UILabel()
label.text = "비밀번호"
label.font = UIFont.systemFont(ofSize: 18)
label.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
return label
}()
// 로그인 - 비밀번호 입력 필드
private let passwordTextField: UITextField = {
let tf = UITextField()
tf.backgroundColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1)
tf.frame.size.height = 48
tf.backgroundColor = .clear
tf.textColor = .white
tf.tintColor = .white
tf.autocapitalizationType = .none
tf.autocorrectionType = .no
tf.spellCheckingType = .no
tf.isSecureTextEntry = true // 비밀번호 가리게
tf.clearsOnBeginEditing = false
tf.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editingChanged)
return tf
}()
// 패스워드 "표시" 버튼 비밀번호 가리기 기능
private let passwordSecureButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("표시", for: .normal)
button.setTitleColor(#colorLiteral(red: 0.8374180198, green: 0.8374378085, blue: 0.8374271393, alpha: 1), for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .light)
button.addTarget(self, action: #selector(passwordSecureModeSetting), for: .touchUpInside)
return button
}()
// MARK: - 로그인 버튼
private let loginButton: UIButton = {
let button = UIButton(type: .custom)
button.backgroundColor = .clear
button.layer.cornerRadius = 5
button.clipsToBounds = true // 영역 내로 잘라낸다
button.layer.borderWidth = 1
button.layer.borderColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1)
button.setTitle("로그인", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
button.isEnabled = false
button.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
return button
}()
// 스택 뷰
lazy var stackView: UIStackView = {
let st = UIStackView(arrangedSubviews: [emailTextFieldView, passwordTextFieldView, loginButton])
st.spacing = 18
st.axis = .vertical
st.distribution = .fillEqually
st.alignment = .fill
return st
}()
// 비밀번호 재설정 버튼
private let passwordResetButton: UIButton = {
let button = UIButton()
button.backgroundColor = .clear
button.setTitle("비밀번호 재설정", for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
// reset 버튼이 눌렸을 때 resetButtonTapped 함수를 실행
button.addTarget(self, action: #selector(resetButtonTapped), for: .touchUpInside)
return button
}()
// 3개의 각 테스트필드 및 로그인 버튼의 높이 설정
private let textViewHeight: CGFloat = 48
// 오토레이아웃 향후 변경을 위한 변수(애니메이션)
lazy var emailInfoLabelCenterYConstraint = emailInfoLabel.centerYAnchor.constraint(equalTo: emailTextFieldView.centerYAnchor)
lazy var passwordInfoLabelCenterYConstraint = passwordInfoLabel.centerYAnchor.constraint(equalTo: passwordTextFieldView.centerYAnchor)
override func viewDidLoad() {
super.viewDidLoad()
// delegate 패턴을 사용할때는 반드시 self로 설정해줘야 한다!
emailTextField.delegate = self
passwordTextField.delegate = self
makeUI()
}
func makeUI() {
view.backgroundColor = UIColor.black
view.addSubview(stackView)
view.addSubview(passwordResetButton)
// 오토레이아웃을 설정하기 전에 false로 설정해야 한다!
emailInfoLabel.translatesAutoresizingMaskIntoConstraints = false
emailTextField.translatesAutoresizingMaskIntoConstraints = false
passwordInfoLabel.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
passwordSecureButton.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
passwordResetButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
emailInfoLabel.leadingAnchor.constraint(equalTo: emailTextFieldView.leadingAnchor, constant: 8),
emailInfoLabel.trailingAnchor.constraint(equalTo: emailTextFieldView.trailingAnchor, constant: 8),
// emailInfoLabel.centerYAnchor.constraint(equalTo: emailTextFieldView.centerYAnchor),
emailInfoLabelCenterYConstraint,
emailTextField.leadingAnchor.constraint(equalTo: emailTextFieldView.leadingAnchor, constant: 8),
emailTextField.trailingAnchor.constraint(equalTo: emailTextFieldView.trailingAnchor, constant: 8),
emailTextField.topAnchor.constraint(equalTo: emailTextFieldView.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),
// passwordInfoLabel.centerYAnchor.constraint(equalTo: passwordTextFieldView.centerYAnchor),
passwordInfoLabelCenterYConstraint,
passwordTextField.topAnchor.constraint(equalTo: passwordTextFieldView.topAnchor, constant: 15),
passwordTextField.bottomAnchor.constraint(equalTo: passwordTextFieldView.bottomAnchor, constant: 2),
passwordTextField.leadingAnchor.constraint(equalTo: passwordTextFieldView.leadingAnchor, constant: 8),
passwordTextField.trailingAnchor.constraint(equalTo: passwordTextField.trailingAnchor, constant: 8),
passwordSecureButton.topAnchor.constraint(equalTo: passwordTextFieldView.topAnchor, constant: 15),
passwordSecureButton.bottomAnchor.constraint(equalTo: passwordTextFieldView.bottomAnchor, constant: -15),
passwordSecureButton.trailingAnchor.constraint(equalTo: passwordTextFieldView.trailingAnchor, constant: -8),
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.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 10),
passwordResetButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30),
passwordResetButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
passwordResetButton.heightAnchor.constraint(equalToConstant: textViewHeight),
])
}
@objc func passwordSecureModeSetting() {
// toggle() 메서드 -> 한번 눌리면 참!, 한번 더 눌리면 거짓!
passwordTextField.isSecureTextEntry.toggle()
}
@objc func loginButtonTapped() {
print("로그인 버튼이 눌렸습니다!")
}
// selector로 만들었기 떄문에 앞에 @objc를 붙인다.
@objc func resetButtonTapped() {
// print("리셋버튼이 눌렸습니다")
let alert = UIAlertController(title: "비밀번호 바꾸기", message: "비밀번호를 바꾸시겠습니까?", preferredStyle: .alert)
let success = UIAlertAction(title: "확인", style: .default) { action in
print("확인 버튼이 눌렸습니다.")
}
let cancel = UIAlertAction(title: "취소", style: .cancel) { cancel in
print("취소 버튼이 눌렸습니다.")
}
alert.addAction(success)
alert.addAction(cancel)
// 다음 화면으로 넘어가는 메서드
present(alert, animated: true, completion: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
}
// MARK: - 확장
extension ViewController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == emailTextField {
emailTextFieldView.backgroundColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1)
emailInfoLabel.font = UIFont.systemFont(ofSize: 11)
// 오토레이아웃 업데이트
emailInfoLabelCenterYConstraint.constant = -13
}
if textField == passwordTextField {
passwordTextFieldView.backgroundColor = #colorLiteral(red: 0.3333333433, green: 0.3333333433, blue: 0.3333333433, alpha: 1)
passwordInfoLabel.font = UIFont.systemFont(ofSize: 11)
// 오토레이아웃 업데이트
passwordInfoLabelCenterYConstraint.constant = -13
}
UIView.animate(withDuration: 0.3) {
self.stackView.layoutIfNeeded()
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == emailTextField {
emailTextFieldView.backgroundColor = #colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1)
// 빈칸이면 원래로 되돌리기
if emailTextField.text == "" {
emailInfoLabel.font = UIFont.systemFont(ofSize: 18)
emailInfoLabelCenterYConstraint.constant = 0
}
}
if textField == passwordTextField {
passwordTextFieldView.backgroundColor = #colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1)
// 빈칸이면 원래로 되돌리기
if passwordTextField.text == "" {
passwordInfoLabel.font = UIFont.systemFont(ofSize: 18)
}
passwordInfoLabelCenterYConstraint.constant = 0
}
UIView.animate(withDuration: 0.3) {
self.stackView.layoutIfNeeded()
}
}
@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.backgroundColor = .clear
loginButton.isEnabled = false // 버튼 비활성화
return
}
loginButton.backgroundColor = .red
loginButton.isEnabled = true // 버튼 활성화
}
}
Swift
복사