Search
Duplicate

오토레이아웃, 스토리보드 없이 UI 짜기! (alert 창)

생성일
2023/06/30 04:14
태그
Swift

오토레이아웃, 스토리보드 없이 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
복사